mirror of
https://github.com/bolucat/Archive.git
synced 2026-04-22 16:07:49 +08:00
Update On Mon Dec 8 19:42:39 CET 2025
This commit is contained in:
@@ -1205,3 +1205,4 @@ Update On Thu Dec 4 19:44:01 CET 2025
|
||||
Update On Fri Dec 5 19:41:34 CET 2025
|
||||
Update On Sat Dec 6 19:36:39 CET 2025
|
||||
Update On Sun Dec 7 19:36:46 CET 2025
|
||||
Update On Mon Dec 8 19:42:31 CET 2025
|
||||
|
||||
@@ -18,17 +18,11 @@ import (
|
||||
"github.com/metacubex/sing/common/network"
|
||||
)
|
||||
|
||||
type ListenerHandler struct {
|
||||
*sing.ListenerHandler
|
||||
DnsAdds []netip.AddrPort
|
||||
DisableICMPForwarding bool
|
||||
}
|
||||
|
||||
func (h *ListenerHandler) ShouldHijackDns(targetAddr netip.AddrPort) bool {
|
||||
if targetAddr.Addr().IsLoopback() && targetAddr.Port() == 53 { // cause by system stack
|
||||
return true
|
||||
}
|
||||
for _, addrPort := range h.DnsAdds {
|
||||
for _, addrPort := range h.DnsAddrPorts {
|
||||
if addrPort == targetAddr || (addrPort.Addr().IsUnspecified() && targetAddr.Port() == 53) {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package sing_tun
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
@@ -17,7 +18,7 @@ import (
|
||||
func (h *ListenerHandler) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
|
||||
switch network {
|
||||
case N.NetworkICMP: // our fork only send those type to PrepareConnection now
|
||||
if h.DisableICMPForwarding || resolver.IsFakeIP(destination.Addr) { // skip fakeip and if ICMP handling is disabled
|
||||
if h.DisableICMPForwarding || h.skipPingForwardingByAddr(destination.Addr) { // skip if ICMP handling is disabled or other condition
|
||||
log.Infoln("[ICMP] %s %s --> %s using fake ping echo", network, source, destination)
|
||||
return nil, nil
|
||||
}
|
||||
@@ -32,3 +33,20 @@ func (h *ListenerHandler) PrepareConnection(network string, source M.Socksaddr,
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (h *ListenerHandler) skipPingForwardingByAddr(addr netip.Addr) bool {
|
||||
for _, prefix := range h.Inet4Address { // skip in interface ipv4 range
|
||||
if prefix.Contains(addr) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for _, prefix := range h.Inet6Address { // skip in interface ipv6 range
|
||||
if prefix.Contains(addr) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if resolver.IsFakeIP(addr) { // skip in fakeIp pool
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -67,6 +67,14 @@ type Listener struct {
|
||||
dnsServerIp []string
|
||||
}
|
||||
|
||||
type ListenerHandler struct {
|
||||
*sing.ListenerHandler
|
||||
DnsAddrPorts []netip.AddrPort
|
||||
Inet4Address []netip.Prefix
|
||||
Inet6Address []netip.Prefix
|
||||
DisableICMPForwarding bool
|
||||
}
|
||||
|
||||
var emptyAddressSet = []*netipx.IPSet{{}}
|
||||
|
||||
func CalculateInterfaceName(name string) (tunName string) {
|
||||
@@ -268,7 +276,9 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis
|
||||
|
||||
handler := &ListenerHandler{
|
||||
ListenerHandler: h,
|
||||
DnsAdds: dnsAdds,
|
||||
DnsAddrPorts: dnsAdds,
|
||||
Inet4Address: options.Inet4Address,
|
||||
Inet6Address: options.Inet6Address,
|
||||
DisableICMPForwarding: options.DisableICMPForwarding,
|
||||
}
|
||||
l = &Listener{
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
LINUX_VERSION-5.10 = .246
|
||||
LINUX_KERNEL_HASH-5.10.246 = eb6b76c269d2dc09791638b10b9dcb9d79fd2abd45113a31fc03f68731caa875
|
||||
LINUX_VERSION-5.10 = .247
|
||||
LINUX_KERNEL_HASH-5.10.247 = 70c8b87ba1fcd8bfa663661934dc9bda92d0b5f3c0fc3197bb56399f69d9fe0c
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
LINUX_VERSION-5.15 = .196
|
||||
LINUX_KERNEL_HASH-5.15.196 = 83157953598b026fb721c906c60dfdfd8e986f25ecb9910f3504f690e2770e05
|
||||
LINUX_VERSION-5.15 = .197
|
||||
LINUX_KERNEL_HASH-5.15.197 = fd218df8e2107a4443b6c29fef7f95aad167031e0fbdbc7a858ae8471360668a
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
LINUX_VERSION-5.4 = .301
|
||||
LINUX_KERNEL_HASH-5.4.301 = b7718766d060e6714bbe47004c71c360e844758f42fbf62cbaa5571119527962
|
||||
LINUX_VERSION-5.4 = .302
|
||||
LINUX_KERNEL_HASH-5.4.302 = ae6a3207f12aa4d6cfb0fa793ec9da4a6fcdfdcb57d869d63d6b77e3a8c1423d
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
LINUX_VERSION-6.1 = .158
|
||||
LINUX_KERNEL_HASH-6.1.158 = ad068bfdb604ec0f4f7de385c8e7ab944008aa78a4aeeca94f53206e6726bfda
|
||||
LINUX_VERSION-6.1 = .159
|
||||
LINUX_KERNEL_HASH-6.1.159 = 1f207ebe93980829ecc0a18b694816f22b715e9893767731651969a168342b9e
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
LINUX_VERSION-6.12 = .60
|
||||
LINUX_KERNEL_HASH-6.12.60 = a63096b2147411d683cecbf87622bb2ff4885bac2b3641d3d4f10250c89cdcf8
|
||||
LINUX_VERSION-6.12 = .61
|
||||
LINUX_KERNEL_HASH-6.12.61 = 1a69745105528676f12f29dc2494945d96cb23666dcc5223794abc22415f1735
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
LINUX_VERSION-6.6 = .118
|
||||
LINUX_KERNEL_HASH-6.6.118 = 4bdddce35474afc8d26f74ebfbcd0e1045ecd15f69e60f53529dba143374b17d
|
||||
LINUX_VERSION-6.6 = .119
|
||||
LINUX_KERNEL_HASH-6.6.119 = 3da09b980bb404cc28793479bb2d6c636522679215ffa65a04c893575253e5e8
|
||||
|
||||
@@ -53,8 +53,10 @@
|
||||
if (priv->sreg_proto_min) {
|
||||
if (nft_dump_register(skb, NFTA_MASQ_REG_PROTO_MIN,
|
||||
priv->sreg_proto_min) ||
|
||||
@@ -112,6 +120,9 @@ static void nft_masq_eval(const struct n
|
||||
@@ -112,6 +120,11 @@ static void nft_masq_eval(const struct n
|
||||
{
|
||||
+ struct nft_masq *priv = nft_expr_priv(expr);
|
||||
+ struct nf_nat_range2 range;
|
||||
switch (nft_pf(pkt)) {
|
||||
case NFPROTO_IPV4:
|
||||
+ if (priv->fullcone) {
|
||||
|
||||
@@ -18,17 +18,11 @@ import (
|
||||
"github.com/metacubex/sing/common/network"
|
||||
)
|
||||
|
||||
type ListenerHandler struct {
|
||||
*sing.ListenerHandler
|
||||
DnsAdds []netip.AddrPort
|
||||
DisableICMPForwarding bool
|
||||
}
|
||||
|
||||
func (h *ListenerHandler) ShouldHijackDns(targetAddr netip.AddrPort) bool {
|
||||
if targetAddr.Addr().IsLoopback() && targetAddr.Port() == 53 { // cause by system stack
|
||||
return true
|
||||
}
|
||||
for _, addrPort := range h.DnsAdds {
|
||||
for _, addrPort := range h.DnsAddrPorts {
|
||||
if addrPort == targetAddr || (addrPort.Addr().IsUnspecified() && targetAddr.Port() == 53) {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package sing_tun
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
@@ -17,7 +18,7 @@ import (
|
||||
func (h *ListenerHandler) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
|
||||
switch network {
|
||||
case N.NetworkICMP: // our fork only send those type to PrepareConnection now
|
||||
if h.DisableICMPForwarding || resolver.IsFakeIP(destination.Addr) { // skip fakeip and if ICMP handling is disabled
|
||||
if h.DisableICMPForwarding || h.skipPingForwardingByAddr(destination.Addr) { // skip if ICMP handling is disabled or other condition
|
||||
log.Infoln("[ICMP] %s %s --> %s using fake ping echo", network, source, destination)
|
||||
return nil, nil
|
||||
}
|
||||
@@ -32,3 +33,20 @@ func (h *ListenerHandler) PrepareConnection(network string, source M.Socksaddr,
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (h *ListenerHandler) skipPingForwardingByAddr(addr netip.Addr) bool {
|
||||
for _, prefix := range h.Inet4Address { // skip in interface ipv4 range
|
||||
if prefix.Contains(addr) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for _, prefix := range h.Inet6Address { // skip in interface ipv6 range
|
||||
if prefix.Contains(addr) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if resolver.IsFakeIP(addr) { // skip in fakeIp pool
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -67,6 +67,14 @@ type Listener struct {
|
||||
dnsServerIp []string
|
||||
}
|
||||
|
||||
type ListenerHandler struct {
|
||||
*sing.ListenerHandler
|
||||
DnsAddrPorts []netip.AddrPort
|
||||
Inet4Address []netip.Prefix
|
||||
Inet6Address []netip.Prefix
|
||||
DisableICMPForwarding bool
|
||||
}
|
||||
|
||||
var emptyAddressSet = []*netipx.IPSet{{}}
|
||||
|
||||
func CalculateInterfaceName(name string) (tunName string) {
|
||||
@@ -268,7 +276,9 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis
|
||||
|
||||
handler := &ListenerHandler{
|
||||
ListenerHandler: h,
|
||||
DnsAdds: dnsAdds,
|
||||
DnsAddrPorts: dnsAdds,
|
||||
Inet4Address: options.Inet4Address,
|
||||
Inet6Address: options.Inet6Address,
|
||||
DisableICMPForwarding: options.DisableICMPForwarding,
|
||||
}
|
||||
l = &Listener{
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=adguardhome
|
||||
PKG_VERSION:=0.107.70
|
||||
PKG_VERSION:=0.107.71
|
||||
PKG_RELEASE:=1
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/AdguardTeam/AdGuardHome/tar.gz/v$(PKG_VERSION)?
|
||||
PKG_HASH:=5eb0c7076ab7b007f0e5afbaffc260d8950b5248bd04407464a2b2f0169694ba
|
||||
PKG_HASH:=f3dde5da6ba48270ac25bd2f501c4ce1af54ddeef93fcd84ef3a8270cec9539f
|
||||
PKG_BUILD_DIR:=$(BUILD_DIR)/AdGuardHome-$(PKG_VERSION)
|
||||
|
||||
PKG_LICENSE:=GPL-3.0-only
|
||||
@@ -58,7 +58,7 @@ define Download/adguardhome-frontend
|
||||
URL:=https://github.com/AdguardTeam/AdGuardHome/releases/download/v$(PKG_VERSION)/
|
||||
URL_FILE:=AdGuardHome_frontend.tar.gz
|
||||
FILE:=$(FRONTEND_FILE)
|
||||
HASH:=127658a4155d429fc8f7c6a4408107662a963b39f6db869f13dcd40aff12e6eb
|
||||
HASH:=51b229a5dff010c17bd8894bbf4291907e93708c366801e32181be7f37dd4488
|
||||
endef
|
||||
|
||||
define Build/Prepare
|
||||
|
||||
@@ -511,6 +511,9 @@ o = s:option(DynamicList, _n("tcp_guise_http_path"), translate("HTTP Path"))
|
||||
o.placeholder = "/"
|
||||
o:depends({ [_n("tcp_guise")] = "http" })
|
||||
|
||||
o = s:option(Value, _n("tcp_guise_http_user_agent"), translate("User-Agent"))
|
||||
o:depends({ [_n("tcp_guise")] = "http" })
|
||||
|
||||
-- [[ mKCP部分 ]]--
|
||||
|
||||
o = s:option(ListValue, _n("mkcp_guise"), translate("Camouflage Type"), translate('<br />none: default, no masquerade, data sent is packets with no characteristics.<br />srtp: disguised as an SRTP packet, it will be recognized as video call data (such as FaceTime).<br />utp: packets disguised as uTP will be recognized as bittorrent downloaded data.<br />wechat-video: packets disguised as WeChat video calls.<br />dtls: disguised as DTLS 1.2 packet.<br />wireguard: disguised as a WireGuard packet. (not really WireGuard protocol)<br />dns: Disguising traffic as DNS requests.'))
|
||||
@@ -558,6 +561,9 @@ o = s:option(Value, _n("ws_path"), translate("WebSocket Path"))
|
||||
o.placeholder = "/"
|
||||
o:depends({ [_n("transport")] = "ws" })
|
||||
|
||||
o = s:option(Value, _n("ws_user_agent"), translate("User-Agent"))
|
||||
o:depends({ [_n("transport")] = "ws" })
|
||||
|
||||
o = s:option(Value, _n("ws_heartbeatPeriod"), translate("HeartbeatPeriod(second)"))
|
||||
o.datatype = "integer"
|
||||
o:depends({ [_n("transport")] = "ws" })
|
||||
@@ -598,6 +604,9 @@ o = s:option(Value, _n("httpupgrade_path"), translate("HttpUpgrade Path"))
|
||||
o.placeholder = "/"
|
||||
o:depends({ [_n("transport")] = "httpupgrade" })
|
||||
|
||||
o = s:option(Value, _n("httpupgrade_user_agent"), translate("User-Agent"))
|
||||
o:depends({ [_n("transport")] = "httpupgrade" })
|
||||
|
||||
-- [[ XHTTP部分 ]]--
|
||||
o = s:option(ListValue, _n("xhttp_mode"), "XHTTP " .. translate("Mode"))
|
||||
o:depends({ [_n("transport")] = "xhttp" })
|
||||
|
||||
@@ -609,6 +609,9 @@ o = s:option(DynamicList, _n("tcp_guise_http_path"), translate("HTTP Path"))
|
||||
o.placeholder = "/"
|
||||
o:depends({ [_n("tcp_guise")] = "http" })
|
||||
|
||||
o = s:option(Value, _n("tcp_guise_http_user_agent"), translate("User-Agent"))
|
||||
o:depends({ [_n("tcp_guise")] = "http" })
|
||||
|
||||
-- [[ HTTP部分 ]]--
|
||||
o = s:option(DynamicList, _n("http_host"), translate("HTTP Host"))
|
||||
o:depends({ [_n("transport")] = "http" })
|
||||
@@ -617,6 +620,9 @@ o = s:option(Value, _n("http_path"), translate("HTTP Path"))
|
||||
o.placeholder = "/"
|
||||
o:depends({ [_n("transport")] = "http" })
|
||||
|
||||
o = s:option(Value, _n("http_user_agent"), translate("User-Agent"))
|
||||
o:depends({ [_n("transport")] = "http" })
|
||||
|
||||
o = s:option(Flag, _n("http_h2_health_check"), translate("Health check"))
|
||||
o:depends({ [_n("tls")] = true, [_n("transport")] = "http" })
|
||||
|
||||
@@ -636,6 +642,9 @@ o = s:option(Value, _n("ws_path"), translate("WebSocket Path"))
|
||||
o.placeholder = "/"
|
||||
o:depends({ [_n("transport")] = "ws" })
|
||||
|
||||
o = s:option(Value, _n("ws_user_agent"), translate("User-Agent"))
|
||||
o:depends({ [_n("transport")] = "ws" })
|
||||
|
||||
o = s:option(Flag, _n("ws_enableEarlyData"), translate("Enable early data"))
|
||||
o:depends({ [_n("transport")] = "ws" })
|
||||
|
||||
@@ -654,6 +663,9 @@ o = s:option(Value, _n("httpupgrade_path"), translate("HTTPUpgrade Path"))
|
||||
o.placeholder = "/"
|
||||
o:depends({ [_n("transport")] = "httpupgrade" })
|
||||
|
||||
o = s:option(Value, _n("httpupgrade_user_agent"), translate("User-Agent"))
|
||||
o:depends({ [_n("transport")] = "httpupgrade" })
|
||||
|
||||
-- [[ gRPC部分 ]]--
|
||||
o = s:option(Value, _n("grpc_serviceName"), "ServiceName")
|
||||
o:depends({ [_n("transport")] = "grpc" })
|
||||
|
||||
@@ -206,6 +206,9 @@ function gen_outbound(flag, node, tag, proxy_table)
|
||||
local first = node.tcp_guise_http_path[1]
|
||||
return (first == "" or not first) and "/" or first
|
||||
end)() or "/",
|
||||
headers = {
|
||||
["User-Agent"] = node.tcp_guise_http_user_agent or nil
|
||||
},
|
||||
idle_timeout = (node.http_h2_health_check == "1") and node.http_h2_read_idle_timeout or nil,
|
||||
ping_timeout = (node.http_h2_health_check == "1") and node.http_h2_health_check_timeout or nil,
|
||||
}
|
||||
@@ -217,6 +220,9 @@ function gen_outbound(flag, node, tag, proxy_table)
|
||||
type = "http",
|
||||
host = node.http_host or {},
|
||||
path = node.http_path or "/",
|
||||
headers = {
|
||||
["User-Agent"] = node.http_user_agent or nil
|
||||
},
|
||||
idle_timeout = (node.http_h2_health_check == "1") and node.http_h2_read_idle_timeout or nil,
|
||||
ping_timeout = (node.http_h2_health_check == "1") and node.http_h2_health_check_timeout or nil,
|
||||
}
|
||||
@@ -227,7 +233,10 @@ function gen_outbound(flag, node, tag, proxy_table)
|
||||
v2ray_transport = {
|
||||
type = "ws",
|
||||
path = node.ws_path or "/",
|
||||
headers = (node.ws_host ~= nil) and { Host = node.ws_host } or nil,
|
||||
headers = {
|
||||
Host = node.ws_host or nil,
|
||||
["User-Agent"] = node.ws_user_agent or nil
|
||||
},
|
||||
max_early_data = tonumber(node.ws_maxEarlyData) or nil,
|
||||
early_data_header_name = (node.ws_earlyDataHeaderName) and node.ws_earlyDataHeaderName or nil --要与 Xray-core 兼容,请将其设置为 Sec-WebSocket-Protocol。它需要与服务器保持一致。
|
||||
}
|
||||
@@ -238,6 +247,9 @@ function gen_outbound(flag, node, tag, proxy_table)
|
||||
type = "httpupgrade",
|
||||
host = node.httpupgrade_host,
|
||||
path = node.httpupgrade_path or "/",
|
||||
headers = {
|
||||
["User-Agent"] = node.httpupgrade_user_agent or nil
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
@@ -179,7 +179,8 @@ function gen_outbound(flag, node, tag, proxy_table)
|
||||
return r
|
||||
end)() or {"/"},
|
||||
headers = {
|
||||
Host = node.tcp_guise_http_host or {}
|
||||
Host = node.tcp_guise_http_host or {},
|
||||
["User-Agent"] = node.tcp_guise_http_user_agent and {node.tcp_guise_http_user_agent} or nil
|
||||
}
|
||||
} or nil
|
||||
}
|
||||
@@ -200,7 +201,10 @@ function gen_outbound(flag, node, tag, proxy_table)
|
||||
} or nil,
|
||||
wsSettings = (node.transport == "ws") and {
|
||||
path = node.ws_path or "/",
|
||||
host = node.ws_host or nil,
|
||||
headers = {
|
||||
Host = node.ws_host or nil,
|
||||
["User-Agent"] = node.ws_user_agent or nil
|
||||
},
|
||||
maxEarlyData = tonumber(node.ws_maxEarlyData) or nil,
|
||||
earlyDataHeaderName = (node.ws_earlyDataHeaderName) and node.ws_earlyDataHeaderName or nil,
|
||||
heartbeatPeriod = tonumber(node.ws_heartbeatPeriod) or nil
|
||||
@@ -215,7 +219,10 @@ function gen_outbound(flag, node, tag, proxy_table)
|
||||
} or nil,
|
||||
httpupgradeSettings = (node.transport == "httpupgrade") and {
|
||||
path = node.httpupgrade_path or "/",
|
||||
host = node.httpupgrade_host
|
||||
host = node.httpupgrade_host,
|
||||
headers = {
|
||||
["User-Agent"] = node.httpupgrade_user_agent or nil
|
||||
}
|
||||
} or nil,
|
||||
xhttpSettings = (node.transport == "xhttp") and {
|
||||
mode = node.xhttp_mode or "auto",
|
||||
|
||||
@@ -65,14 +65,14 @@ end
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="<%=cbid%>" style="display: inline-block;">
|
||||
<div id="<%=cbid%>" class="cbi-input-select" style="display: inline-block;">
|
||||
<!-- 搜索 -->
|
||||
<input type="text"
|
||||
id="<%=cbid%>.search"
|
||||
class="node-search-input cbi-input-text"
|
||||
placeholder="<%:Search nodes...%>"
|
||||
oninput="filterGroups_<%=self.option%>(this.value)"
|
||||
style="width:100%;padding:6px;margin-bottom:8px;border:1px solid #ccc;border-radius:4px;box-sizing:border-box;" />
|
||||
style="width:100%;padding:6px;margin-bottom:8px;border:1px solid #ccc;border-radius:4px;box-sizing:border-box;max-height:36px;" />
|
||||
<!-- 主容器 -->
|
||||
<div style="max-height:300px; overflow:auto; margin-bottom:8px; white-space:nowrap;">
|
||||
<ul class="cbi-multi" id="<%=cbid%>.node_list" style="padding:0 !important;margin:0 !important;width:100%;box-sizing:border-box;">
|
||||
@@ -85,7 +85,7 @@ end
|
||||
style="cursor:pointer;padding:6px;background:#f0f0f0;border-radius:4px;margin-bottom:4px;display:flex;align-items:center;white-space:nowrap;">
|
||||
<span id="arrow-<%=self.option%>-<%=gname%>" class="lv-arrow-down-small"></span>
|
||||
<b style="margin-left:8px;"><%=gname%></b>
|
||||
<span id="group-count-<%=self.option%>-<%=gname%>" style="margin-left:8px;color:blue;">
|
||||
<span id="group-count-<%=self.option%>-<%=gname%>" style="margin-left:8px;color:#007bff;">
|
||||
(0/<%=#items%>)
|
||||
</span>
|
||||
</div>
|
||||
@@ -93,7 +93,7 @@ end
|
||||
<ul id="group-<%=self.option%>-<%=gname%>" style="margin:0 0 8px 16px; padding:0; list-style:none;">
|
||||
|
||||
<% for _, item in ipairs(items) do %>
|
||||
<li data-node-name="<%=pcdata(item.label):lower()%>" style="list-style:none; padding:0; margin:0; white-space:nowrap;">
|
||||
<li data-node-name="<%=pcdata(item.label):lower()%>" style="list-style:none;padding:0;margin:0;white-space:nowrap;" title="<%=pcdata(item.label)%>">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="cbi-input-checkbox"
|
||||
|
||||
+9
-9
@@ -30,9 +30,9 @@ jobs:
|
||||
- name: Install Rust
|
||||
run: |
|
||||
rustup set profile minimal
|
||||
rustup toolchain install 1.85
|
||||
rustup default 1.85
|
||||
rustup override set 1.85
|
||||
rustup toolchain install 1.88
|
||||
rustup default 1.88
|
||||
rustup override set 1.88
|
||||
- name: Build with All Features Enabled (Unix)
|
||||
if: ${{ runner.os == 'Linux' || runner.os == 'macOS' }}
|
||||
run: cargo build --verbose --features "full-extra local-flow-stat utility-url-outline"
|
||||
@@ -63,9 +63,9 @@ jobs:
|
||||
- name: Install Rust
|
||||
run: |
|
||||
rustup set profile minimal
|
||||
rustup toolchain install 1.85
|
||||
rustup default 1.85
|
||||
rustup override set 1.85
|
||||
rustup toolchain install 1.88
|
||||
rustup default 1.88
|
||||
rustup override set 1.88
|
||||
- name: Build with All Features Enabled
|
||||
run: cargo build --manifest-path crates/shadowsocks-service/Cargo.toml --verbose --features "full dns-over-tls dns-over-https dns-over-h3 local-dns local-flow-stat local-http-rustls local-tun local-fake-dns local-online-config stream-cipher aead-cipher-extra aead-cipher-2022 aead-cipher-2022-extra security-replay-attack-detect"
|
||||
|
||||
@@ -92,8 +92,8 @@ jobs:
|
||||
- name: Install Rust
|
||||
run: |
|
||||
rustup set profile minimal
|
||||
rustup toolchain install 1.85
|
||||
rustup default 1.85
|
||||
rustup override set 1.85
|
||||
rustup toolchain install 1.88
|
||||
rustup default 1.88
|
||||
rustup override set 1.88
|
||||
- name: Build with All Features Enabled
|
||||
run: cargo build --manifest-path crates/shadowsocks/Cargo.toml --verbose --features "stream-cipher aead-cipher-extra aead-cipher-2022 aead-cipher-2022-extra security-replay-attack-detect"
|
||||
|
||||
Generated
+2
-2
@@ -1766,9 +1766,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "json5"
|
||||
version = "1.2.0"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bfcd7c9a7e6d6270614c3c7ccec2ec6fb0bf7f6da3384042e80e737cde1f941"
|
||||
checksum = "56c86c72f9e1d3fe29baa32cab8896548eef9aae271fce4e796d16b583fdf6d5"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"ucd-trie",
|
||||
|
||||
@@ -9,7 +9,7 @@ documentation = "https://docs.rs/shadowsocks-rust"
|
||||
keywords = ["shadowsocks", "proxy", "socks", "socks5", "firewall"]
|
||||
license = "MIT"
|
||||
edition = "2024"
|
||||
rust-version = "1.85"
|
||||
rust-version = "1.88"
|
||||
|
||||
[badges]
|
||||
maintenance = { status = "passively-maintained" }
|
||||
@@ -219,7 +219,7 @@ tracing-appender = { version = "0.2.3", optional = true, default-features = fals
|
||||
time = { version = "0.3", optional = true }
|
||||
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
json5 = "1.2"
|
||||
json5 = "1.3"
|
||||
thiserror = "2.0"
|
||||
base64 = "0.22"
|
||||
|
||||
|
||||
@@ -18,8 +18,8 @@ fn main() -> ExitCode {
|
||||
.about("A fast tunnel proxy that helps you bypass firewalls. (https://shadowsocks.org)");
|
||||
|
||||
// Allow running `ssservice` as symlink of `sslocal`, `ssserver` and `ssmanager`
|
||||
if let Some(program_path) = env::args().next() {
|
||||
if let Some(program_name) = Path::new(&program_path).file_name() {
|
||||
if let Some(program_path) = env::args().next()
|
||||
&& let Some(program_name) = Path::new(&program_path).file_name() {
|
||||
match program_name.to_str() {
|
||||
Some("sslocal") => return local::main(&local::define_command_line_options(app).get_matches()),
|
||||
Some("ssserver") => return server::main(&server::define_command_line_options(app).get_matches()),
|
||||
@@ -27,7 +27,6 @@ fn main() -> ExitCode {
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let matches = app
|
||||
.subcommand_required(true)
|
||||
|
||||
@@ -1 +1 @@
|
||||
msrv = "1.85"
|
||||
msrv = "1.88"
|
||||
|
||||
@@ -9,7 +9,7 @@ documentation = "https://docs.rs/shadowsocks-service"
|
||||
keywords = ["shadowsocks", "proxy", "socks", "socks5", "firewall"]
|
||||
license = "MIT"
|
||||
edition = "2024"
|
||||
rust-version = "1.85"
|
||||
rust-version = "1.88"
|
||||
|
||||
[badges]
|
||||
maintenance = { status = "passively-maintained" }
|
||||
@@ -195,7 +195,7 @@ smoltcp = { version = "0.12", optional = true, default-features = false, feature
|
||||
] }
|
||||
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
json5 = "1.2"
|
||||
json5 = "1.3"
|
||||
serde_json = "1.0"
|
||||
bson = { version = "3.0.0", features = ["serde"], optional = true }
|
||||
|
||||
|
||||
@@ -208,15 +208,14 @@ impl ParsingRules {
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else if let Some(set_rule) = caps.get(2) {
|
||||
if let Ok(set_rule) = str::from_utf8(set_rule.as_bytes()) {
|
||||
} else if let Some(set_rule) = caps.get(2)
|
||||
&& let Ok(set_rule) = str::from_utf8(set_rule.as_bytes()) {
|
||||
let set_rule = set_rule.replace("\\.", ".");
|
||||
if self.add_set_rule_inner(&set_rule).is_ok() {
|
||||
trace!("REGEX-RULE {} => SET-RULE {}", rule, set_rule);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trace!("REGEX-RULE {}", rule);
|
||||
|
||||
@@ -2388,9 +2388,9 @@ impl Config {
|
||||
}
|
||||
|
||||
// Security
|
||||
if let Some(sec) = config.security {
|
||||
if let Some(replay_attack) = sec.replay_attack {
|
||||
if let Some(policy) = replay_attack.policy {
|
||||
if let Some(sec) = config.security
|
||||
&& let Some(replay_attack) = sec.replay_attack
|
||||
&& let Some(policy) = replay_attack.policy {
|
||||
match policy.parse::<ReplayAttackPolicy>() {
|
||||
Ok(p) => nconfig.security.replay_attack.policy = p,
|
||||
Err(..) => {
|
||||
@@ -2399,8 +2399,6 @@ impl Config {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(balancer) = config.balancer {
|
||||
nconfig.balancer = BalancerConfig {
|
||||
@@ -2620,19 +2618,17 @@ impl Config {
|
||||
}
|
||||
|
||||
// Balancer related checks
|
||||
if let Some(rtt) = self.balancer.max_server_rtt {
|
||||
if rtt.as_secs() == 0 {
|
||||
if let Some(rtt) = self.balancer.max_server_rtt
|
||||
&& rtt.as_secs() == 0 {
|
||||
let err = Error::new(ErrorKind::Invalid, "balancer.max_server_rtt must be > 0", None);
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(intv) = self.balancer.check_interval {
|
||||
if intv.as_secs() == 0 {
|
||||
if let Some(intv) = self.balancer.check_interval
|
||||
&& intv.as_secs() == 0 {
|
||||
let err = Error::new(ErrorKind::Invalid, "balancer.check_interval must be > 0", None);
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.config_type.is_server() && self.server.is_empty() {
|
||||
@@ -2667,12 +2663,11 @@ impl Config {
|
||||
let server = &inst.config;
|
||||
|
||||
// Plugin shouldn't be an empty string
|
||||
if let Some(plugin) = server.plugin() {
|
||||
if plugin.plugin.trim().is_empty() {
|
||||
if let Some(plugin) = server.plugin()
|
||||
&& plugin.plugin.trim().is_empty() {
|
||||
let err = Error::new(ErrorKind::Malformed, "`plugin` shouldn't be an empty string", None);
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
|
||||
// Server's domain name shouldn't be an empty string
|
||||
match server.addr() {
|
||||
@@ -3072,14 +3067,13 @@ impl fmt::Display for Config {
|
||||
jconf.mode = Some(m.mode.to_string());
|
||||
}
|
||||
|
||||
if jconf.method.is_none() {
|
||||
if let Some(ref m) = m.method {
|
||||
if jconf.method.is_none()
|
||||
&& let Some(ref m) = m.method {
|
||||
jconf.method = Some(m.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
if jconf.plugin.is_none() {
|
||||
if let Some(ref p) = m.plugin {
|
||||
if jconf.plugin.is_none()
|
||||
&& let Some(ref p) = m.plugin {
|
||||
jconf.plugin = Some(p.plugin.clone());
|
||||
if let Some(ref o) = p.plugin_opts {
|
||||
jconf.plugin_opts = Some(o.clone());
|
||||
@@ -3088,7 +3082,6 @@ impl fmt::Display for Config {
|
||||
jconf.plugin_args = Some(p.plugin_args.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.no_delay {
|
||||
@@ -3194,8 +3187,8 @@ impl fmt::Display for Config {
|
||||
/// If value is in format `${VAR_NAME}` then it will try to read from `VAR_NAME` environment variable.
|
||||
/// It will return the original value if fails to read `${VAR_NAME}`.
|
||||
pub fn read_variable_field_value(value: &str) -> Cow<'_, str> {
|
||||
if let Some(left_over) = value.strip_prefix("${") {
|
||||
if let Some(var_name) = left_over.strip_suffix('}') {
|
||||
if let Some(left_over) = value.strip_prefix("${")
|
||||
&& let Some(var_name) = left_over.strip_suffix('}') {
|
||||
match env::var(var_name) {
|
||||
Ok(value) => return value.into(),
|
||||
Err(err) => {
|
||||
@@ -3206,7 +3199,6 @@ pub fn read_variable_field_value(value: &str) -> Cow<'_, str> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
value.into()
|
||||
}
|
||||
|
||||
+2
-3
@@ -163,11 +163,10 @@ impl PingBalancerBuilder {
|
||||
}
|
||||
|
||||
pub async fn build(self) -> io::Result<PingBalancer> {
|
||||
if let Some(intv) = self.check_best_interval {
|
||||
if intv > self.check_interval {
|
||||
if let Some(intv) = self.check_best_interval
|
||||
&& intv > self.check_interval {
|
||||
return Err(io::Error::other("check_interval must be >= check_best_interval"));
|
||||
}
|
||||
}
|
||||
|
||||
let (shared_context, task_abortable) = PingBalancerContext::new(
|
||||
self.servers,
|
||||
|
||||
@@ -193,7 +193,7 @@ impl ServerStat {
|
||||
vlat_abs_diff.sort_unstable();
|
||||
|
||||
let abs_diff_median_mid = vlat_abs_diff.len() / 2;
|
||||
self.data.latency_mad = if vlat_abs_diff.len() % 2 == 0 {
|
||||
self.data.latency_mad = if vlat_abs_diff.len().is_multiple_of(2) {
|
||||
(vlat_abs_diff[abs_diff_median_mid] + vlat_abs_diff[abs_diff_median_mid - 1]) / 2
|
||||
} else {
|
||||
vlat_abs_diff[abs_diff_median_mid]
|
||||
|
||||
@@ -515,12 +515,11 @@ where
|
||||
}
|
||||
};
|
||||
|
||||
if UDP_SOCKET_SUPPORT_DUAL_STACK {
|
||||
if let SocketAddr::V4(saddr) = target_addr {
|
||||
if UDP_SOCKET_SUPPORT_DUAL_STACK
|
||||
&& let SocketAddr::V4(saddr) = target_addr {
|
||||
let mapped_ip = saddr.ip().to_ipv6_mapped();
|
||||
target_addr = SocketAddr::V6(SocketAddrV6::new(mapped_ip, saddr.port(), 0, 0));
|
||||
}
|
||||
}
|
||||
|
||||
let n = socket.send_to(data, target_addr).await?;
|
||||
if n != data.len() {
|
||||
|
||||
@@ -214,15 +214,14 @@ impl OnlineConfigService {
|
||||
// Check plugin whitelist
|
||||
if let Some(ref allowed_plugins) = self.allowed_plugins {
|
||||
for server in &online_config.server {
|
||||
if let Some(plugin) = server.config.plugin() {
|
||||
if !allowed_plugins.contains(&plugin.plugin) {
|
||||
if let Some(plugin) = server.config.plugin()
|
||||
&& !allowed_plugins.contains(&plugin.plugin) {
|
||||
error!(
|
||||
"server-loader task found not allowed plugin: {}, url: {}",
|
||||
plugin.plugin, self.config_url
|
||||
);
|
||||
return Err(io::Error::other(format!("not allowed plugin: {}", plugin.plugin)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -63,11 +63,10 @@ async fn handle_redir_client(
|
||||
// Get forward address from socket
|
||||
//
|
||||
// Try to convert IPv4 mapped IPv6 address for dual-stack mode.
|
||||
if let SocketAddr::V6(ref a) = daddr {
|
||||
if let Some(v4) = to_ipv4_mapped(a.ip()) {
|
||||
if let SocketAddr::V6(ref a) = daddr
|
||||
&& let Some(v4) = to_ipv4_mapped(a.ip()) {
|
||||
daddr = SocketAddr::new(IpAddr::from(v4), a.port());
|
||||
}
|
||||
}
|
||||
let target_addr = Address::from(daddr);
|
||||
establish_client_tcp_redir(context, balancer, s, peer_addr, &target_addr).await
|
||||
}
|
||||
|
||||
@@ -296,11 +296,10 @@ impl RedirUdpServer {
|
||||
}
|
||||
|
||||
// Try to convert IPv4 mapped IPv6 address for dual-stack mode.
|
||||
if let SocketAddr::V6(ref a) = dst {
|
||||
if let Some(v4) = to_ipv4_mapped(a.ip()) {
|
||||
if let SocketAddr::V6(ref a) = dst
|
||||
&& let Some(v4) = to_ipv4_mapped(a.ip()) {
|
||||
dst = SocketAddr::new(IpAddr::from(v4), a.port());
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(err) = manager.send_to(src, Address::from(dst), pkt).await {
|
||||
debug!(
|
||||
|
||||
+2
-3
@@ -71,8 +71,8 @@ impl UdpRedirSocket {
|
||||
|
||||
socket.set_nonblocking(true)?;
|
||||
socket.set_reuse_address(true)?;
|
||||
if reuse_port {
|
||||
if let Err(err) = socket.set_reuse_port(true) {
|
||||
if reuse_port
|
||||
&& let Err(err) = socket.set_reuse_port(true) {
|
||||
if let Some(libc::ENOPROTOOPT) = err.raw_os_error() {
|
||||
// SO_REUSEPORT is supported after 3.9
|
||||
trace!("failed to set SO_REUSEPORT, error: {}", err);
|
||||
@@ -81,7 +81,6 @@ impl UdpRedirSocket {
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let sock_addr = SockAddr::from(addr);
|
||||
|
||||
|
||||
+2
-3
@@ -52,8 +52,8 @@ impl UdpRedirSocket {
|
||||
|
||||
socket.set_nonblocking(true)?;
|
||||
socket.set_reuse_address(true)?;
|
||||
if reuse_port {
|
||||
if let Err(err) = socket.set_reuse_port(true) {
|
||||
if reuse_port
|
||||
&& let Err(err) = socket.set_reuse_port(true) {
|
||||
if let Some(libc::ENOPROTOOPT) = err.raw_os_error() {
|
||||
trace!("failed to set SO_REUSEPORT, error: {}", err);
|
||||
} else {
|
||||
@@ -61,7 +61,6 @@ impl UdpRedirSocket {
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let sock_addr = SockAddr::from(addr);
|
||||
|
||||
|
||||
@@ -159,11 +159,10 @@ impl AsyncRead for TcpConnection {
|
||||
}
|
||||
|
||||
// Nothing could be read. Wait for notify.
|
||||
if let Some(old_waker) = control.recv_waker.replace(cx.waker().clone()) {
|
||||
if !old_waker.will_wake(cx.waker()) {
|
||||
if let Some(old_waker) = control.recv_waker.replace(cx.waker().clone())
|
||||
&& !old_waker.will_wake(cx.waker()) {
|
||||
old_waker.wake();
|
||||
}
|
||||
}
|
||||
|
||||
return Poll::Pending;
|
||||
}
|
||||
@@ -191,11 +190,10 @@ impl AsyncWrite for TcpConnection {
|
||||
// Write to buffer
|
||||
|
||||
if control.send_buffer.is_full() {
|
||||
if let Some(old_waker) = control.send_waker.replace(cx.waker().clone()) {
|
||||
if !old_waker.will_wake(cx.waker()) {
|
||||
if let Some(old_waker) = control.send_waker.replace(cx.waker().clone())
|
||||
&& !old_waker.will_wake(cx.waker()) {
|
||||
old_waker.wake();
|
||||
}
|
||||
}
|
||||
|
||||
return Poll::Pending;
|
||||
}
|
||||
@@ -224,11 +222,10 @@ impl AsyncWrite for TcpConnection {
|
||||
control.send_state = TcpSocketState::Close;
|
||||
}
|
||||
|
||||
if let Some(old_waker) = control.send_waker.replace(cx.waker().clone()) {
|
||||
if !old_waker.will_wake(cx.waker()) {
|
||||
if let Some(old_waker) = control.send_waker.replace(cx.waker().clone())
|
||||
&& !old_waker.will_wake(cx.waker()) {
|
||||
old_waker.wake();
|
||||
}
|
||||
}
|
||||
|
||||
self.manager_notify.notify();
|
||||
Poll::Pending
|
||||
@@ -421,11 +418,10 @@ impl TcpTun {
|
||||
wake_receiver = true;
|
||||
}
|
||||
|
||||
if wake_receiver && control.recv_waker.is_some() {
|
||||
if let Some(waker) = control.recv_waker.take() {
|
||||
if wake_receiver && control.recv_waker.is_some()
|
||||
&& let Some(waker) = control.recv_waker.take() {
|
||||
waker.wake();
|
||||
}
|
||||
}
|
||||
|
||||
// Check if writable
|
||||
let mut wake_sender = false;
|
||||
@@ -456,11 +452,10 @@ impl TcpTun {
|
||||
}
|
||||
}
|
||||
|
||||
if wake_sender && control.send_waker.is_some() {
|
||||
if let Some(waker) = control.send_waker.take() {
|
||||
if wake_sender && control.send_waker.is_some()
|
||||
&& let Some(waker) = control.send_waker.take() {
|
||||
waker.wake();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for socket_handle in sockets_to_remove {
|
||||
@@ -601,11 +596,10 @@ async fn handle_redir_client(
|
||||
// Get forward address from socket
|
||||
//
|
||||
// Try to convert IPv4 mapped IPv6 address for dual-stack mode.
|
||||
if let SocketAddr::V6(ref a) = daddr {
|
||||
if let Some(v4) = to_ipv4_mapped(a.ip()) {
|
||||
if let SocketAddr::V6(ref a) = daddr
|
||||
&& let Some(v4) = to_ipv4_mapped(a.ip()) {
|
||||
daddr = SocketAddr::new(IpAddr::from(v4), a.port());
|
||||
}
|
||||
}
|
||||
let target_addr = Address::from(daddr);
|
||||
establish_client_tcp_redir(context, balancer, s, peer_addr, &target_addr).await
|
||||
}
|
||||
|
||||
@@ -329,8 +329,8 @@ impl Manager {
|
||||
};
|
||||
|
||||
let pid_path = self.server_pid_path(port);
|
||||
if pid_path.exists() {
|
||||
if let Ok(mut pid_file) = File::open(&pid_path) {
|
||||
if pid_path.exists()
|
||||
&& let Ok(mut pid_file) = File::open(&pid_path) {
|
||||
let mut pid_content = String::new();
|
||||
if pid_file.read_to_string(&mut pid_content).is_ok() {
|
||||
let pid_content = pid_content.trim();
|
||||
@@ -346,7 +346,6 @@ impl Manager {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let server_config_path = self.server_config_path(port);
|
||||
|
||||
|
||||
@@ -593,15 +593,14 @@ impl UdpAssociationContext {
|
||||
data: &[u8],
|
||||
control: &Option<UdpSocketControlData>,
|
||||
) {
|
||||
if let Some(ref mut session) = self.client_session {
|
||||
if peer_addr != self.peer_addr {
|
||||
if let Some(ref mut session) = self.client_session
|
||||
&& peer_addr != self.peer_addr {
|
||||
debug!(
|
||||
"udp relay for {} changed to {}, session: {:?}",
|
||||
self.peer_addr, peer_addr, session.client_session_id
|
||||
);
|
||||
self.peer_addr = peer_addr;
|
||||
}
|
||||
}
|
||||
|
||||
trace!(
|
||||
"udp relay {} -> {} with {} bytes, control: {:?}",
|
||||
@@ -737,11 +736,10 @@ impl UdpAssociationContext {
|
||||
// It is an undefined behavior in shadowsocks' protocol about how to handle IPv4-mapped-IPv6.
|
||||
// But for some implementations, they may expect the target address to be IPv4, because
|
||||
// the peer address is IPv4 when calling `sendto`.
|
||||
if let Address::SocketAddress(SocketAddr::V6(ref v6)) = addr {
|
||||
if let Some(v4) = to_ipv4_mapped(v6.ip()) {
|
||||
if let Address::SocketAddress(SocketAddr::V6(ref v6)) = addr
|
||||
&& let Some(v4) = to_ipv4_mapped(v6.ip()) {
|
||||
addr = Address::SocketAddress(SocketAddr::new(v4.into(), v6.port()));
|
||||
}
|
||||
}
|
||||
|
||||
match self.client_session {
|
||||
None => {
|
||||
|
||||
@@ -9,7 +9,7 @@ documentation = "https://docs.rs/shadowsocks-core"
|
||||
keywords = ["shadowsocks", "proxy", "socks", "socks5", "firewall"]
|
||||
license = "MIT"
|
||||
edition = "2024"
|
||||
rust-version = "1.85"
|
||||
rust-version = "1.88"
|
||||
|
||||
[badges]
|
||||
maintenance = { status = "passively-maintained" }
|
||||
|
||||
@@ -695,21 +695,19 @@ impl ServerConfig {
|
||||
|
||||
/// Get server's TCP external address
|
||||
pub fn tcp_external_addr(&self) -> &ServerAddr {
|
||||
if let Some(plugin) = self.plugin() {
|
||||
if plugin.plugin_mode.enable_tcp() {
|
||||
if let Some(plugin) = self.plugin()
|
||||
&& plugin.plugin_mode.enable_tcp() {
|
||||
return self.plugin_addr.as_ref().unwrap_or(&self.addr);
|
||||
}
|
||||
}
|
||||
&self.addr
|
||||
}
|
||||
|
||||
/// Get server's UDP external address
|
||||
pub fn udp_external_addr(&self) -> &ServerAddr {
|
||||
if let Some(plugin) = self.plugin() {
|
||||
if plugin.plugin_mode.enable_udp() {
|
||||
if let Some(plugin) = self.plugin()
|
||||
&& plugin.plugin_mode.enable_udp() {
|
||||
return self.plugin_addr.as_ref().unwrap_or(&self.addr);
|
||||
}
|
||||
}
|
||||
&self.addr
|
||||
}
|
||||
|
||||
|
||||
@@ -167,15 +167,14 @@ static IP_STACK_CAPABILITIES: LazyLock<IpStackCapabilities> = LazyLock::new(|| {
|
||||
}
|
||||
|
||||
// Check IPv6 (::1)
|
||||
if let Ok(ipv6_socket) = Socket::new(Domain::IPV6, Type::STREAM, Some(Protocol::TCP)) {
|
||||
if ipv6_socket.set_only_v6(true).is_ok() {
|
||||
if let Ok(ipv6_socket) = Socket::new(Domain::IPV6, Type::STREAM, Some(Protocol::TCP))
|
||||
&& ipv6_socket.set_only_v6(true).is_ok() {
|
||||
let local_host = SockAddr::from(SocketAddr::new(Ipv6Addr::LOCALHOST.into(), 0));
|
||||
if ipv6_socket.bind(&local_host).is_ok() {
|
||||
caps.support_ipv6 = true;
|
||||
debug!("IpStackCapability support_ipv6=true");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check IPv4-mapped-IPv6 (127.0.0.1)
|
||||
if check_ipv4_mapped_ipv6_capability().is_ok() {
|
||||
|
||||
@@ -390,11 +390,10 @@ pub async fn bind_outbound_udp_socket(bind_addr: &SocketAddr, config: &ConnectOp
|
||||
UdpSocket::from_std(socket.into())?
|
||||
};
|
||||
|
||||
if !config.udp.allow_fragmentation {
|
||||
if let Err(err) = set_disable_ip_fragmentation(af, &socket) {
|
||||
if !config.udp.allow_fragmentation
|
||||
&& let Err(err) = set_disable_ip_fragmentation(af, &socket) {
|
||||
warn!("failed to disable IP fragmentation, error: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
// Set IP_BOUND_IF for BSD-like
|
||||
if let Some(ref iface) = config.bind_interface {
|
||||
|
||||
@@ -310,11 +310,10 @@ pub async fn bind_outbound_udp_socket(bind_addr: &SocketAddr, config: &ConnectOp
|
||||
UdpSocket::from_std(socket.into())?
|
||||
};
|
||||
|
||||
if !config.udp.allow_fragmentation {
|
||||
if let Err(err) = set_disable_ip_fragmentation(af, &socket) {
|
||||
if !config.udp.allow_fragmentation
|
||||
&& let Err(err) = set_disable_ip_fragmentation(af, &socket) {
|
||||
warn!("failed to disable IP fragmentation, error: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
// Any traffic except localhost should be protected
|
||||
// This is a workaround for VPNService
|
||||
|
||||
@@ -189,11 +189,10 @@ impl UdpSocket {
|
||||
/// Wrapper of `UdpSocket::poll_send`
|
||||
pub fn poll_send(&self, cx: &mut TaskContext<'_>, buf: &[u8]) -> Poll<io::Result<usize>> {
|
||||
// Check MTU
|
||||
if let Some(mtu) = self.mtu {
|
||||
if buf.len() > mtu {
|
||||
if let Some(mtu) = self.mtu
|
||||
&& buf.len() > mtu {
|
||||
return Err(make_mtu_error(buf.len(), mtu)).into();
|
||||
}
|
||||
}
|
||||
|
||||
self.socket.poll_send(cx, buf)
|
||||
}
|
||||
@@ -202,11 +201,10 @@ impl UdpSocket {
|
||||
#[inline]
|
||||
pub async fn send(&self, buf: &[u8]) -> io::Result<usize> {
|
||||
// Check MTU
|
||||
if let Some(mtu) = self.mtu {
|
||||
if buf.len() > mtu {
|
||||
if let Some(mtu) = self.mtu
|
||||
&& buf.len() > mtu {
|
||||
return Err(make_mtu_error(buf.len(), mtu));
|
||||
}
|
||||
}
|
||||
|
||||
self.socket.send(buf).await
|
||||
}
|
||||
@@ -214,11 +212,10 @@ impl UdpSocket {
|
||||
/// Wrapper of `UdpSocket::poll_send_to`
|
||||
pub fn poll_send_to(&self, cx: &mut TaskContext<'_>, buf: &[u8], target: SocketAddr) -> Poll<io::Result<usize>> {
|
||||
// Check MTU
|
||||
if let Some(mtu) = self.mtu {
|
||||
if buf.len() > mtu {
|
||||
if let Some(mtu) = self.mtu
|
||||
&& buf.len() > mtu {
|
||||
return Err(make_mtu_error(buf.len(), mtu)).into();
|
||||
}
|
||||
}
|
||||
|
||||
self.socket.poll_send_to(cx, buf, target)
|
||||
}
|
||||
@@ -227,11 +224,10 @@ impl UdpSocket {
|
||||
#[inline]
|
||||
pub async fn send_to<A: ToSocketAddrs>(&self, buf: &[u8], target: A) -> io::Result<usize> {
|
||||
// Check MTU
|
||||
if let Some(mtu) = self.mtu {
|
||||
if buf.len() > mtu {
|
||||
if let Some(mtu) = self.mtu
|
||||
&& buf.len() > mtu {
|
||||
return Err(make_mtu_error(buf.len(), mtu));
|
||||
}
|
||||
}
|
||||
|
||||
self.socket.send_to(buf, target).await
|
||||
}
|
||||
@@ -241,11 +237,10 @@ impl UdpSocket {
|
||||
pub fn poll_recv(&self, cx: &mut TaskContext<'_>, buf: &mut ReadBuf<'_>) -> Poll<io::Result<()>> {
|
||||
ready!(self.socket.poll_recv(cx, buf))?;
|
||||
|
||||
if let Some(mtu) = self.mtu {
|
||||
if buf.filled().len() > mtu {
|
||||
if let Some(mtu) = self.mtu
|
||||
&& buf.filled().len() > mtu {
|
||||
return Err(make_mtu_error(buf.filled().len(), mtu)).into();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(()).into()
|
||||
}
|
||||
@@ -255,11 +250,10 @@ impl UdpSocket {
|
||||
pub async fn recv(&self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
let n = self.socket.recv(buf).await?;
|
||||
|
||||
if let Some(mtu) = self.mtu {
|
||||
if n > mtu {
|
||||
if let Some(mtu) = self.mtu
|
||||
&& n > mtu {
|
||||
return Err(make_mtu_error(n, mtu));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(n)
|
||||
}
|
||||
@@ -269,11 +263,10 @@ impl UdpSocket {
|
||||
pub fn poll_recv_from(&self, cx: &mut TaskContext<'_>, buf: &mut ReadBuf<'_>) -> Poll<io::Result<SocketAddr>> {
|
||||
let addr = ready!(self.socket.poll_recv_from(cx, buf))?;
|
||||
|
||||
if let Some(mtu) = self.mtu {
|
||||
if buf.filled().len() > mtu {
|
||||
if let Some(mtu) = self.mtu
|
||||
&& buf.filled().len() > mtu {
|
||||
return Err(make_mtu_error(buf.filled().len(), mtu)).into();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(addr).into()
|
||||
}
|
||||
@@ -283,11 +276,10 @@ impl UdpSocket {
|
||||
pub async fn recv_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> {
|
||||
let (n, addr) = self.socket.recv_from(buf).await?;
|
||||
|
||||
if let Some(mtu) = self.mtu {
|
||||
if n > mtu {
|
||||
if let Some(mtu) = self.mtu
|
||||
&& n > mtu {
|
||||
return Err(make_mtu_error(n, mtu));
|
||||
}
|
||||
}
|
||||
|
||||
Ok((n, addr))
|
||||
}
|
||||
|
||||
@@ -159,11 +159,10 @@ where
|
||||
|
||||
// Wakeup writer task because we have already received the salt
|
||||
#[cfg(feature = "aead-cipher-2022")]
|
||||
if let ProxyServerStreamWriteState::PrepareHeader(waker) = this.writer_state {
|
||||
if let Some(waker) = waker.take() {
|
||||
if let ProxyServerStreamWriteState::PrepareHeader(waker) = this.writer_state
|
||||
&& let Some(waker) = waker.take() {
|
||||
waker.wake();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(()).into()
|
||||
}
|
||||
@@ -190,11 +189,10 @@ where
|
||||
*(this.writer_state) = ProxyServerStreamWriteState::Established;
|
||||
} else {
|
||||
// Reader didn't receive the salt from client yet.
|
||||
if let Some(waker) = waker.take() {
|
||||
if !waker.will_wake(cx.waker()) {
|
||||
if let Some(waker) = waker.take()
|
||||
&& !waker.will_wake(cx.waker()) {
|
||||
waker.wake();
|
||||
}
|
||||
}
|
||||
*waker = Some(cx.waker().clone());
|
||||
return Poll::Pending;
|
||||
}
|
||||
|
||||
@@ -755,17 +755,15 @@ pub fn create(matches: &ArgMatches) -> ShadowsocksResult<(Runtime, impl Future<O
|
||||
|
||||
#[cfg(feature = "local-redir")]
|
||||
{
|
||||
if RedirType::tcp_default() != RedirType::NotSupported {
|
||||
if let Some(tcp_redir) = matches.get_one::<String>("TCP_REDIR") {
|
||||
if RedirType::tcp_default() != RedirType::NotSupported
|
||||
&& let Some(tcp_redir) = matches.get_one::<String>("TCP_REDIR") {
|
||||
local_config.tcp_redir = tcp_redir.parse::<RedirType>().expect("tcp-redir");
|
||||
}
|
||||
}
|
||||
|
||||
if RedirType::udp_default() != RedirType::NotSupported {
|
||||
if let Some(udp_redir) = matches.get_one::<String>("UDP_REDIR") {
|
||||
if RedirType::udp_default() != RedirType::NotSupported
|
||||
&& let Some(udp_redir) = matches.get_one::<String>("UDP_REDIR") {
|
||||
local_config.udp_redir = udp_redir.parse::<RedirType>().expect("udp-redir");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "local-dns")]
|
||||
|
||||
@@ -404,17 +404,15 @@ pub fn create(matches: &ArgMatches) -> ShadowsocksResult<(Runtime, impl Future<O
|
||||
}
|
||||
|
||||
// Overrides
|
||||
if matches.get_flag("UDP_ONLY") {
|
||||
if let Some(ref mut m) = config.manager {
|
||||
if matches.get_flag("UDP_ONLY")
|
||||
&& let Some(ref mut m) = config.manager {
|
||||
m.mode = Mode::UdpOnly;
|
||||
}
|
||||
}
|
||||
|
||||
if matches.get_flag("TCP_AND_UDP") {
|
||||
if let Some(ref mut m) = config.manager {
|
||||
if matches.get_flag("TCP_AND_UDP")
|
||||
&& let Some(ref mut m) = config.manager {
|
||||
m.mode = Mode::TcpAndUdp;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(acl_file) = matches.get_one::<String>("ACL") {
|
||||
let acl = AccessControl::load_from_file(acl_file)
|
||||
|
||||
@@ -1587,13 +1587,13 @@ return view.extend({
|
||||
so = ss.option(CBIBubblesValue, '_value', _('Value'));
|
||||
so.modalonly = false;
|
||||
|
||||
so = ss.option(form.ListValue, 'chain_head_sub', _('Destination'));
|
||||
so = ss.option(form.ListValue, 'chain_head_sub', _('Destination provider'));
|
||||
so.load = L.bind(hm.loadProviderLabel, so, [['', _('-- Please choose --')]]);
|
||||
so.rmempty = false;
|
||||
so.depends('type', 'provider');
|
||||
so.modalonly = true;
|
||||
|
||||
so = ss.option(form.ListValue, 'chain_head', _('Destination'));
|
||||
so = ss.option(form.ListValue, 'chain_head', _('Destination proxy node'));
|
||||
so.load = L.bind(hm.loadNodeLabel, so, [['', _('-- Please choose --')]]);
|
||||
so.rmempty = false;
|
||||
so.validate = function(section_id, value) {
|
||||
@@ -1607,13 +1607,13 @@ return view.extend({
|
||||
so.depends('type', 'node');
|
||||
so.modalonly = true;
|
||||
|
||||
so = ss.option(form.ListValue, 'chain_tail_group', _('Transit'));
|
||||
so = ss.option(form.ListValue, 'chain_tail_group', _('Transit proxy group'));
|
||||
so.load = L.bind(hm.loadProxyGroupLabel, so, [['', _('-- Please choose --')]]);
|
||||
so.rmempty = false;
|
||||
so.depends({chain_tail: /.+/, '!reverse': true});
|
||||
so.modalonly = true;
|
||||
|
||||
so = ss.option(form.ListValue, 'chain_tail', _('Transit'));
|
||||
so = ss.option(form.ListValue, 'chain_tail', _('Transit proxy node'));
|
||||
so.load = L.bind(hm.loadNodeLabel, so, [['', _('-- Please choose --')]]);
|
||||
so.rmempty = false;
|
||||
so.validate = function(section_id, value) {
|
||||
|
||||
@@ -550,15 +550,18 @@ msgstr ""
|
||||
msgid "Default DNS server"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/fchomo/node.js:1590
|
||||
#: htdocs/luci-static/resources/view/fchomo/node.js:1596
|
||||
msgid "Destination"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/fchomo/node.js:631
|
||||
msgid "Destination addresses allowed to be forwarded via Wireguard."
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/fchomo/node.js:1590
|
||||
msgid "Destination provider"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/fchomo/node.js:1596
|
||||
msgid "Destination proxy node"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/fchomo/node.js:225
|
||||
msgid "Dial fields"
|
||||
msgstr ""
|
||||
@@ -2666,8 +2669,11 @@ msgid "Tproxy port"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/fchomo/node.js:1610
|
||||
msgid "Transit proxy group"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/fchomo/node.js:1616
|
||||
msgid "Transit"
|
||||
msgid "Transit proxy node"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/fchomo/node.js:347
|
||||
|
||||
@@ -561,15 +561,18 @@ msgstr "默认 DNS(由 WAN 下发)"
|
||||
msgid "Default DNS server"
|
||||
msgstr "默认 DNS 服务器"
|
||||
|
||||
#: htdocs/luci-static/resources/view/fchomo/node.js:1590
|
||||
#: htdocs/luci-static/resources/view/fchomo/node.js:1596
|
||||
msgid "Destination"
|
||||
msgstr "落地"
|
||||
|
||||
#: htdocs/luci-static/resources/view/fchomo/node.js:631
|
||||
msgid "Destination addresses allowed to be forwarded via Wireguard."
|
||||
msgstr "允许通过 WireGuard 转发的目的地址"
|
||||
|
||||
#: htdocs/luci-static/resources/view/fchomo/node.js:1590
|
||||
msgid "Destination provider"
|
||||
msgstr "落地供应商"
|
||||
|
||||
#: htdocs/luci-static/resources/view/fchomo/node.js:1596
|
||||
msgid "Destination proxy node"
|
||||
msgstr "落地代理节点"
|
||||
|
||||
#: htdocs/luci-static/resources/view/fchomo/node.js:225
|
||||
msgid "Dial fields"
|
||||
msgstr "拨号字段"
|
||||
@@ -2704,9 +2707,12 @@ msgid "Tproxy port"
|
||||
msgstr "Tproxy 端口"
|
||||
|
||||
#: htdocs/luci-static/resources/view/fchomo/node.js:1610
|
||||
msgid "Transit proxy group"
|
||||
msgstr "中转代理组"
|
||||
|
||||
#: htdocs/luci-static/resources/view/fchomo/node.js:1616
|
||||
msgid "Transit"
|
||||
msgstr "中转"
|
||||
msgid "Transit proxy node"
|
||||
msgstr "中转代理节点"
|
||||
|
||||
#: htdocs/luci-static/resources/view/fchomo/node.js:347
|
||||
#: htdocs/luci-static/resources/view/fchomo/node.js:962
|
||||
|
||||
@@ -561,15 +561,18 @@ msgstr "預設 DNS(由 WAN 下發)"
|
||||
msgid "Default DNS server"
|
||||
msgstr "預設 DNS 伺服器"
|
||||
|
||||
#: htdocs/luci-static/resources/view/fchomo/node.js:1590
|
||||
#: htdocs/luci-static/resources/view/fchomo/node.js:1596
|
||||
msgid "Destination"
|
||||
msgstr "落地"
|
||||
|
||||
#: htdocs/luci-static/resources/view/fchomo/node.js:631
|
||||
msgid "Destination addresses allowed to be forwarded via Wireguard."
|
||||
msgstr "允許通過 WireGuard 轉發的目的位址"
|
||||
|
||||
#: htdocs/luci-static/resources/view/fchomo/node.js:1590
|
||||
msgid "Destination provider"
|
||||
msgstr "落地供應商"
|
||||
|
||||
#: htdocs/luci-static/resources/view/fchomo/node.js:1596
|
||||
msgid "Destination proxy node"
|
||||
msgstr "落地代理節點"
|
||||
|
||||
#: htdocs/luci-static/resources/view/fchomo/node.js:225
|
||||
msgid "Dial fields"
|
||||
msgstr "撥號欄位"
|
||||
@@ -2704,9 +2707,12 @@ msgid "Tproxy port"
|
||||
msgstr "Tproxy 連接埠"
|
||||
|
||||
#: htdocs/luci-static/resources/view/fchomo/node.js:1610
|
||||
msgid "Transit proxy group"
|
||||
msgstr "中轉代理組"
|
||||
|
||||
#: htdocs/luci-static/resources/view/fchomo/node.js:1616
|
||||
msgid "Transit"
|
||||
msgstr "中轉"
|
||||
msgid "Transit proxy node"
|
||||
msgstr "中轉代理節點"
|
||||
|
||||
#: htdocs/luci-static/resources/view/fchomo/node.js:347
|
||||
#: htdocs/luci-static/resources/view/fchomo/node.js:962
|
||||
|
||||
@@ -511,6 +511,9 @@ o = s:option(DynamicList, _n("tcp_guise_http_path"), translate("HTTP Path"))
|
||||
o.placeholder = "/"
|
||||
o:depends({ [_n("tcp_guise")] = "http" })
|
||||
|
||||
o = s:option(Value, _n("tcp_guise_http_user_agent"), translate("User-Agent"))
|
||||
o:depends({ [_n("tcp_guise")] = "http" })
|
||||
|
||||
-- [[ mKCP部分 ]]--
|
||||
|
||||
o = s:option(ListValue, _n("mkcp_guise"), translate("Camouflage Type"), translate('<br />none: default, no masquerade, data sent is packets with no characteristics.<br />srtp: disguised as an SRTP packet, it will be recognized as video call data (such as FaceTime).<br />utp: packets disguised as uTP will be recognized as bittorrent downloaded data.<br />wechat-video: packets disguised as WeChat video calls.<br />dtls: disguised as DTLS 1.2 packet.<br />wireguard: disguised as a WireGuard packet. (not really WireGuard protocol)<br />dns: Disguising traffic as DNS requests.'))
|
||||
@@ -558,6 +561,9 @@ o = s:option(Value, _n("ws_path"), translate("WebSocket Path"))
|
||||
o.placeholder = "/"
|
||||
o:depends({ [_n("transport")] = "ws" })
|
||||
|
||||
o = s:option(Value, _n("ws_user_agent"), translate("User-Agent"))
|
||||
o:depends({ [_n("transport")] = "ws" })
|
||||
|
||||
o = s:option(Value, _n("ws_heartbeatPeriod"), translate("HeartbeatPeriod(second)"))
|
||||
o.datatype = "integer"
|
||||
o:depends({ [_n("transport")] = "ws" })
|
||||
@@ -598,6 +604,9 @@ o = s:option(Value, _n("httpupgrade_path"), translate("HttpUpgrade Path"))
|
||||
o.placeholder = "/"
|
||||
o:depends({ [_n("transport")] = "httpupgrade" })
|
||||
|
||||
o = s:option(Value, _n("httpupgrade_user_agent"), translate("User-Agent"))
|
||||
o:depends({ [_n("transport")] = "httpupgrade" })
|
||||
|
||||
-- [[ XHTTP部分 ]]--
|
||||
o = s:option(ListValue, _n("xhttp_mode"), "XHTTP " .. translate("Mode"))
|
||||
o:depends({ [_n("transport")] = "xhttp" })
|
||||
|
||||
@@ -609,6 +609,9 @@ o = s:option(DynamicList, _n("tcp_guise_http_path"), translate("HTTP Path"))
|
||||
o.placeholder = "/"
|
||||
o:depends({ [_n("tcp_guise")] = "http" })
|
||||
|
||||
o = s:option(Value, _n("tcp_guise_http_user_agent"), translate("User-Agent"))
|
||||
o:depends({ [_n("tcp_guise")] = "http" })
|
||||
|
||||
-- [[ HTTP部分 ]]--
|
||||
o = s:option(DynamicList, _n("http_host"), translate("HTTP Host"))
|
||||
o:depends({ [_n("transport")] = "http" })
|
||||
@@ -617,6 +620,9 @@ o = s:option(Value, _n("http_path"), translate("HTTP Path"))
|
||||
o.placeholder = "/"
|
||||
o:depends({ [_n("transport")] = "http" })
|
||||
|
||||
o = s:option(Value, _n("http_user_agent"), translate("User-Agent"))
|
||||
o:depends({ [_n("transport")] = "http" })
|
||||
|
||||
o = s:option(Flag, _n("http_h2_health_check"), translate("Health check"))
|
||||
o:depends({ [_n("tls")] = true, [_n("transport")] = "http" })
|
||||
|
||||
@@ -636,6 +642,9 @@ o = s:option(Value, _n("ws_path"), translate("WebSocket Path"))
|
||||
o.placeholder = "/"
|
||||
o:depends({ [_n("transport")] = "ws" })
|
||||
|
||||
o = s:option(Value, _n("ws_user_agent"), translate("User-Agent"))
|
||||
o:depends({ [_n("transport")] = "ws" })
|
||||
|
||||
o = s:option(Flag, _n("ws_enableEarlyData"), translate("Enable early data"))
|
||||
o:depends({ [_n("transport")] = "ws" })
|
||||
|
||||
@@ -654,6 +663,9 @@ o = s:option(Value, _n("httpupgrade_path"), translate("HTTPUpgrade Path"))
|
||||
o.placeholder = "/"
|
||||
o:depends({ [_n("transport")] = "httpupgrade" })
|
||||
|
||||
o = s:option(Value, _n("httpupgrade_user_agent"), translate("User-Agent"))
|
||||
o:depends({ [_n("transport")] = "httpupgrade" })
|
||||
|
||||
-- [[ gRPC部分 ]]--
|
||||
o = s:option(Value, _n("grpc_serviceName"), "ServiceName")
|
||||
o:depends({ [_n("transport")] = "grpc" })
|
||||
|
||||
@@ -206,6 +206,9 @@ function gen_outbound(flag, node, tag, proxy_table)
|
||||
local first = node.tcp_guise_http_path[1]
|
||||
return (first == "" or not first) and "/" or first
|
||||
end)() or "/",
|
||||
headers = {
|
||||
["User-Agent"] = node.tcp_guise_http_user_agent or nil
|
||||
},
|
||||
idle_timeout = (node.http_h2_health_check == "1") and node.http_h2_read_idle_timeout or nil,
|
||||
ping_timeout = (node.http_h2_health_check == "1") and node.http_h2_health_check_timeout or nil,
|
||||
}
|
||||
@@ -217,6 +220,9 @@ function gen_outbound(flag, node, tag, proxy_table)
|
||||
type = "http",
|
||||
host = node.http_host or {},
|
||||
path = node.http_path or "/",
|
||||
headers = {
|
||||
["User-Agent"] = node.http_user_agent or nil
|
||||
},
|
||||
idle_timeout = (node.http_h2_health_check == "1") and node.http_h2_read_idle_timeout or nil,
|
||||
ping_timeout = (node.http_h2_health_check == "1") and node.http_h2_health_check_timeout or nil,
|
||||
}
|
||||
@@ -227,7 +233,10 @@ function gen_outbound(flag, node, tag, proxy_table)
|
||||
v2ray_transport = {
|
||||
type = "ws",
|
||||
path = node.ws_path or "/",
|
||||
headers = (node.ws_host ~= nil) and { Host = node.ws_host } or nil,
|
||||
headers = {
|
||||
Host = node.ws_host or nil,
|
||||
["User-Agent"] = node.ws_user_agent or nil
|
||||
},
|
||||
max_early_data = tonumber(node.ws_maxEarlyData) or nil,
|
||||
early_data_header_name = (node.ws_earlyDataHeaderName) and node.ws_earlyDataHeaderName or nil --要与 Xray-core 兼容,请将其设置为 Sec-WebSocket-Protocol。它需要与服务器保持一致。
|
||||
}
|
||||
@@ -238,6 +247,9 @@ function gen_outbound(flag, node, tag, proxy_table)
|
||||
type = "httpupgrade",
|
||||
host = node.httpupgrade_host,
|
||||
path = node.httpupgrade_path or "/",
|
||||
headers = {
|
||||
["User-Agent"] = node.httpupgrade_user_agent or nil
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
@@ -179,7 +179,8 @@ function gen_outbound(flag, node, tag, proxy_table)
|
||||
return r
|
||||
end)() or {"/"},
|
||||
headers = {
|
||||
Host = node.tcp_guise_http_host or {}
|
||||
Host = node.tcp_guise_http_host or {},
|
||||
["User-Agent"] = node.tcp_guise_http_user_agent and {node.tcp_guise_http_user_agent} or nil
|
||||
}
|
||||
} or nil
|
||||
}
|
||||
@@ -200,7 +201,10 @@ function gen_outbound(flag, node, tag, proxy_table)
|
||||
} or nil,
|
||||
wsSettings = (node.transport == "ws") and {
|
||||
path = node.ws_path or "/",
|
||||
host = node.ws_host or nil,
|
||||
headers = {
|
||||
Host = node.ws_host or nil,
|
||||
["User-Agent"] = node.ws_user_agent or nil
|
||||
},
|
||||
maxEarlyData = tonumber(node.ws_maxEarlyData) or nil,
|
||||
earlyDataHeaderName = (node.ws_earlyDataHeaderName) and node.ws_earlyDataHeaderName or nil,
|
||||
heartbeatPeriod = tonumber(node.ws_heartbeatPeriod) or nil
|
||||
@@ -215,7 +219,10 @@ function gen_outbound(flag, node, tag, proxy_table)
|
||||
} or nil,
|
||||
httpupgradeSettings = (node.transport == "httpupgrade") and {
|
||||
path = node.httpupgrade_path or "/",
|
||||
host = node.httpupgrade_host
|
||||
host = node.httpupgrade_host,
|
||||
headers = {
|
||||
["User-Agent"] = node.httpupgrade_user_agent or nil
|
||||
}
|
||||
} or nil,
|
||||
xhttpSettings = (node.transport == "xhttp") and {
|
||||
mode = node.xhttp_mode or "auto",
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<%+cbi/valueheader%>
|
||||
<%
|
||||
local api = require "luci.passwall.api"
|
||||
local cbid = "cbid." .. self.config .. "." .. section .. "." .. self.option
|
||||
|
||||
-- 读取 MultiValue
|
||||
@@ -32,7 +31,7 @@ local group_order = {}
|
||||
for _, item in ipairs(values) do
|
||||
local g = item.group
|
||||
if not g or g == "" then
|
||||
g = api.i18n.translate("default")
|
||||
g = translate("default")
|
||||
end
|
||||
if not groups[g] then
|
||||
groups[g] = {}
|
||||
@@ -66,14 +65,14 @@ end
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="<%=cbid%>" style="display: inline-block;">
|
||||
<div id="<%=cbid%>" class="cbi-input-select" style="display: inline-block;">
|
||||
<!-- 搜索 -->
|
||||
<input type="text"
|
||||
id="<%=cbid%>.search"
|
||||
class="node-search-input cbi-input-text"
|
||||
placeholder="<%:Search nodes...%>"
|
||||
oninput="filterGroups_<%=self.option%>(this.value)"
|
||||
style="width:100%;padding:6px;margin-bottom:8px;border:1px solid #ccc;border-radius:4px;box-sizing:border-box;" />
|
||||
style="width:100%;padding:6px;margin-bottom:8px;border:1px solid #ccc;border-radius:4px;box-sizing:border-box;max-height:36px;" />
|
||||
<!-- 主容器 -->
|
||||
<div style="max-height:300px; overflow:auto; margin-bottom:8px; white-space:nowrap;">
|
||||
<ul class="cbi-multi" id="<%=cbid%>.node_list" style="padding:0 !important;margin:0 !important;width:100%;box-sizing:border-box;">
|
||||
@@ -84,9 +83,9 @@ end
|
||||
<div class="group-title"
|
||||
onclick="toggleGroup_<%=self.option%>('<%=gname%>')"
|
||||
style="cursor:pointer;padding:6px;background:#f0f0f0;border-radius:4px;margin-bottom:4px;display:flex;align-items:center;white-space:nowrap;">
|
||||
<span id="arrow-<%=self.option%>-<%=gname%>" class="lv-arrow-down-small" style="margin-right: 8px;"></span>
|
||||
<b><%=gname%></b>
|
||||
<span id="group-count-<%=self.option%>-<%=gname%>" style="margin-left:8px;color:blue;">
|
||||
<span id="arrow-<%=self.option%>-<%=gname%>" class="lv-arrow-down-small"></span>
|
||||
<b style="margin-left:8px;"><%=gname%></b>
|
||||
<span id="group-count-<%=self.option%>-<%=gname%>" style="margin-left:8px;color:#007bff;">
|
||||
(0/<%=#items%>)
|
||||
</span>
|
||||
</div>
|
||||
@@ -94,7 +93,7 @@ end
|
||||
<ul id="group-<%=self.option%>-<%=gname%>" style="margin:0 0 8px 16px; padding:0; list-style:none;">
|
||||
|
||||
<% for _, item in ipairs(items) do %>
|
||||
<li data-node-name="<%=pcdata(item.label):lower()%>" style="list-style:none; padding:0; margin:0; white-space:nowrap;">
|
||||
<li data-node-name="<%=pcdata(item.label):lower()%>" style="list-style:none;padding:0;margin:0;white-space:nowrap;" title="<%=pcdata(item.label)%>">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="cbi-input-checkbox"
|
||||
|
||||
@@ -21,22 +21,22 @@ define Download/geoip
|
||||
HASH:=6878dbacfb1fcb1ee022f63ed6934bcefc95a3c4ba10c88f1131fb88dbf7c337
|
||||
endef
|
||||
|
||||
GEOSITE_VER:=20251207105427
|
||||
GEOSITE_VER:=20251208040409
|
||||
GEOSITE_FILE:=dlc.dat.$(GEOSITE_VER)
|
||||
define Download/geosite
|
||||
URL:=https://github.com/v2fly/domain-list-community/releases/download/$(GEOSITE_VER)/
|
||||
URL_FILE:=dlc.dat
|
||||
FILE:=$(GEOSITE_FILE)
|
||||
HASH:=546daff727fb5068cd53e10c471d9439e4009d77286ad33b5fd43194cf874221
|
||||
HASH:=cdc1a448e95ba9109974c606d79d04318452f7c8181084bcc3b143c148349922
|
||||
endef
|
||||
|
||||
GEOSITE_IRAN_VER:=202512010051
|
||||
GEOSITE_IRAN_VER:=202512080042
|
||||
GEOSITE_IRAN_FILE:=iran.dat.$(GEOSITE_IRAN_VER)
|
||||
define Download/geosite-ir
|
||||
URL:=https://github.com/bootmortis/iran-hosted-domains/releases/download/$(GEOSITE_IRAN_VER)/
|
||||
URL_FILE:=iran.dat
|
||||
FILE:=$(GEOSITE_IRAN_FILE)
|
||||
HASH:=ae286762be4b810bd6b0b16d9960d52af587cdb3c1ec21f2fd0249cc0bf3eb40
|
||||
HASH:=1d7a8d3da982beefbb0057c0695341eb2a4bfd6bf01e3bfa3da9f8f42e07f9a4
|
||||
endef
|
||||
|
||||
define Package/v2ray-geodata/template
|
||||
|
||||
@@ -57,7 +57,10 @@ public class ShadowsocksFmt : BaseFmt
|
||||
{
|
||||
pluginArgs += "mode=websocket;";
|
||||
pluginArgs += $"host={item.RequestHost};";
|
||||
pluginArgs += $"path={item.Path};";
|
||||
// https://github.com/shadowsocks/v2ray-plugin/blob/e9af1cdd2549d528deb20a4ab8d61c5fbe51f306/args.go#L172
|
||||
// Equal signs and commas [and backslashes] must be escaped with a backslash.
|
||||
var path = item.Path.Replace("\\", "\\\\").Replace("=", "\\=").Replace(",", "\\,");
|
||||
pluginArgs += $"path={path};";
|
||||
}
|
||||
else if (item.Network == nameof(ETransport.quic))
|
||||
{
|
||||
@@ -75,8 +78,6 @@ public class ShadowsocksFmt : BaseFmt
|
||||
|
||||
var base64Content = cert.Replace(beginMarker, "").Replace(endMarker, "").Trim();
|
||||
|
||||
// https://github.com/shadowsocks/v2ray-plugin/blob/e9af1cdd2549d528deb20a4ab8d61c5fbe51f306/args.go#L172
|
||||
// Equal signs and commas [and backslashes] must be escaped with a backslash.
|
||||
base64Content = base64Content.Replace("=", "\\=");
|
||||
|
||||
pluginArgs += $"certRaw={base64Content};";
|
||||
@@ -85,6 +86,7 @@ public class ShadowsocksFmt : BaseFmt
|
||||
if (pluginArgs.Length > 0)
|
||||
{
|
||||
plugin = "v2ray-plugin";
|
||||
pluginArgs += "mux=0;";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,6 +224,7 @@ public class ShadowsocksFmt : BaseFmt
|
||||
var path = pluginParts.FirstOrDefault(t => t.StartsWith("path="));
|
||||
var hasTls = pluginParts.Any(t => t == "tls");
|
||||
var certRaw = pluginParts.FirstOrDefault(t => t.StartsWith("certRaw="));
|
||||
var mux = pluginParts.FirstOrDefault(t => t.StartsWith("mux="));
|
||||
|
||||
var modeValue = mode.Replace("mode=", "");
|
||||
if (modeValue == "websocket")
|
||||
@@ -234,7 +237,9 @@ public class ShadowsocksFmt : BaseFmt
|
||||
}
|
||||
if (!path.IsNullOrEmpty())
|
||||
{
|
||||
item.Path = path.Replace("path=", "");
|
||||
var pathValue = path.Replace("path=", "");
|
||||
pathValue = pathValue.Replace("\\=", "=").Replace("\\,", ",").Replace("\\\\", "\\");
|
||||
item.Path = pathValue;
|
||||
}
|
||||
}
|
||||
else if (modeValue == "quic")
|
||||
@@ -258,6 +263,16 @@ public class ShadowsocksFmt : BaseFmt
|
||||
item.Cert = certPem;
|
||||
}
|
||||
}
|
||||
|
||||
if (!mux.IsNullOrEmpty())
|
||||
{
|
||||
var muxValue = mux.Replace("mux=", "");
|
||||
var muxCount = muxValue.ToInt();
|
||||
if (muxCount > 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,10 @@ public partial class CoreConfigSingboxService
|
||||
{
|
||||
pluginArgs += "mode=websocket;";
|
||||
pluginArgs += $"host={node.RequestHost};";
|
||||
pluginArgs += $"path={node.Path};";
|
||||
// https://github.com/shadowsocks/v2ray-plugin/blob/e9af1cdd2549d528deb20a4ab8d61c5fbe51f306/args.go#L172
|
||||
// Equal signs and commas [and backslashes] must be escaped with a backslash.
|
||||
var path = node.Path.Replace("\\", "\\\\").Replace("=", "\\=").Replace(",", "\\,");
|
||||
pluginArgs += $"path={path};";
|
||||
}
|
||||
else if (node.Network == nameof(ETransport.quic))
|
||||
{
|
||||
@@ -64,8 +67,6 @@ public partial class CoreConfigSingboxService
|
||||
|
||||
var base64Content = cert.Replace(beginMarker, "").Replace(endMarker, "").Trim();
|
||||
|
||||
// https://github.com/shadowsocks/v2ray-plugin/blob/e9af1cdd2549d528deb20a4ab8d61c5fbe51f306/args.go#L172
|
||||
// Equal signs and commas [and backslashes] must be escaped with a backslash.
|
||||
base64Content = base64Content.Replace("=", "\\=");
|
||||
|
||||
pluginArgs += $"certRaw={base64Content};";
|
||||
@@ -74,6 +75,9 @@ public partial class CoreConfigSingboxService
|
||||
if (pluginArgs.Length > 0)
|
||||
{
|
||||
outbound.plugin = "v2ray-plugin";
|
||||
pluginArgs += "mux=0;";
|
||||
// pluginStr remove last ';'
|
||||
pluginArgs = pluginArgs[..^1];
|
||||
outbound.plugin_opts = pluginArgs;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package ctlcmd
|
||||
|
||||
import "syscall"
|
||||
|
||||
func getSysProcAttr() *syscall.SysProcAttr {
|
||||
return nil
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package ctlcmd
|
||||
|
||||
import "syscall"
|
||||
|
||||
func getSysProcAttr() *syscall.SysProcAttr {
|
||||
return &syscall.SysProcAttr{
|
||||
HideWindow: true,
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package ctlcmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/platform"
|
||||
)
|
||||
|
||||
func Run(args []string, input io.Reader) (buf.MultiBuffer, error) {
|
||||
xctl := platform.GetToolLocation("xctl")
|
||||
if _, err := os.Stat(xctl); err != nil {
|
||||
return nil, errors.New("xctl doesn't exist").Base(err)
|
||||
}
|
||||
|
||||
var errBuffer buf.MultiBufferContainer
|
||||
var outBuffer buf.MultiBufferContainer
|
||||
|
||||
cmd := exec.Command(xctl, args...)
|
||||
cmd.Stderr = &errBuffer
|
||||
cmd.Stdout = &outBuffer
|
||||
cmd.SysProcAttr = getSysProcAttr()
|
||||
if input != nil {
|
||||
cmd.Stdin = input
|
||||
}
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
return nil, errors.New("failed to start xctl").Base(err)
|
||||
}
|
||||
|
||||
if err := cmd.Wait(); err != nil {
|
||||
msg := "failed to execute xctl"
|
||||
if errBuffer.Len() > 0 {
|
||||
msg += ": \n" + strings.TrimSpace(errBuffer.MultiBuffer.String())
|
||||
}
|
||||
return nil, errors.New(msg).Base(err)
|
||||
}
|
||||
|
||||
// log stderr, info message
|
||||
if !errBuffer.IsEmpty() {
|
||||
errors.LogInfo(context.Background(), "<xctl message> \n", strings.TrimSpace(errBuffer.MultiBuffer.String()))
|
||||
}
|
||||
|
||||
return outBuffer.MultiBuffer, nil
|
||||
}
|
||||
@@ -8,19 +8,10 @@ import (
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func ExpandEnv(s string) string {
|
||||
return os.ExpandEnv(s)
|
||||
}
|
||||
|
||||
func LineSeparator() string {
|
||||
return "\n"
|
||||
}
|
||||
|
||||
func GetToolLocation(file string) string {
|
||||
toolPath := NewEnvFlag(ToolLocation).GetValue(getExecutableDir)
|
||||
return filepath.Join(toolPath, file)
|
||||
}
|
||||
|
||||
// GetAssetLocation searches for `file` in the env dir, the executable dir, and certain locations
|
||||
func GetAssetLocation(file string) string {
|
||||
assetPath := NewEnvFlag(AssetLocation).GetValue(getExecutableDir)
|
||||
|
||||
@@ -8,10 +8,8 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
PluginLocation = "xray.location.plugin"
|
||||
ConfigLocation = "xray.location.config"
|
||||
ConfdirLocation = "xray.location.confdir"
|
||||
ToolLocation = "xray.location.tool"
|
||||
AssetLocation = "xray.location.asset"
|
||||
CertLocation = "xray.location.cert"
|
||||
|
||||
@@ -79,17 +77,6 @@ func getExecutableDir() string {
|
||||
return filepath.Dir(exec)
|
||||
}
|
||||
|
||||
func getExecutableSubDir(dir string) func() string {
|
||||
return func() string {
|
||||
return filepath.Join(getExecutableDir(), dir)
|
||||
}
|
||||
}
|
||||
|
||||
func GetPluginDirectory() string {
|
||||
pluginDir := NewEnvFlag(PluginLocation).GetValue(getExecutableSubDir("plugins"))
|
||||
return pluginDir
|
||||
}
|
||||
|
||||
func GetConfigurationPath() string {
|
||||
configPath := NewEnvFlag(ConfigLocation).GetValue(getExecutableDir)
|
||||
return filepath.Join(configPath, "config.json")
|
||||
|
||||
@@ -5,20 +5,10 @@ package platform
|
||||
|
||||
import "path/filepath"
|
||||
|
||||
func ExpandEnv(s string) string {
|
||||
// TODO
|
||||
return s
|
||||
}
|
||||
|
||||
func LineSeparator() string {
|
||||
return "\r\n"
|
||||
}
|
||||
|
||||
func GetToolLocation(file string) string {
|
||||
toolPath := NewEnvFlag(ToolLocation).GetValue(getExecutableDir)
|
||||
return filepath.Join(toolPath, file+".exe")
|
||||
}
|
||||
|
||||
// GetAssetLocation searches for `file` in the env dir and the executable dir
|
||||
func GetAssetLocation(file string) string {
|
||||
assetPath := NewEnvFlag(AssetLocation).GetValue(getExecutableDir)
|
||||
|
||||
@@ -64,7 +64,7 @@ func GetMergedConfig(args cmdarg.Arg) (string, error) {
|
||||
var files []*ConfigSource
|
||||
supported := []string{"json", "yaml", "toml"}
|
||||
for _, file := range args {
|
||||
format := getFormat(file)
|
||||
format := GetFormat(file)
|
||||
if slices.Contains(supported, format) {
|
||||
files = append(files, &ConfigSource{
|
||||
Name: file,
|
||||
@@ -98,7 +98,7 @@ func getExtension(filename string) string {
|
||||
return filename[idx+1:]
|
||||
}
|
||||
|
||||
func getFormat(filename string) string {
|
||||
func GetFormat(filename string) string {
|
||||
return GetFormatByExtension(getExtension(filename))
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ func LoadConfig(formatName string, input interface{}) (*Config, error) {
|
||||
|
||||
if formatName == "auto" {
|
||||
if file != "stdin:" {
|
||||
f = getFormat(file)
|
||||
f = GetFormat(file)
|
||||
} else {
|
||||
f = "json"
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
var (
|
||||
Version_x byte = 25
|
||||
Version_y byte = 12
|
||||
Version_z byte = 2
|
||||
Version_z byte = 8
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -3,8 +3,6 @@ package conf
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
@@ -47,8 +45,6 @@ var (
|
||||
"dns": func() interface{} { return new(DNSOutboundConfig) },
|
||||
"wireguard": func() interface{} { return &WireGuardConfig{IsClient: true} },
|
||||
}, "protocol", "settings")
|
||||
|
||||
ctllog = log.New(os.Stderr, "xctl> ", 0)
|
||||
)
|
||||
|
||||
type SniffingConfig struct {
|
||||
|
||||
@@ -3,7 +3,6 @@ package convert
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/xtls/xray-core/common/cmdarg"
|
||||
creflect "github.com/xtls/xray-core/common/reflect"
|
||||
@@ -61,7 +60,7 @@ func executeConvertConfigsToProtobuf(cmd *base.Command, args []string) {
|
||||
}
|
||||
|
||||
if len(optFile) > 0 {
|
||||
switch core.GetFormatByExtension(getFileExtension(optFile)){
|
||||
switch core.GetFormat(optFile){
|
||||
case "protobuf", "":
|
||||
fmt.Println("Output ProtoBuf file is ", optFile)
|
||||
default:
|
||||
@@ -106,11 +105,3 @@ func executeConvertConfigsToProtobuf(cmd *base.Command, args []string) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getFileExtension(filename string) string {
|
||||
idx := strings.LastIndexByte(filename, '.')
|
||||
if idx == -1 {
|
||||
return ""
|
||||
}
|
||||
return filename[idx+1:]
|
||||
}
|
||||
|
||||
@@ -10,12 +10,10 @@ import (
|
||||
|
||||
type (
|
||||
configFileLoader func(string) (io.Reader, error)
|
||||
extconfigLoader func([]string, io.Reader) (io.Reader, error)
|
||||
)
|
||||
|
||||
var (
|
||||
EffectiveConfigFileLoader configFileLoader
|
||||
EffectiveExtConfigLoader extconfigLoader
|
||||
)
|
||||
|
||||
// LoadConfig reads from a path/url/stdin
|
||||
@@ -27,13 +25,3 @@ func LoadConfig(file string) (io.Reader, error) {
|
||||
}
|
||||
return EffectiveConfigFileLoader(file)
|
||||
}
|
||||
|
||||
// LoadExtConfig calls xctl to handle multiple config
|
||||
// the actual work also in external module
|
||||
func LoadExtConfig(files []string, reader io.Reader) (io.Reader, error) {
|
||||
if EffectiveExtConfigLoader == nil {
|
||||
return nil, errors.New("external config module not loaded").AtError()
|
||||
}
|
||||
|
||||
return EffectiveExtConfigLoader(files, reader)
|
||||
}
|
||||
|
||||
-11
@@ -13,7 +13,6 @@ import (
|
||||
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/platform/ctlcmd"
|
||||
"github.com/xtls/xray-core/main/confloader"
|
||||
)
|
||||
|
||||
@@ -129,16 +128,6 @@ func FetchUnixSocketHTTPContent(target string) ([]byte, error) {
|
||||
return content, nil
|
||||
}
|
||||
|
||||
func ExtConfigLoader(files []string, reader io.Reader) (io.Reader, error) {
|
||||
buf, err := ctlcmd.Run(append([]string{"convert"}, files...), reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return strings.NewReader(buf.String()), nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
confloader.EffectiveConfigFileLoader = ConfigLoader
|
||||
confloader.EffectiveExtConfigLoader = ExtConfigLoader
|
||||
}
|
||||
|
||||
+18
-18
@@ -248,7 +248,7 @@ func (w *VisionReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
|
||||
*withinPaddingBuffers = false
|
||||
*switchToDirectCopy = true
|
||||
} else {
|
||||
errors.LogInfo(w.ctx, "XtlsRead unknown command ", *currentCommand, buffer.Len())
|
||||
errors.LogDebug(w.ctx, "XtlsRead unknown command ", *currentCommand, buffer.Len())
|
||||
}
|
||||
}
|
||||
if w.trafficState.NumberOfPacketToFilter > 0 {
|
||||
@@ -269,9 +269,9 @@ func (w *VisionReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
|
||||
w.rawInput = nil
|
||||
|
||||
if inbound := session.InboundFromContext(w.ctx); inbound != nil && inbound.Conn != nil {
|
||||
if w.isUplink && inbound.CanSpliceCopy == 2 {
|
||||
inbound.CanSpliceCopy = 1
|
||||
}
|
||||
// if w.isUplink && inbound.CanSpliceCopy == 2 { // TODO: enable uplink splice
|
||||
// inbound.CanSpliceCopy = 1
|
||||
// }
|
||||
if !w.isUplink && w.ob != nil && w.ob.CanSpliceCopy == 2 { // ob need to be passed in due to context can have more than one ob
|
||||
w.ob.CanSpliceCopy = 1
|
||||
}
|
||||
@@ -334,9 +334,9 @@ func (w *VisionWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
||||
if !w.isUplink && inbound.CanSpliceCopy == 2 {
|
||||
inbound.CanSpliceCopy = 1
|
||||
}
|
||||
if w.isUplink && w.ob != nil && w.ob.CanSpliceCopy == 2 {
|
||||
w.ob.CanSpliceCopy = 1
|
||||
}
|
||||
// if w.isUplink && w.ob != nil && w.ob.CanSpliceCopy == 2 { // TODO: enable uplink splice
|
||||
// w.ob.CanSpliceCopy = 1
|
||||
// }
|
||||
}
|
||||
rawConn, _, writerCounter := UnwrapRawConn(w.conn)
|
||||
w.Writer = buf.NewWriter(rawConn)
|
||||
@@ -478,7 +478,7 @@ func ReshapeMultiBuffer(ctx context.Context, buffer buf.MultiBuffer) buf.MultiBu
|
||||
buffer[i] = nil
|
||||
}
|
||||
buffer = buffer[:0]
|
||||
errors.LogInfo(ctx, "ReshapeMultiBuffer ", toPrint)
|
||||
errors.LogDebug(ctx, "ReshapeMultiBuffer ", toPrint)
|
||||
return mb2
|
||||
}
|
||||
|
||||
@@ -517,7 +517,7 @@ func XtlsPadding(b *buf.Buffer, command byte, userUUID *[]byte, longPadding bool
|
||||
b = nil
|
||||
}
|
||||
newbuffer.Extend(paddingLen)
|
||||
errors.LogInfo(ctx, "XtlsPadding ", contentLen, " ", paddingLen, " ", command)
|
||||
errors.LogDebug(ctx, "XtlsPadding ", contentLen, " ", paddingLen, " ", command)
|
||||
return newbuffer
|
||||
}
|
||||
|
||||
@@ -564,7 +564,7 @@ func XtlsUnpadding(b *buf.Buffer, s *TrafficState, isUplink bool, ctx context.Co
|
||||
*remainingPadding = int32(data) << 8
|
||||
case 1:
|
||||
*remainingPadding = *remainingPadding | int32(data)
|
||||
errors.LogInfo(ctx, "Xtls Unpadding new block, content ", *remainingContent, " padding ", *remainingPadding, " command ", *currentCommand)
|
||||
errors.LogDebug(ctx, "Xtls Unpadding new block, content ", *remainingContent, " padding ", *remainingPadding, " command ", *currentCommand)
|
||||
}
|
||||
*remainingCommand--
|
||||
} else if *remainingContent > 0 {
|
||||
@@ -623,11 +623,11 @@ func XtlsFilterTls(buffer buf.MultiBuffer, trafficState *TrafficState, ctx conte
|
||||
cipherSuite := b.BytesRange(43+sessionIdLen+1, 43+sessionIdLen+3)
|
||||
trafficState.Cipher = uint16(cipherSuite[0])<<8 | uint16(cipherSuite[1])
|
||||
} else {
|
||||
errors.LogInfo(ctx, "XtlsFilterTls short server hello, tls 1.2 or older? ", b.Len(), " ", trafficState.RemainingServerHello)
|
||||
errors.LogDebug(ctx, "XtlsFilterTls short server hello, tls 1.2 or older? ", b.Len(), " ", trafficState.RemainingServerHello)
|
||||
}
|
||||
} else if bytes.Equal(TlsClientHandShakeStart, startsBytes[:2]) && startsBytes[5] == TlsHandshakeTypeClientHello {
|
||||
trafficState.IsTLS = true
|
||||
errors.LogInfo(ctx, "XtlsFilterTls found tls client hello! ", buffer.Len())
|
||||
errors.LogDebug(ctx, "XtlsFilterTls found tls client hello! ", buffer.Len())
|
||||
}
|
||||
}
|
||||
if trafficState.RemainingServerHello > 0 {
|
||||
@@ -643,18 +643,18 @@ func XtlsFilterTls(buffer buf.MultiBuffer, trafficState *TrafficState, ctx conte
|
||||
} else if v != "TLS_AES_128_CCM_8_SHA256" {
|
||||
trafficState.EnableXtls = true
|
||||
}
|
||||
errors.LogInfo(ctx, "XtlsFilterTls found tls 1.3! ", b.Len(), " ", v)
|
||||
errors.LogDebug(ctx, "XtlsFilterTls found tls 1.3! ", b.Len(), " ", v)
|
||||
trafficState.NumberOfPacketToFilter = 0
|
||||
return
|
||||
} else if trafficState.RemainingServerHello <= 0 {
|
||||
errors.LogInfo(ctx, "XtlsFilterTls found tls 1.2! ", b.Len())
|
||||
errors.LogDebug(ctx, "XtlsFilterTls found tls 1.2! ", b.Len())
|
||||
trafficState.NumberOfPacketToFilter = 0
|
||||
return
|
||||
}
|
||||
errors.LogInfo(ctx, "XtlsFilterTls inconclusive server hello ", b.Len(), " ", trafficState.RemainingServerHello)
|
||||
errors.LogDebug(ctx, "XtlsFilterTls inconclusive server hello ", b.Len(), " ", trafficState.RemainingServerHello)
|
||||
}
|
||||
if trafficState.NumberOfPacketToFilter <= 0 {
|
||||
errors.LogInfo(ctx, "XtlsFilterTls stop filtering", buffer.Len())
|
||||
errors.LogDebug(ctx, "XtlsFilterTls stop filtering", buffer.Len())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -736,7 +736,7 @@ func CopyRawConnIfExist(ctx context.Context, readerConn net.Conn, writerConn net
|
||||
}
|
||||
}
|
||||
if splice {
|
||||
errors.LogInfo(ctx, "CopyRawConn splice")
|
||||
errors.LogDebug(ctx, "CopyRawConn splice")
|
||||
statWriter, _ := writer.(*dispatcher.SizeStatWriter)
|
||||
//runtime.Gosched() // necessary
|
||||
time.Sleep(time.Millisecond) // without this, there will be a rare ssl error for freedom splice
|
||||
@@ -779,7 +779,7 @@ func CopyRawConnIfExist(ctx context.Context, readerConn net.Conn, writerConn net
|
||||
}
|
||||
|
||||
func readV(ctx context.Context, reader buf.Reader, writer buf.Writer, timer signal.ActivityUpdater, readCounter stats.Counter) error {
|
||||
errors.LogInfo(ctx, "CopyRawConn (maybe) readv")
|
||||
errors.LogDebug(ctx, "CopyRawConn (maybe) readv")
|
||||
if err := buf.Copy(reader, writer, buf.UpdateActivity(timer), buf.AddToStatCounter(readCounter)); err != nil {
|
||||
return errors.New("failed to process response").Base(err)
|
||||
}
|
||||
|
||||
Vendored
+9
-9
@@ -196,7 +196,7 @@ jobs:
|
||||
UPDATE_TO: yt-dlp/yt-dlp@2025.09.05
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0 # Needed for changelog
|
||||
|
||||
@@ -257,7 +257,7 @@ jobs:
|
||||
SKIP_ONEFILE_BUILD: ${{ (!matrix.onefile && '1') || '' }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Cache requirements
|
||||
if: matrix.cache_requirements
|
||||
@@ -320,7 +320,7 @@ jobs:
|
||||
UPDATE_TO: yt-dlp/yt-dlp@2025.09.05
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
# NB: Building universal2 does not work with python from actions/setup-python
|
||||
|
||||
- name: Cache requirements
|
||||
@@ -343,14 +343,14 @@ jobs:
|
||||
brew uninstall --ignore-dependencies python3
|
||||
python3 -m venv ~/yt-dlp-build-venv
|
||||
source ~/yt-dlp-build-venv/bin/activate
|
||||
python3 devscripts/install_deps.py --only-optional-groups --include-group build
|
||||
python3 devscripts/install_deps.py --print --include-group pyinstaller > requirements.txt
|
||||
python3 devscripts/install_deps.py --omit-default --include-extra build
|
||||
python3 devscripts/install_deps.py --print --include-extra pyinstaller > requirements.txt
|
||||
# We need to ignore wheels otherwise we break universal2 builds
|
||||
python3 -m pip install -U --no-binary :all: -r requirements.txt
|
||||
# We need to fuse our own universal2 wheels for curl_cffi
|
||||
python3 -m pip install -U 'delocate==0.11.0'
|
||||
mkdir curl_cffi_whls curl_cffi_universal2
|
||||
python3 devscripts/install_deps.py --print --only-optional-groups --include-group curl-cffi > requirements.txt
|
||||
python3 devscripts/install_deps.py --print --omit-default --include-extra curl-cffi > requirements.txt
|
||||
for platform in "macosx_11_0_arm64" "macosx_11_0_x86_64"; do
|
||||
python3 -m pip download \
|
||||
--only-binary=:all: \
|
||||
@@ -450,7 +450,7 @@ jobs:
|
||||
PYI_WHEEL: pyinstaller-${{ matrix.pyi_version }}-py3-none-${{ matrix.platform_tag }}.whl
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: ${{ matrix.python_version }}
|
||||
@@ -484,11 +484,11 @@ jobs:
|
||||
mkdir /pyi-wheels
|
||||
python -m pip download -d /pyi-wheels --no-deps --require-hashes "pyinstaller@${Env:PYI_URL}#sha256=${Env:PYI_HASH}"
|
||||
python -m pip install --force-reinstall -U "/pyi-wheels/${Env:PYI_WHEEL}"
|
||||
python devscripts/install_deps.py --only-optional-groups --include-group build
|
||||
python devscripts/install_deps.py --omit-default --include-extra build
|
||||
if ("${Env:ARCH}" -eq "x86") {
|
||||
python devscripts/install_deps.py
|
||||
} else {
|
||||
python devscripts/install_deps.py --include-group curl-cffi
|
||||
python devscripts/install_deps.py --include-extra curl-cffi
|
||||
}
|
||||
|
||||
- name: Prepare
|
||||
|
||||
+2
-2
@@ -35,7 +35,7 @@ jobs:
|
||||
env:
|
||||
QJS_VERSION: '2025-04-26' # Earliest version with rope strings
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
@@ -67,7 +67,7 @@ jobs:
|
||||
unzip quickjs.zip
|
||||
- name: Install test requirements
|
||||
run: |
|
||||
python ./devscripts/install_deps.py --print --only-optional-groups --include-group test > requirements.txt
|
||||
python ./devscripts/install_deps.py --print --omit-default --include-extra test > requirements.txt
|
||||
python ./devscripts/install_deps.py --print -c certifi -c requests -c urllib3 -c yt-dlp-ejs >> requirements.txt
|
||||
python -m pip install -U -r requirements.txt
|
||||
- name: Run tests
|
||||
|
||||
Vendored
+7
-33
@@ -2,7 +2,7 @@ name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ 'master', 'gh-pages', 'release' ]
|
||||
branches: [ 'master' ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ 'master' ]
|
||||
@@ -11,7 +11,7 @@ on:
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
name: Analyze (${{ matrix.language }})
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
@@ -21,45 +21,19 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'python' ]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
||||
# Use only 'java' to analyze code written in Java, Kotlin or both
|
||||
# Use only 'javascript' to analyze code written in JavaScript, TypeScript or both
|
||||
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
|
||||
language: [ 'actions', 'javascript-typescript', 'python' ]
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
uses: github/codeql-action/init@v4
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
|
||||
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||
# queries: security-extended,security-and-quality
|
||||
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
|
||||
# If the Autobuild fails above, remove it and uncomment the following three lines.
|
||||
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
|
||||
|
||||
# - run: |
|
||||
# echo "Run, Build Application using script"
|
||||
# ./location_of_script_within_repo/buildscript.sh
|
||||
build-mode: none
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
uses: github/codeql-action/analyze@v4
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
|
||||
Vendored
+2
-2
@@ -55,7 +55,7 @@ jobs:
|
||||
- os: windows-latest
|
||||
python-version: pypy-3.11
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
@@ -63,7 +63,7 @@ jobs:
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install test requirements
|
||||
run: python ./devscripts/install_deps.py --include-group test --include-group curl-cffi
|
||||
run: python ./devscripts/install_deps.py --include-extra test --include-extra curl-cffi
|
||||
- name: Run tests
|
||||
timeout-minutes: 15
|
||||
continue-on-error: False
|
||||
|
||||
+4
-4
@@ -9,13 +9,13 @@ jobs:
|
||||
if: "contains(github.event.head_commit.message, 'ci run dl')"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.10'
|
||||
- name: Install test requirements
|
||||
run: python ./devscripts/install_deps.py --include-group dev
|
||||
run: python ./devscripts/install_deps.py --include-extra dev
|
||||
- name: Run tests
|
||||
continue-on-error: true
|
||||
run: python ./devscripts/run_tests.py download
|
||||
@@ -36,13 +36,13 @@ jobs:
|
||||
- os: windows-latest
|
||||
python-version: pypy-3.11
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install test requirements
|
||||
run: python ./devscripts/install_deps.py --include-group dev
|
||||
run: python ./devscripts/install_deps.py --include-extra dev
|
||||
- name: Run tests
|
||||
continue-on-error: true
|
||||
run: python ./devscripts/run_tests.py download
|
||||
|
||||
+4
-4
@@ -9,13 +9,13 @@ jobs:
|
||||
if: "!contains(github.event.head_commit.message, 'ci skip all')"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Set up Python 3.10
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.10'
|
||||
- name: Install test requirements
|
||||
run: python ./devscripts/install_deps.py --only-optional-groups --include-group test
|
||||
run: python ./devscripts/install_deps.py --omit-default --include-extra test
|
||||
- name: Run tests
|
||||
timeout-minutes: 15
|
||||
run: |
|
||||
@@ -26,12 +26,12 @@ jobs:
|
||||
if: "!contains(github.event.head_commit.message, 'ci skip all')"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.10'
|
||||
- name: Install dev dependencies
|
||||
run: python ./devscripts/install_deps.py --only-optional-groups --include-group static-analysis
|
||||
run: python ./devscripts/install_deps.py --omit-default --include-extra static-analysis
|
||||
- name: Make lazy extractors
|
||||
run: python ./devscripts/make_lazy_extractors.py
|
||||
- name: Run ruff
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ jobs:
|
||||
outputs:
|
||||
commit: ${{ steps.check_for_new_commits.outputs.commit }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Check for new commits
|
||||
|
||||
Vendored
+4
-4
@@ -75,7 +75,7 @@ jobs:
|
||||
head_sha: ${{ steps.get_target.outputs.head_sha }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -170,7 +170,7 @@ jobs:
|
||||
id-token: write # mandatory for trusted publishing
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-python@v6
|
||||
@@ -180,7 +180,7 @@ jobs:
|
||||
- name: Install Requirements
|
||||
run: |
|
||||
sudo apt -y install pandoc man
|
||||
python devscripts/install_deps.py --only-optional-groups --include-group build
|
||||
python devscripts/install_deps.py --omit-default --include-extra build
|
||||
|
||||
- name: Prepare
|
||||
env:
|
||||
@@ -233,7 +233,7 @@ jobs:
|
||||
VERSION: ${{ needs.prepare.outputs.version }}
|
||||
HEAD_SHA: ${{ needs.prepare.outputs.head_sha }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/download-artifact@v5
|
||||
|
||||
+4
-4
@@ -17,8 +17,8 @@ on:
|
||||
permissions:
|
||||
contents: read
|
||||
env:
|
||||
ACTIONLINT_VERSION: "1.7.8"
|
||||
ACTIONLINT_SHA256SUM: be92c2652ab7b6d08425428797ceabeb16e31a781c07bc388456b4e592f3e36a
|
||||
ACTIONLINT_VERSION: "1.7.9"
|
||||
ACTIONLINT_SHA256SUM: 233b280d05e100837f4af1433c7b40a5dcb306e3aa68fb4f17f8a7f45a7df7b4
|
||||
ACTIONLINT_REPO: https://github.com/rhysd/actionlint
|
||||
|
||||
jobs:
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
name: Check workflows
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.10" # Keep this in sync with release.yml's prepare job
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
env:
|
||||
ACTIONLINT_TARBALL: ${{ format('actionlint_{0}_linux_amd64.tar.gz', env.ACTIONLINT_VERSION) }}
|
||||
run: |
|
||||
python -m devscripts.install_deps --only-optional-groups --include-group test
|
||||
python -m devscripts.install_deps --omit-default --include-extra test
|
||||
sudo apt -y install shellcheck
|
||||
python -m pip install -U pyflakes
|
||||
curl -LO "${ACTIONLINT_REPO}/releases/download/v${ACTIONLINT_VERSION}/${ACTIONLINT_TARBALL}"
|
||||
|
||||
@@ -177,7 +177,7 @@ While it is strongly recommended to use `hatch` for yt-dlp development, if you a
|
||||
|
||||
```shell
|
||||
# To only install development dependencies:
|
||||
$ python -m devscripts.install_deps --include-group dev
|
||||
$ python -m devscripts.install_deps --include-extra dev
|
||||
|
||||
# Or, for an editable install plus dev dependencies:
|
||||
$ python -m pip install -e ".[default,dev]"
|
||||
@@ -763,7 +763,7 @@ Wrap all extracted numeric data into safe functions from [`yt_dlp/utils/`](yt_dl
|
||||
|
||||
Use `url_or_none` for safe URL processing.
|
||||
|
||||
Use `traverse_obj` and `try_call` (superseeds `dict_get` and `try_get`) for safe metadata extraction from parsed JSON.
|
||||
Use `traverse_obj` and `try_call` (supersedes `dict_get` and `try_get`) for safe metadata extraction from parsed JSON.
|
||||
|
||||
Use `unified_strdate` for uniform `upload_date` or any `YYYYMMDD` meta field extraction, `unified_timestamp` for uniform `timestamp` extraction, `parse_filesize` for `filesize` extraction, `parse_count` for count meta fields extraction, `parse_resolution`, `parse_duration` for `duration` extraction, `parse_age_limit` for `age_limit` extraction.
|
||||
|
||||
|
||||
+10
-1
@@ -828,9 +828,18 @@ krystophny
|
||||
matyb08
|
||||
pha1n0q
|
||||
PierceLBrooks
|
||||
sepro
|
||||
TheQWERTYCodr
|
||||
thomasmllt
|
||||
w4grfw
|
||||
WeidiDeng
|
||||
Zer0spectrum
|
||||
0xvd
|
||||
1bnBattuta
|
||||
beliote
|
||||
darkstar
|
||||
Haytam001
|
||||
mrFlamel
|
||||
oxyzenQ
|
||||
putridambassador121
|
||||
RezSat
|
||||
WhatAmISupposedToPutHere
|
||||
|
||||
+59
-1
@@ -4,6 +4,64 @@
|
||||
# To create a release, dispatch the https://github.com/yt-dlp/yt-dlp/actions/workflows/release.yml workflow on master
|
||||
-->
|
||||
|
||||
### 2025.12.08
|
||||
|
||||
#### Core changes
|
||||
- [Respect `PATHEXT` when locating JS runtime on Windows](https://github.com/yt-dlp/yt-dlp/commit/e564b4a8080cff48fa0c28f20272c05085ee6130) ([#15117](https://github.com/yt-dlp/yt-dlp/issues/15117)) by [Grub4K](https://github.com/Grub4K)
|
||||
- **cookies**: [Fix `--cookies-from-browser` for new installs of Firefox 147+](https://github.com/yt-dlp/yt-dlp/commit/fa16dc5241ac1552074feee48e1c2605dc36d352) ([#15215](https://github.com/yt-dlp/yt-dlp/issues/15215)) by [bashonly](https://github.com/bashonly), [mbway](https://github.com/mbway)
|
||||
|
||||
#### Extractor changes
|
||||
- **agalega**: [Add extractor](https://github.com/yt-dlp/yt-dlp/commit/3cb5e4db54d44fe82d4eee94ae2f37cbce2e7dfc) ([#15105](https://github.com/yt-dlp/yt-dlp/issues/15105)) by [putridambassador121](https://github.com/putridambassador121)
|
||||
- **alibaba**: [Add extractor](https://github.com/yt-dlp/yt-dlp/commit/c70b57c03e0c25767a5166620798297a2a4878fb) ([#15253](https://github.com/yt-dlp/yt-dlp/issues/15253)) by [seproDev](https://github.com/seproDev)
|
||||
- **bitmovin**: [Add extractor](https://github.com/yt-dlp/yt-dlp/commit/45a3b42bb917e99b0b5c155c272ebf4a82a5bf66) ([#15064](https://github.com/yt-dlp/yt-dlp/issues/15064)) by [seproDev](https://github.com/seproDev)
|
||||
- **digiteka**: [Rework extractor](https://github.com/yt-dlp/yt-dlp/commit/6842620d56e4c4e6affb90c2f8dff8a36dee852c) ([#14903](https://github.com/yt-dlp/yt-dlp/issues/14903)) by [beliote](https://github.com/beliote)
|
||||
- **fc2**: live: [Raise appropriate error when stream is offline](https://github.com/yt-dlp/yt-dlp/commit/4433b3a217c9f430dc057643bfd7b6769eff4a45) ([#15180](https://github.com/yt-dlp/yt-dlp/issues/15180)) by [Zer0spectrum](https://github.com/Zer0spectrum)
|
||||
- **floatplane**: [Add subtitle support](https://github.com/yt-dlp/yt-dlp/commit/b333ef1b3f961e292a8bf7052c54b54c81587a17) ([#15069](https://github.com/yt-dlp/yt-dlp/issues/15069)) by [seproDev](https://github.com/seproDev)
|
||||
- **jtbc**: [Fix extractor](https://github.com/yt-dlp/yt-dlp/commit/947e7883406e5ea43687d6e4ff721cc0162c9664) ([#15047](https://github.com/yt-dlp/yt-dlp/issues/15047)) by [seproDev](https://github.com/seproDev)
|
||||
- **loom**: [Fix extractor](https://github.com/yt-dlp/yt-dlp/commit/36b29bb3532e008a2aaf3d36d1c6fc3944137930) ([#15236](https://github.com/yt-dlp/yt-dlp/issues/15236)) by [bashonly](https://github.com/bashonly)
|
||||
- **mave**: channel: [Add extractor](https://github.com/yt-dlp/yt-dlp/commit/5f66ac71f6637f768cd251509b0a932d0ce56427) ([#14915](https://github.com/yt-dlp/yt-dlp/issues/14915)) by [anlar](https://github.com/anlar)
|
||||
- **medaltv**: [Rework extractor](https://github.com/yt-dlp/yt-dlp/commit/a4c72acc462668a938827370bd77084a1cd4733b) ([#15103](https://github.com/yt-dlp/yt-dlp/issues/15103)) by [seproDev](https://github.com/seproDev)
|
||||
- **netapp**: [Add extractors](https://github.com/yt-dlp/yt-dlp/commit/20f83f208eae863250b35e2761adad88e91d85a1) ([#15122](https://github.com/yt-dlp/yt-dlp/issues/15122)) by [darkstar](https://github.com/darkstar)
|
||||
- **nhk**: [Fix extractors](https://github.com/yt-dlp/yt-dlp/commit/12d411722a3d7a0382d1d230a904ecd4e20298b6) ([#14528](https://github.com/yt-dlp/yt-dlp/issues/14528)) by [garret1317](https://github.com/garret1317)
|
||||
- **nowcanal**: [Add extractor](https://github.com/yt-dlp/yt-dlp/commit/4e680db1505dafb93313b1d42ffcd3f230fcc92a) ([#14584](https://github.com/yt-dlp/yt-dlp/issues/14584)) by [pferreir](https://github.com/pferreir)
|
||||
- **patreon**: campaign: [Fix extractor](https://github.com/yt-dlp/yt-dlp/commit/023e4db9afe0630c608621846856a1ca876d8bab) ([#15108](https://github.com/yt-dlp/yt-dlp/issues/15108)) by [thomasmllt](https://github.com/thomasmllt)
|
||||
- **rinsefm**: [Fix extractors](https://github.com/yt-dlp/yt-dlp/commit/d6aa8c235d2e7d9374f79ec73af23a3859c76bea) ([#15020](https://github.com/yt-dlp/yt-dlp/issues/15020)) by [1bnBattuta](https://github.com/1bnBattuta), [seproDev](https://github.com/seproDev)
|
||||
- **s4c**: [Fix geo-restricted content](https://github.com/yt-dlp/yt-dlp/commit/26c2545b87e2b22f134d1f567ed4d4b0b91c3253) ([#15196](https://github.com/yt-dlp/yt-dlp/issues/15196)) by [seproDev](https://github.com/seproDev)
|
||||
- **soundcloudplaylist**: [Support new API URLs](https://github.com/yt-dlp/yt-dlp/commit/1dd84b9d1c33e50de49866b0d93c2596897ce506) ([#15071](https://github.com/yt-dlp/yt-dlp/issues/15071)) by [seproDev](https://github.com/seproDev)
|
||||
- **sporteurope**: [Support new domain](https://github.com/yt-dlp/yt-dlp/commit/025191fea655ac879ca6dc68df358c26456a6e46) ([#15251](https://github.com/yt-dlp/yt-dlp/issues/15251)) by [bashonly](https://github.com/bashonly)
|
||||
- **sproutvideo**: [Fix extractor](https://github.com/yt-dlp/yt-dlp/commit/2c9f0c3456057aff0631d9ea6d3eda70ffd8aabe) ([#15113](https://github.com/yt-dlp/yt-dlp/issues/15113)) by [bashonly](https://github.com/bashonly)
|
||||
- **thechosen**: [Add extractors](https://github.com/yt-dlp/yt-dlp/commit/854fded114f3b7b33693c2d3418575d04014aa4b) ([#14183](https://github.com/yt-dlp/yt-dlp/issues/14183)) by [mrFlamel](https://github.com/mrFlamel)
|
||||
- **thisoldhouse**: [Fix login support](https://github.com/yt-dlp/yt-dlp/commit/9daba4f442139ee2537746398afc5ac30b51c28c) ([#15097](https://github.com/yt-dlp/yt-dlp/issues/15097)) by [bashonly](https://github.com/bashonly)
|
||||
- **tubitv**: series: [Fix extractor](https://github.com/yt-dlp/yt-dlp/commit/2a777ecbd598de19a4c691ba1f790ccbec9cdbc4) ([#15018](https://github.com/yt-dlp/yt-dlp/issues/15018)) by [Zer0spectrum](https://github.com/Zer0spectrum)
|
||||
- **urplay**: [Fix extractor](https://github.com/yt-dlp/yt-dlp/commit/c2e7e9cdb2261adde01048d161914b156a3bad51) ([#15120](https://github.com/yt-dlp/yt-dlp/issues/15120)) by [seproDev](https://github.com/seproDev)
|
||||
- **web.archive**: youtube: [Fix extractor](https://github.com/yt-dlp/yt-dlp/commit/7ec6b9bc40ee8a21b11cce83a09a07a37014062e) ([#15234](https://github.com/yt-dlp/yt-dlp/issues/15234)) by [seproDev](https://github.com/seproDev)
|
||||
- **wistiachannel**: [Fix extractor](https://github.com/yt-dlp/yt-dlp/commit/0c696239ef418776ac6ba20284bd2f3976a011b4) ([#14218](https://github.com/yt-dlp/yt-dlp/issues/14218)) by [Sojiroh](https://github.com/Sojiroh)
|
||||
- **xhamster**: [Fix extractor](https://github.com/yt-dlp/yt-dlp/commit/29e257037862f3b2ad65e6e8d2972f9ed89389e3) ([#15252](https://github.com/yt-dlp/yt-dlp/issues/15252)) by [0xvd](https://github.com/0xvd)
|
||||
- **yfanefa**: [Add extractor](https://github.com/yt-dlp/yt-dlp/commit/af285016d2b14c4445109283e7c590b31542de88) ([#15032](https://github.com/yt-dlp/yt-dlp/issues/15032)) by [Haytam001](https://github.com/Haytam001)
|
||||
- **youtube**
|
||||
- [Add `use_ad_playback_context` extractor-arg](https://github.com/yt-dlp/yt-dlp/commit/f7acf3c1f42cc474927ecc452205d7877af36731) ([#15220](https://github.com/yt-dlp/yt-dlp/issues/15220)) by [WhatAmISupposedToPutHere](https://github.com/WhatAmISupposedToPutHere)
|
||||
- [Allow `ejs` patch version to differ](https://github.com/yt-dlp/yt-dlp/commit/7bd79d92965fe9f84d7e1720eb6bb10fa9a10c77) ([#15263](https://github.com/yt-dlp/yt-dlp/issues/15263)) by [Grub4K](https://github.com/Grub4K)
|
||||
- [Detect "super resolution" AI-upscaled formats](https://github.com/yt-dlp/yt-dlp/commit/4cb5e191efeebc3679f89c3c8ac819bcd511bb1f) ([#15050](https://github.com/yt-dlp/yt-dlp/issues/15050)) by [bashonly](https://github.com/bashonly)
|
||||
- [Determine wait time from player response](https://github.com/yt-dlp/yt-dlp/commit/715af0c636b2b33fb3df1eb2ee37eac8262d43ac) ([#14646](https://github.com/yt-dlp/yt-dlp/issues/14646)) by [bashonly](https://github.com/bashonly), [WhatAmISupposedToPutHere](https://github.com/WhatAmISupposedToPutHere)
|
||||
- [Extract all automatic caption languages](https://github.com/yt-dlp/yt-dlp/commit/419776ecf57269efb13095386a19ddc75c1f11b2) ([#15156](https://github.com/yt-dlp/yt-dlp/issues/15156)) by [bashonly](https://github.com/bashonly)
|
||||
- [Improve message when no JS runtime is found](https://github.com/yt-dlp/yt-dlp/commit/1d43fa5af883f96af902a29544fc766f5e97fce6) ([#15266](https://github.com/yt-dlp/yt-dlp/issues/15266)) by [bashonly](https://github.com/bashonly)
|
||||
- [Update ejs to 0.3.2](https://github.com/yt-dlp/yt-dlp/commit/0c7e4cfcaed95909d7c1c0a11b5a12881bcfdfd6) ([#15267](https://github.com/yt-dlp/yt-dlp/issues/15267)) by [bashonly](https://github.com/bashonly)
|
||||
|
||||
#### Downloader changes
|
||||
- [Fix playback wait time for ffmpeg downloads](https://github.com/yt-dlp/yt-dlp/commit/23f1ab346927ab73ad510fd7ba105a69e5291c66) ([#15066](https://github.com/yt-dlp/yt-dlp/issues/15066)) by [bashonly](https://github.com/bashonly)
|
||||
|
||||
#### Postprocessor changes
|
||||
- **ffmpeg**: [Fix uncaught error if bad --ffmpeg-location is given](https://github.com/yt-dlp/yt-dlp/commit/0eed3fe530d6ff4b668494c5b1d4d6fc1ade96f7) ([#15104](https://github.com/yt-dlp/yt-dlp/issues/15104)) by [bashonly](https://github.com/bashonly)
|
||||
- **ffmpegmetadata**: [Add more tag mappings](https://github.com/yt-dlp/yt-dlp/commit/04050be583aae21f99932a674d1d2992ff016d5c) ([#14654](https://github.com/yt-dlp/yt-dlp/issues/14654)) by [garret1317](https://github.com/garret1317)
|
||||
|
||||
#### Networking changes
|
||||
- **Request Handler**: urllib: [Do not read after close](https://github.com/yt-dlp/yt-dlp/commit/6ee6a6fc58d6254ef944bd311e6890e208a75e98) ([#15049](https://github.com/yt-dlp/yt-dlp/issues/15049)) by [bashonly](https://github.com/bashonly)
|
||||
|
||||
#### Misc. changes
|
||||
- **build**: [Bump PyInstaller minimum version requirement to 6.17.0](https://github.com/yt-dlp/yt-dlp/commit/280165026886a1f1614ab527c34c66d71faa5d69) ([#15199](https://github.com/yt-dlp/yt-dlp/issues/15199)) by [bashonly](https://github.com/bashonly)
|
||||
- **cleanup**: Miscellaneous: [7a52ff2](https://github.com/yt-dlp/yt-dlp/commit/7a52ff29d86efc8f3adeba977b2009ce40b8e52e) by [bashonly](https://github.com/bashonly), [oxyzenQ](https://github.com/oxyzenQ), [RezSat](https://github.com/RezSat), [seproDev](https://github.com/seproDev)
|
||||
- **devscripts**: `install_deps`: [Align options/terms with PEP 735](https://github.com/yt-dlp/yt-dlp/commit/29fe515d8d3386b3406ff02bdabb967d6821bc02) ([#15200](https://github.com/yt-dlp/yt-dlp/issues/15200)) by [bashonly](https://github.com/bashonly)
|
||||
|
||||
### 2025.11.12
|
||||
|
||||
#### Important changes
|
||||
@@ -64,7 +122,7 @@ yt-dlp now requires users to have an external JavaScript runtime (e.g. Deno) ins
|
||||
- **build**: [Bump musllinux Python version to 3.14](https://github.com/yt-dlp/yt-dlp/commit/646904cd3a79429ec5fdc43f904b3f57ae213f34) ([#14623](https://github.com/yt-dlp/yt-dlp/issues/14623)) by [bashonly](https://github.com/bashonly)
|
||||
- **cleanup**
|
||||
- Miscellaneous
|
||||
- [c63b4e2](https://github.com/yt-dlp/yt-dlp/commit/c63b4e2a2b81cc78397c8709ef53ffd29bada213) by [bashonly](https://github.com/bashonly), [matyb08](https://github.com/matyb08), [sepro](https://github.com/sepro)
|
||||
- [c63b4e2](https://github.com/yt-dlp/yt-dlp/commit/c63b4e2a2b81cc78397c8709ef53ffd29bada213) by [bashonly](https://github.com/bashonly), [matyb08](https://github.com/matyb08), [seproDev](https://github.com/seproDev)
|
||||
- [335653b](https://github.com/yt-dlp/yt-dlp/commit/335653be82d5ef999cfc2879d005397402eebec1) by [bashonly](https://github.com/bashonly), [seproDev](https://github.com/seproDev)
|
||||
- **devscripts**: [Improve `install_deps` script](https://github.com/yt-dlp/yt-dlp/commit/73922e66e437fb4bb618bdc119a96375081bf508) ([#14766](https://github.com/yt-dlp/yt-dlp/issues/14766)) by [bashonly](https://github.com/bashonly)
|
||||
- **test**: [Skip flaky tests if source unchanged](https://github.com/yt-dlp/yt-dlp/commit/ade8c2b36ff300edef87d48fd1ba835ac35c5b63) ([#14970](https://github.com/yt-dlp/yt-dlp/issues/14970)) by [bashonly](https://github.com/bashonly), [Grub4K](https://github.com/Grub4K)
|
||||
|
||||
@@ -8,9 +8,7 @@ You can also find lists of all [contributors of yt-dlp](CONTRIBUTORS) and [autho
|
||||
|
||||
Core Maintainers are responsible for reviewing and merging contributions, publishing releases, and steering the overall direction of the project.
|
||||
|
||||
**You can contact the core maintainers via `maintainers@yt-dlp.org`.**
|
||||
|
||||
This is **NOT** a support channel. [Open an issue](https://github.com/yt-dlp/yt-dlp/issues/new/choose) if you need help or want to report a bug.
|
||||
**You can contact the core maintainers via `maintainers@yt-dlp.org`.** This email address is **NOT** a support channel. [Open an issue](https://github.com/yt-dlp/yt-dlp/issues/new/choose) if you need help or want to report a bug.
|
||||
|
||||
### [coletdjnz](https://github.com/coletdjnz)
|
||||
|
||||
@@ -18,6 +16,7 @@ This is **NOT** a support channel. [Open an issue](https://github.com/yt-dlp/yt-
|
||||
|
||||
* Overhauled the networking stack and implemented support for `requests` and `curl_cffi` (`--impersonate`) HTTP clients
|
||||
* Reworked the plugin architecture to support installing plugins across all yt-dlp distributions (exe, pip, etc.)
|
||||
* Implemented support for external JavaScript runtimes/engines
|
||||
* Maintains support for YouTube
|
||||
* Added and fixed support for various other sites
|
||||
|
||||
@@ -25,9 +24,10 @@ This is **NOT** a support channel. [Open an issue](https://github.com/yt-dlp/yt-
|
||||
|
||||
* Rewrote and maintains the build/release workflows and the self-updater: executables, automated/nightly/master releases, `--update-to`
|
||||
* Overhauled external downloader cookie handling
|
||||
* Helped in implementing support for external JavaScript runtimes/engines
|
||||
* Added `--cookies-from-browser` support for Firefox containers
|
||||
* Overhauled and maintains support for sites like Youtube, Vimeo, Twitter, TikTok, etc
|
||||
* Added support for sites like Dacast, Kick, Loom, SproutVideo, Triller, Weverse, etc
|
||||
* Maintains support for sites like YouTube, Vimeo, Twitter, TikTok, etc
|
||||
* Added support for various sites
|
||||
|
||||
|
||||
### [Grub4K](https://github.com/Grub4K)
|
||||
@@ -37,12 +37,14 @@ This is **NOT** a support channel. [Open an issue](https://github.com/yt-dlp/yt-
|
||||
* `--update-to`, self-updater rewrite, automated/nightly/master releases
|
||||
* Reworked internals like `traverse_obj`, various core refactors and bugs fixes
|
||||
* Implemented proper progress reporting for parallel downloads
|
||||
* Implemented support for external JavaScript runtimes/engines
|
||||
* Improved/fixed/added Bundestag, crunchyroll, pr0gramm, Twitter, WrestleUniverse etc
|
||||
|
||||
|
||||
### [sepro](https://github.com/seproDev)
|
||||
|
||||
* UX improvements: Warn when ffmpeg is missing, warn when double-clicking exe
|
||||
* Helped in implementing support for external JavaScript runtimes/engines
|
||||
* Code cleanup: Remove dead extractors, mark extractors as broken, enable/apply ruff rules
|
||||
* Improved/fixed/added ArdMediathek, DRTV, Floatplane, MagentaMusik, Naver, Nebula, OnDemandKorea, Vbox7 etc
|
||||
|
||||
|
||||
+3
-3
@@ -202,9 +202,9 @@ CONTRIBUTORS: Changelog.md
|
||||
|
||||
# The following EJS_-prefixed variables are auto-generated by devscripts/update_ejs.py
|
||||
# DO NOT EDIT!
|
||||
EJS_VERSION = 0.3.1
|
||||
EJS_WHEEL_NAME = yt_dlp_ejs-0.3.1-py3-none-any.whl
|
||||
EJS_WHEEL_HASH = sha256:a6e3548874db7c774388931752bb46c7f4642c044b2a189e56968f3d5ecab622
|
||||
EJS_VERSION = 0.3.2
|
||||
EJS_WHEEL_NAME = yt_dlp_ejs-0.3.2-py3-none-any.whl
|
||||
EJS_WHEEL_HASH = sha256:f2dc6b3d1b909af1f13e021621b0af048056fca5fb07c4db6aa9bbb37a4f66a9
|
||||
EJS_PY_FOLDERS = yt_dlp_ejs yt_dlp_ejs/yt yt_dlp_ejs/yt/solver
|
||||
EJS_PY_FILES = yt_dlp_ejs/__init__.py yt_dlp_ejs/_version.py yt_dlp_ejs/yt/__init__.py yt_dlp_ejs/yt/solver/__init__.py
|
||||
EJS_JS_FOLDERS = yt_dlp_ejs/yt/solver
|
||||
|
||||
+11
-11
@@ -203,7 +203,7 @@ Python versions 3.10+ (CPython) and 3.11+ (PyPy) are supported. Other versions a
|
||||
On Windows, [Microsoft Visual C++ 2010 SP1 Redistributable Package (x86)](https://download.microsoft.com/download/1/6/5/165255E7-1014-4D0A-B094-B6A430A6BFFC/vcredist_x86.exe) is also necessary to run yt-dlp. You probably already have this, but if the executable throws an error due to missing `MSVCR100.dll` you need to install it manually.
|
||||
-->
|
||||
|
||||
While all the other dependencies are optional, `ffmpeg`, `ffprobe`, `yt-dlp-ejs` and a JavaScript runtime are highly recommended
|
||||
While all the other dependencies are optional, `ffmpeg`, `ffprobe`, `yt-dlp-ejs` and a supported JavaScript runtime/engine are highly recommended
|
||||
|
||||
### Strongly recommended
|
||||
|
||||
@@ -215,7 +215,7 @@ While all the other dependencies are optional, `ffmpeg`, `ffprobe`, `yt-dlp-ejs`
|
||||
|
||||
* [**yt-dlp-ejs**](https://github.com/yt-dlp/ejs) - Required for deciphering YouTube n/sig values. Licensed under [Unlicense](https://github.com/yt-dlp/ejs/blob/main/LICENSE), bundles [MIT](https://github.com/davidbonnet/astring/blob/main/LICENSE) and [ISC](https://github.com/meriyah/meriyah/blob/main/LICENSE.md) components.
|
||||
|
||||
A JavaScript runtime like [**deno**](https://deno.land) (recommended), [**node.js**](https://nodejs.org), [**bun**](https://bun.sh), or [**QuickJS**](https://bellard.org/quickjs/) is also required to run yt-dlp-ejs. See [the wiki](https://github.com/yt-dlp/yt-dlp/wiki/EJS).
|
||||
A JavaScript runtime/engine like [**deno**](https://deno.land) (recommended), [**node.js**](https://nodejs.org), [**bun**](https://bun.sh), or [**QuickJS**](https://bellard.org/quickjs/) is also required to run yt-dlp-ejs. See [the wiki](https://github.com/yt-dlp/yt-dlp/wiki/EJS).
|
||||
|
||||
### Networking
|
||||
* [**certifi**](https://github.com/certifi/python-certifi)\* - Provides Mozilla's root certificate bundle. Licensed under [MPLv2](https://github.com/certifi/python-certifi/blob/master/LICENSE)
|
||||
@@ -228,7 +228,7 @@ While all the other dependencies are optional, `ffmpeg`, `ffprobe`, `yt-dlp-ejs`
|
||||
The following provide support for impersonating browser requests. This may be required for some sites that employ TLS fingerprinting.
|
||||
|
||||
* [**curl_cffi**](https://github.com/lexiforest/curl_cffi) (recommended) - Python binding for [curl-impersonate](https://github.com/lexiforest/curl-impersonate). Provides impersonation targets for Chrome, Edge and Safari. Licensed under [MIT](https://github.com/lexiforest/curl_cffi/blob/main/LICENSE)
|
||||
* Can be installed with the `curl-cffi` group, e.g. `pip install "yt-dlp[default,curl-cffi]"`
|
||||
* Can be installed with the `curl-cffi` extra, e.g. `pip install "yt-dlp[default,curl-cffi]"`
|
||||
* Currently included in most builds *except* `yt-dlp` (Unix zipimport binary), `yt-dlp_x86` (Windows 32-bit) and `yt-dlp_musllinux_aarch64`
|
||||
|
||||
|
||||
@@ -265,7 +265,7 @@ To build the standalone executable, you must have Python and `pyinstaller` (plus
|
||||
You can run the following commands:
|
||||
|
||||
```
|
||||
python devscripts/install_deps.py --include-group pyinstaller
|
||||
python devscripts/install_deps.py --include-extra pyinstaller
|
||||
python devscripts/make_lazy_extractors.py
|
||||
python -m bundle.pyinstaller
|
||||
```
|
||||
@@ -483,7 +483,7 @@ Tip: Use `CTRL`+`F` (or `Command`+`F`) to search by keywords
|
||||
two-letter ISO 3166-2 country code
|
||||
|
||||
## Video Selection:
|
||||
-I, --playlist-items ITEM_SPEC Comma separated playlist_index of the items
|
||||
-I, --playlist-items ITEM_SPEC Comma-separated playlist_index of the items
|
||||
to download. You can specify a range using
|
||||
"[START]:[STOP][:STEP]". For backward
|
||||
compatibility, START-STOP is also supported.
|
||||
@@ -1299,7 +1299,7 @@ The field names themselves (the part inside the parenthesis) can also have some
|
||||
|
||||
1. **Default**: A literal default value can be specified for when the field is empty using a `|` separator. This overrides `--output-na-placeholder`. E.g. `%(uploader|Unknown)s`
|
||||
|
||||
1. **More Conversions**: In addition to the normal format types `diouxXeEfFgGcrs`, yt-dlp additionally supports converting to `B` = **B**ytes, `j` = **j**son (flag `#` for pretty-printing, `+` for Unicode), `h` = HTML escaping, `l` = a comma separated **l**ist (flag `#` for `\n` newline-separated), `q` = a string **q**uoted for the terminal (flag `#` to split a list into different arguments), `D` = add **D**ecimal suffixes (e.g. 10M) (flag `#` to use 1024 as factor), and `S` = **S**anitize as filename (flag `#` for restricted)
|
||||
1. **More Conversions**: In addition to the normal format types `diouxXeEfFgGcrs`, yt-dlp additionally supports converting to `B` = **B**ytes, `j` = **j**son (flag `#` for pretty-printing, `+` for Unicode), `h` = HTML escaping, `l` = a comma-separated **l**ist (flag `#` for `\n` newline-separated), `q` = a string **q**uoted for the terminal (flag `#` to split a list into different arguments), `D` = add **D**ecimal suffixes (e.g. 10M) (flag `#` to use 1024 as factor), and `S` = **S**anitize as filename (flag `#` for restricted)
|
||||
|
||||
1. **Unicode normalization**: The format type `U` can be used for NFC [Unicode normalization](https://docs.python.org/3/library/unicodedata.html#unicodedata.normalize). The alternate form flag (`#`) changes the normalization to NFD and the conversion flag `+` can be used for NFKC/NFKD compatibility equivalence normalization. E.g. `%(title)+.100U` is NFKC
|
||||
|
||||
@@ -1798,8 +1798,8 @@ Metadata fields | From
|
||||
`track` | `track_number`
|
||||
`artist` | `artist`, `artists`, `creator`, `creators`, `uploader` or `uploader_id`
|
||||
`composer` | `composer` or `composers`
|
||||
`genre` | `genre` or `genres`
|
||||
`album` | `album`
|
||||
`genre` | `genre`, `genres`, `categories` or `tags`
|
||||
`album` | `album` or `series`
|
||||
`album_artist` | `album_artist` or `album_artists`
|
||||
`disc` | `disc_number`
|
||||
`show` | `series`
|
||||
@@ -1852,7 +1852,7 @@ The following extractors use this feature:
|
||||
#### youtube
|
||||
* `lang`: Prefer translated metadata (`title`, `description` etc) of this language code (case-sensitive). By default, the video primary language metadata is preferred, with a fallback to `en` translated. See [youtube/_base.py](https://github.com/yt-dlp/yt-dlp/blob/415b4c9f955b1a0391204bd24a7132590e7b3bdb/yt_dlp/extractor/youtube/_base.py#L402-L409) for the list of supported content language codes
|
||||
* `skip`: One or more of `hls`, `dash` or `translated_subs` to skip extraction of the m3u8 manifests, dash manifests and [auto-translated subtitles](https://github.com/yt-dlp/yt-dlp/issues/4090#issuecomment-1158102032) respectively
|
||||
* `player_client`: Clients to extract video data from. The currently available clients are `web`, `web_safari`, `web_embedded`, `web_music`, `web_creator`, `mweb`, `ios`, `android`, `android_sdkless`, `android_vr`, `tv`, `tv_simply`, `tv_downgraded`, and `tv_embedded`. By default, `tv,android_sdkless,web` is used. If no JavaScript runtime is available, then `android_sdkless,web_safari,web` is used. If logged-in cookies are passed to yt-dlp, then `tv_downgraded,web_safari,web` is used for free accounts and `tv_downgraded,web_creator,web` is used for premium accounts. The `web_music` client is added for `music.youtube.com` URLs when logged-in cookies are used. The `web_embedded` client is added for age-restricted videos but only works if the video is embeddable. The `tv_embedded` and `web_creator` clients are added for age-restricted videos if account age-verification is required. Some clients, such as `web` and `web_music`, require a `po_token` for their formats to be downloadable. Some clients, such as `web_creator`, will only work with authentication. Not all clients support authentication via cookies. You can use `default` for the default clients, or you can use `all` for all clients (not recommended). You can prefix a client with `-` to exclude it, e.g. `youtube:player_client=default,-ios`
|
||||
* `player_client`: Clients to extract video data from. The currently available clients are `web`, `web_safari`, `web_embedded`, `web_music`, `web_creator`, `mweb`, `ios`, `android`, `android_sdkless`, `android_vr`, `tv`, `tv_simply`, `tv_downgraded`, and `tv_embedded`. By default, `tv,android_sdkless,web` is used. If no JavaScript runtime/engine is available, then `android_sdkless,web_safari,web` is used. If logged-in cookies are passed to yt-dlp, then `tv_downgraded,web_safari,web` is used for free accounts and `tv_downgraded,web_creator,web` is used for premium accounts. The `web_music` client is added for `music.youtube.com` URLs when logged-in cookies are used. The `web_embedded` client is added for age-restricted videos but only works if the video is embeddable. The `tv_embedded` and `web_creator` clients are added for age-restricted videos if account age-verification is required. Some clients, such as `web` and `web_music`, require a `po_token` for their formats to be downloadable. Some clients, such as `web_creator`, will only work with authentication. Not all clients support authentication via cookies. You can use `default` for the default clients, or you can use `all` for all clients (not recommended). You can prefix a client with `-` to exclude it, e.g. `youtube:player_client=default,-ios`
|
||||
* `player_skip`: Skip some network requests that are generally needed for robust extraction. One or more of `configs` (skip client configs), `webpage` (skip initial webpage), `js` (skip js player), `initial_data` (skip initial data/next ep request). While these options can help reduce the number of requests needed or avoid some rate-limiting, they could cause issues such as missing formats or metadata. See [#860](https://github.com/yt-dlp/yt-dlp/pull/860) and [#12826](https://github.com/yt-dlp/yt-dlp/issues/12826) for more details
|
||||
* `webpage_skip`: Skip extraction of embedded webpage data. One or both of `player_response`, `initial_data`. These options are for testing purposes and don't skip any network requests
|
||||
* `player_params`: YouTube player parameters to use for player requests. Will overwrite any default ones set by yt-dlp.
|
||||
@@ -1867,14 +1867,14 @@ The following extractors use this feature:
|
||||
* `raise_incomplete_data`: `Incomplete Data Received` raises an error instead of reporting a warning
|
||||
* `data_sync_id`: Overrides the account Data Sync ID used in Innertube API requests. This may be needed if you are using an account with `youtube:player_skip=webpage,configs` or `youtubetab:skip=webpage`
|
||||
* `visitor_data`: Overrides the Visitor Data used in Innertube API requests. This should be used with `player_skip=webpage,configs` and without cookies. Note: this may have adverse effects if used improperly. If a session from a browser is wanted, you should pass cookies instead (which contain the Visitor ID)
|
||||
* `po_token`: Proof of Origin (PO) Token(s) to use. Comma seperated list of PO Tokens in the format `CLIENT.CONTEXT+PO_TOKEN`, e.g. `youtube:po_token=web.gvs+XXX,web.player=XXX,web_safari.gvs+YYY`. Context can be any of `gvs` (Google Video Server URLs), `player` (Innertube player request) or `subs` (Subtitles)
|
||||
* `po_token`: Proof of Origin (PO) Token(s) to use. Comma-separated list of PO Tokens in the format `CLIENT.CONTEXT+PO_TOKEN`, e.g. `youtube:po_token=web.gvs+XXX,web.player=XXX,web_safari.gvs+YYY`. Context can be any of `gvs` (Google Video Server URLs), `player` (Innertube player request) or `subs` (Subtitles)
|
||||
* `pot_trace`: Enable debug logging for PO Token fetching. Either `true` or `false` (default)
|
||||
* `fetch_pot`: Policy to use for fetching a PO Token from providers. One of `always` (always try fetch a PO Token regardless if the client requires one for the given context), `never` (never fetch a PO Token), or `auto` (default; only fetch a PO Token if the client requires one for the given context)
|
||||
* `jsc_trace`: Enable debug logging for JS Challenge fetching. Either `true` or `false` (default)
|
||||
* `use_ad_playback_context`: Skip preroll ads to eliminate the mandatory wait period before download. Do NOT use this when passing premium account cookies to yt-dlp, as it will result in a loss of premium formats. Only effective with the `web`, `web_safari`, `web_music` and `mweb` player clients. Either `true` or `false` (default)
|
||||
|
||||
#### youtube-ejs
|
||||
* `jitless`: Run suported Javascript engines in JIT-less mode. Supported runtimes are `deno`, `node` and `bun`. Provides better security at the cost of performance/speed. Do note that `node` and `bun` are still considered unsecure. Either `true` or `false` (default)
|
||||
* `jitless`: Run supported Javascript engines in JIT-less mode. Supported runtimes are `deno`, `node` and `bun`. Provides better security at the cost of performance/speed. Do note that `node` and `bun` are still considered insecure. Either `true` or `false` (default)
|
||||
|
||||
#### youtubepot-webpo
|
||||
* `bind_to_visitor_id`: Whether to use the Visitor ID instead of Visitor Data for caching WebPO tokens. Either `true` (default) or `false`
|
||||
|
||||
@@ -15,12 +15,12 @@ function venvpy {
|
||||
}
|
||||
|
||||
INCLUDES=(
|
||||
--include-group pyinstaller
|
||||
--include-group secretstorage
|
||||
--include-extra pyinstaller
|
||||
--include-extra secretstorage
|
||||
)
|
||||
|
||||
if [[ -z "${EXCLUDE_CURL_CFFI:-}" ]]; then
|
||||
INCLUDES+=(--include-group curl-cffi)
|
||||
INCLUDES+=(--include-extra curl-cffi)
|
||||
fi
|
||||
|
||||
runpy -m venv /yt-dlp-build-venv
|
||||
@@ -28,7 +28,7 @@ runpy -m venv /yt-dlp-build-venv
|
||||
source /yt-dlp-build-venv/bin/activate
|
||||
# Inside the venv we use venvpy instead of runpy
|
||||
venvpy -m ensurepip --upgrade --default-pip
|
||||
venvpy -m devscripts.install_deps --only-optional-groups --include-group build
|
||||
venvpy -m devscripts.install_deps --omit-default --include-extra build
|
||||
venvpy -m devscripts.install_deps "${INCLUDES[@]}"
|
||||
venvpy -m devscripts.make_lazy_extractors
|
||||
venvpy devscripts/update-version.py -c "${CHANNEL}" -r "${ORIGIN}" "${VERSION}"
|
||||
|
||||
@@ -319,5 +319,11 @@
|
||||
"action": "add",
|
||||
"when": "6224a3898821965a7d6a2cb9cc2de40a0fd6e6bc",
|
||||
"short": "[priority] **An external JavaScript runtime is now required for full YouTube support**\nyt-dlp now requires users to have an external JavaScript runtime (e.g. Deno) installed in order to solve the JavaScript challenges presented by YouTube. [Read more](https://github.com/yt-dlp/yt-dlp/issues/15012)"
|
||||
},
|
||||
{
|
||||
"action": "change",
|
||||
"when": "c63b4e2a2b81cc78397c8709ef53ffd29bada213",
|
||||
"short": "[cleanup] Misc (#14767)",
|
||||
"authors": ["bashonly", "seproDev", "matyb08"]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -25,16 +25,16 @@ def parse_args():
|
||||
'-e', '--exclude-dependency', metavar='DEPENDENCY', action='append',
|
||||
help='exclude a dependency (can be used multiple times)')
|
||||
parser.add_argument(
|
||||
'-i', '--include-group', metavar='GROUP', action='append',
|
||||
help='include an optional dependency group (can be used multiple times)')
|
||||
'-i', '--include-extra', metavar='EXTRA', action='append',
|
||||
help='include an extra/optional-dependencies list (can be used multiple times)')
|
||||
parser.add_argument(
|
||||
'-c', '--cherry-pick', metavar='DEPENDENCY', action='append',
|
||||
help=(
|
||||
'only include a specific dependency from the resulting dependency list '
|
||||
'(can be used multiple times)'))
|
||||
parser.add_argument(
|
||||
'-o', '--only-optional-groups', action='store_true',
|
||||
help='omit default dependencies unless the "default" group is specified with --include-group')
|
||||
'-o', '--omit-default', action='store_true',
|
||||
help='omit the "default" extra unless it is explicitly included (it is included by default)')
|
||||
parser.add_argument(
|
||||
'-p', '--print', action='store_true',
|
||||
help='only print requirements to stdout')
|
||||
@@ -51,27 +51,27 @@ def uniq(arg) -> dict[str, None]:
|
||||
def main():
|
||||
args = parse_args()
|
||||
project_table = parse_toml(read_file(args.input))['project']
|
||||
recursive_pattern = re.compile(rf'{project_table["name"]}\[(?P<group_name>[\w-]+)\]')
|
||||
optional_groups = project_table['optional-dependencies']
|
||||
recursive_pattern = re.compile(rf'{project_table["name"]}\[(?P<extra_name>[\w-]+)\]')
|
||||
extras = project_table['optional-dependencies']
|
||||
|
||||
excludes = uniq(args.exclude_dependency)
|
||||
only_includes = uniq(args.cherry_pick)
|
||||
include_groups = uniq(args.include_group)
|
||||
include_extras = uniq(args.include_extra)
|
||||
|
||||
def yield_deps(group):
|
||||
for dep in group:
|
||||
def yield_deps(extra):
|
||||
for dep in extra:
|
||||
if mobj := recursive_pattern.fullmatch(dep):
|
||||
yield from optional_groups.get(mobj.group('group_name'), ())
|
||||
yield from extras.get(mobj.group('extra_name'), ())
|
||||
else:
|
||||
yield dep
|
||||
|
||||
targets = {}
|
||||
if not args.only_optional_groups:
|
||||
if not args.omit_default:
|
||||
# legacy: 'dependencies' is empty now
|
||||
targets.update(dict.fromkeys(project_table['dependencies']))
|
||||
targets.update(dict.fromkeys(yield_deps(optional_groups['default'])))
|
||||
targets.update(dict.fromkeys(yield_deps(extras['default'])))
|
||||
|
||||
for include in filter(None, map(optional_groups.get, include_groups)):
|
||||
for include in filter(None, map(extras.get, include_extras)):
|
||||
targets.update(dict.fromkeys(yield_deps(include)))
|
||||
|
||||
def target_filter(target):
|
||||
|
||||
@@ -251,7 +251,13 @@ class CommitRange:
|
||||
''', re.VERBOSE | re.DOTALL)
|
||||
EXTRACTOR_INDICATOR_RE = re.compile(r'(?:Fix|Add)\s+Extractors?', re.IGNORECASE)
|
||||
REVERT_RE = re.compile(r'(?:\[[^\]]+\]\s+)?(?i:Revert)\s+([\da-f]{40})')
|
||||
FIXES_RE = re.compile(r'(?i:(?:bug\s*)?fix(?:es)?(?:\s+bugs?)?(?:\s+in|\s+for)?|Improve)\s+([\da-f]{40})')
|
||||
FIXES_RE = re.compile(r'''
|
||||
(?i:
|
||||
(?:bug\s*)?fix(?:es)?(?:
|
||||
\s+(?:bugs?|regression(?:\s+introduced)?)
|
||||
)?(?:\s+(?:in|for|from|by))?
|
||||
|Improve
|
||||
)\s+([\da-f]{40})''', re.VERBOSE)
|
||||
UPSTREAM_MERGE_RE = re.compile(r'Update to ytdl-commit-([\da-f]+)')
|
||||
|
||||
def __init__(self, start, end, default_author=None):
|
||||
|
||||
@@ -56,7 +56,7 @@ default = [
|
||||
"requests>=2.32.2,<3",
|
||||
"urllib3>=2.0.2,<3",
|
||||
"websockets>=13.0",
|
||||
"yt-dlp-ejs==0.3.1",
|
||||
"yt-dlp-ejs==0.3.2",
|
||||
]
|
||||
curl-cffi = [
|
||||
"curl-cffi>=0.5.10,!=0.6.*,!=0.7.*,!=0.8.*,!=0.9.*,<0.14; implementation_name=='cpython'",
|
||||
|
||||
@@ -50,8 +50,10 @@ The only reliable way to check if a site is supported is to try it.
|
||||
- **aenetworks:collection**
|
||||
- **aenetworks:show**
|
||||
- **AeonCo**
|
||||
- **agalega:videos**
|
||||
- **AirTV**
|
||||
- **AitubeKZVideo**
|
||||
- **Alibaba**
|
||||
- **AliExpressLive**
|
||||
- **AlJazeera**
|
||||
- **Allocine**
|
||||
@@ -190,6 +192,7 @@ The only reliable way to check if a site is supported is to try it.
|
||||
- **Biography**
|
||||
- **BitChute**
|
||||
- **BitChuteChannel**
|
||||
- **Bitmovin**
|
||||
- **BlackboardCollaborate**
|
||||
- **BlackboardCollaborateLaunch**
|
||||
- **BleacherReport**: (**Currently broken**)
|
||||
@@ -731,7 +734,7 @@ The only reliable way to check if a site is supported is to try it.
|
||||
- **loc**: Library of Congress
|
||||
- **Loco**
|
||||
- **loom**
|
||||
- **loom:folder**
|
||||
- **loom:folder**: (**Currently broken**)
|
||||
- **LoveHomePorn**
|
||||
- **LRTRadio**
|
||||
- **LRTStream**
|
||||
@@ -762,7 +765,8 @@ The only reliable way to check if a site is supported is to try it.
|
||||
- **massengeschmack.tv**
|
||||
- **Masters**
|
||||
- **MatchTV**
|
||||
- **Mave**
|
||||
- **mave**
|
||||
- **mave:channel**
|
||||
- **MBN**: mbn.co.kr (매일방송)
|
||||
- **MDR**: MDR.DE
|
||||
- **MedalTV**
|
||||
@@ -895,6 +899,8 @@ The only reliable way to check if a site is supported is to try it.
|
||||
- **NerdCubedFeed**
|
||||
- **Nest**
|
||||
- **NestClip**
|
||||
- **NetAppCollection**
|
||||
- **NetAppVideo**
|
||||
- **netease:album**: 网易云音乐 - 专辑
|
||||
- **netease:djradio**: 网易云音乐 - 电台
|
||||
- **netease:mv**: 网易云音乐 - MV
|
||||
@@ -962,6 +968,7 @@ The only reliable way to check if a site is supported is to try it.
|
||||
- **Nova**: TN.cz, Prásk.tv, Nova.cz, Novaplus.cz, FANDA.tv, Krásná.cz and Doma.cz
|
||||
- **NovaEmbed**
|
||||
- **NovaPlay**
|
||||
- **NowCanal**
|
||||
- **nowness**
|
||||
- **nowness:playlist**
|
||||
- **nowness:series**
|
||||
@@ -1373,7 +1380,7 @@ The only reliable way to check if a site is supported is to try it.
|
||||
- **Spiegel**
|
||||
- **Sport5**
|
||||
- **SportBox**: (**Currently broken**)
|
||||
- **SportDeutschland**
|
||||
- **sporteurope**
|
||||
- **Spreaker**
|
||||
- **SpreakerShow**
|
||||
- **SpringboardPlatform**
|
||||
@@ -1461,6 +1468,8 @@ The only reliable way to check if a site is supported is to try it.
|
||||
- **TFO**: (**Currently broken**)
|
||||
- **theatercomplextown:ppv**: [*theatercomplextown*](## "netrc machine")
|
||||
- **theatercomplextown:vod**: [*theatercomplextown*](## "netrc machine")
|
||||
- **TheChosen**
|
||||
- **TheChosenGroup**
|
||||
- **TheGuardianPodcast**
|
||||
- **TheGuardianPodcastPlaylist**
|
||||
- **TheHighWire**
|
||||
@@ -1778,6 +1787,7 @@ The only reliable way to check if a site is supported is to try it.
|
||||
- **YapFiles**: (**Currently broken**)
|
||||
- **Yappy**: (**Currently broken**)
|
||||
- **YappyProfile**
|
||||
- **yfanefa**
|
||||
- **YleAreena**
|
||||
- **YouJizz**
|
||||
- **youku**: 优酷
|
||||
|
||||
@@ -1403,6 +1403,9 @@ class TestUtil(unittest.TestCase):
|
||||
self.assertEqual(version_tuple('1'), (1,))
|
||||
self.assertEqual(version_tuple('10.23.344'), (10, 23, 344))
|
||||
self.assertEqual(version_tuple('10.1-6'), (10, 1, 6)) # avconv style
|
||||
self.assertEqual(version_tuple('invalid', lenient=True), (-1,))
|
||||
self.assertEqual(version_tuple('1.2.3', lenient=True), (1, 2, 3))
|
||||
self.assertEqual(version_tuple('12.34-something', lenient=True), (12, 34, -1))
|
||||
|
||||
def test_detect_exe_version(self):
|
||||
self.assertEqual(detect_exe_version('''ffmpeg version 1.2.1
|
||||
|
||||
@@ -212,9 +212,16 @@ def _firefox_browser_dirs():
|
||||
|
||||
else:
|
||||
yield from map(os.path.expanduser, (
|
||||
# New installations of FF147+ respect the XDG base directory specification
|
||||
# Ref: https://bugzilla.mozilla.org/show_bug.cgi?id=259356
|
||||
os.path.join(_config_home(), 'mozilla/firefox'),
|
||||
# Existing FF version<=146 installations
|
||||
'~/.mozilla/firefox',
|
||||
'~/snap/firefox/common/.mozilla/firefox',
|
||||
# Flatpak XDG: https://docs.flatpak.org/en/latest/conventions.html#xdg-base-directories
|
||||
'~/.var/app/org.mozilla.firefox/config/mozilla/firefox',
|
||||
'~/.var/app/org.mozilla.firefox/.mozilla/firefox',
|
||||
# Snap installations do not respect the XDG base directory specification
|
||||
'~/snap/firefox/common/.mozilla/firefox',
|
||||
))
|
||||
|
||||
|
||||
|
||||
@@ -2914,10 +2914,10 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
|
||||
if not (requested_clients or excluded_clients) and default_clients == self._DEFAULT_JSLESS_CLIENTS:
|
||||
self.report_warning(
|
||||
f'No supported JavaScript runtime could be found. YouTube extraction without '
|
||||
f'a JS runtime has been deprecated, and some formats may be missing. '
|
||||
f'See {_EJS_WIKI_URL} for details on installing one. To silence this warning, '
|
||||
f'you can use --extractor-args "youtube:player_client=default"', only_once=True)
|
||||
f'No supported JavaScript runtime could be found. Only deno is enabled by default; '
|
||||
f'to use another runtime add --js-runtimes RUNTIME[:PATH] to your command/config. '
|
||||
f'YouTube extraction without a JS runtime has been deprecated, and some formats may be missing. '
|
||||
f'See {_EJS_WIKI_URL} for details on installing one', only_once=True)
|
||||
|
||||
if not requested_clients:
|
||||
requested_clients.extend(default_clients)
|
||||
|
||||
@@ -21,6 +21,7 @@ from yt_dlp.extractor.youtube.jsc.provider import (
|
||||
)
|
||||
from yt_dlp.extractor.youtube.pot._provider import configuration_arg
|
||||
from yt_dlp.extractor.youtube.pot.provider import provider_bug_report_message
|
||||
from yt_dlp.utils import version_tuple
|
||||
from yt_dlp.utils._jsruntime import JsRuntimeInfo
|
||||
|
||||
if _has_ejs:
|
||||
@@ -223,7 +224,8 @@ class EJSBaseJCP(JsChallengeProvider):
|
||||
skipped_components.append(script)
|
||||
continue
|
||||
if not self.is_dev:
|
||||
if script.version != self._SCRIPT_VERSION:
|
||||
# Matching patch version is expected to have same hash
|
||||
if version_tuple(script.version, lenient=True)[:2] != version_tuple(self._SCRIPT_VERSION, lenient=True)[:2]:
|
||||
self.logger.warning(
|
||||
f'Challenge solver {script_type.value} script version {script.version} '
|
||||
f'is not supported (source: {script.source.value}, variant: {script.variant}, supported version: {self._SCRIPT_VERSION})')
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# This file is generated by devscripts/update_ejs.py. DO NOT MODIFY!
|
||||
|
||||
VERSION = '0.3.1'
|
||||
VERSION = '0.3.2'
|
||||
HASHES = {
|
||||
'yt.solver.bun.lib.js': '6ff45e94de9f0ea936a183c48173cfa9ce526ee4b7544cd556428427c1dd53c8073ef0174e79b320252bf0e7c64b0032cc1cf9c4358f3fda59033b7caa01c241',
|
||||
'yt.solver.core.js': '0cd96b2d3f319dfa62cae689efa7d930ef1706e95f5921794db5089b2262957ec0a17d73938d8975ea35d0309cbfb4c8e4418d5e219837215eee242890c8b64d',
|
||||
|
||||
@@ -689,7 +689,7 @@ def create_parser():
|
||||
'-I', '--playlist-items',
|
||||
dest='playlist_items', metavar='ITEM_SPEC', default=None,
|
||||
help=(
|
||||
'Comma separated playlist_index of the items to download. '
|
||||
'Comma-separated playlist_index of the items to download. '
|
||||
'You can specify a range using "[START]:[STOP][:STEP]". For backward compatibility, START-STOP is also supported. '
|
||||
'Use negative indices to count from the right and negative STEP to download in reverse order. '
|
||||
'E.g. "-I 1:3,7,-5::2" used on a playlist of size 15 will download the items at index 1,2,3,7,11,13,15'))
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user