mirror of
https://github.com/bolucat/Archive.git
synced 2025-09-26 20:21:35 +08:00
Update On Fri Sep 12 20:36:09 CEST 2025
This commit is contained in:
1
.github/update.log
vendored
1
.github/update.log
vendored
@@ -1118,3 +1118,4 @@ Update On Mon Sep 8 20:41:03 CEST 2025
|
||||
Update On Tue Sep 9 20:33:51 CEST 2025
|
||||
Update On Wed Sep 10 20:42:57 CEST 2025
|
||||
Update On Thu Sep 11 20:34:24 CEST 2025
|
||||
Update On Fri Sep 12 20:36:01 CEST 2025
|
||||
|
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/component/proxydialer"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/ntp"
|
||||
gost "github.com/metacubex/mihomo/transport/gost-plugin"
|
||||
"github.com/metacubex/mihomo/transport/restls"
|
||||
obfs "github.com/metacubex/mihomo/transport/simple-obfs"
|
||||
@@ -251,8 +252,9 @@ func (ss *ShadowSocks) SupportUOT() bool {
|
||||
|
||||
func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||
method, err := shadowsocks.CreateMethod(context.Background(), option.Cipher, shadowsocks.MethodOptions{
|
||||
method, err := shadowsocks.CreateMethod(option.Cipher, shadowsocks.MethodOptions{
|
||||
Password: option.Password,
|
||||
TimeFunc: ntp.Now,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ss %s cipher: %s initialize error: %w", addr, option.Cipher, err)
|
||||
|
@@ -331,15 +331,22 @@ func (cp *CompatibleProvider) Close() error {
|
||||
}
|
||||
|
||||
func NewProxiesParser(filter string, excludeFilter string, excludeType string, dialerProxy string, override OverrideSchema) (resource.Parser[[]C.Proxy], error) {
|
||||
excludeFilterReg, err := regexp2.Compile(excludeFilter, regexp2.None)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid excludeFilter regex: %w", err)
|
||||
}
|
||||
var excludeTypeArray []string
|
||||
if excludeType != "" {
|
||||
excludeTypeArray = strings.Split(excludeType, "|")
|
||||
}
|
||||
|
||||
var excludeFilterRegs []*regexp2.Regexp
|
||||
if excludeFilter != "" {
|
||||
for _, excludeFilter := range strings.Split(excludeFilter, "`") {
|
||||
excludeFilterReg, err := regexp2.Compile(excludeFilter, regexp2.None)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid excludeFilter regex: %w", err)
|
||||
}
|
||||
excludeFilterRegs = append(excludeFilterRegs, excludeFilterReg)
|
||||
}
|
||||
}
|
||||
|
||||
var filterRegs []*regexp2.Regexp
|
||||
for _, filter := range strings.Split(filter, "`") {
|
||||
filterReg, err := regexp2.Compile(filter, regexp2.None)
|
||||
@@ -367,8 +374,9 @@ func NewProxiesParser(filter string, excludeFilter string, excludeType string, d
|
||||
proxies := []C.Proxy{}
|
||||
proxiesSet := map[string]struct{}{}
|
||||
for _, filterReg := range filterRegs {
|
||||
LOOP1:
|
||||
for idx, mapping := range schema.Proxies {
|
||||
if nil != excludeTypeArray && len(excludeTypeArray) > 0 {
|
||||
if len(excludeTypeArray) > 0 {
|
||||
mType, ok := mapping["type"]
|
||||
if !ok {
|
||||
continue
|
||||
@@ -377,18 +385,11 @@ func NewProxiesParser(filter string, excludeFilter string, excludeType string, d
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
flag := false
|
||||
for i := range excludeTypeArray {
|
||||
if strings.EqualFold(pType, excludeTypeArray[i]) {
|
||||
flag = true
|
||||
break
|
||||
for _, excludeType := range excludeTypeArray {
|
||||
if strings.EqualFold(pType, excludeType) {
|
||||
continue LOOP1
|
||||
}
|
||||
|
||||
}
|
||||
if flag {
|
||||
continue
|
||||
}
|
||||
|
||||
}
|
||||
mName, ok := mapping["name"]
|
||||
if !ok {
|
||||
@@ -398,9 +399,11 @@ func NewProxiesParser(filter string, excludeFilter string, excludeType string, d
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if len(excludeFilter) > 0 {
|
||||
if mat, _ := excludeFilterReg.MatchString(name); mat {
|
||||
continue
|
||||
if len(excludeFilterRegs) > 0 {
|
||||
for _, excludeFilterReg := range excludeFilterRegs {
|
||||
if mat, _ := excludeFilterReg.MatchString(name); mat {
|
||||
continue LOOP1
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(filter) > 0 {
|
||||
|
@@ -24,13 +24,13 @@ require (
|
||||
github.com/metacubex/quic-go v0.54.1-0.20250730114134-a1ae705fe295
|
||||
github.com/metacubex/randv2 v0.2.0
|
||||
github.com/metacubex/restls-client-go v0.1.7
|
||||
github.com/metacubex/sing v0.5.6-0.20250904143031-f1a62fab1489
|
||||
github.com/metacubex/sing v0.5.6-0.20250912172506-82b42a287539
|
||||
github.com/metacubex/sing-mux v0.3.3
|
||||
github.com/metacubex/sing-quic v0.0.0-20250909002258-06122df8f231
|
||||
github.com/metacubex/sing-shadowsocks v0.2.12
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.6
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.7
|
||||
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2
|
||||
github.com/metacubex/sing-tun v0.4.8-0.20250910070000-df2c1a4be299
|
||||
github.com/metacubex/sing-tun v0.4.8-0.20250912172659-89eba941fb22
|
||||
github.com/metacubex/sing-vmess v0.2.4-0.20250908094854-bc8e2a88b115
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f
|
||||
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee
|
||||
|
@@ -117,20 +117,20 @@ github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFq
|
||||
github.com/metacubex/restls-client-go v0.1.7 h1:eCwiXCTQb5WJu9IlgYvDBA1OgrINv58dEe7hcN5H15k=
|
||||
github.com/metacubex/restls-client-go v0.1.7/go.mod h1:BN/U52vPw7j8VTSh2vleD/MnmVKCov84mS5VcjVHH4g=
|
||||
github.com/metacubex/sing v0.5.2/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
|
||||
github.com/metacubex/sing v0.5.6-0.20250904143031-f1a62fab1489 h1:jKOFzhHTbxqhCluh5ONxjDe6CJMNHvgniXAf1RWuzlE=
|
||||
github.com/metacubex/sing v0.5.6-0.20250904143031-f1a62fab1489/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
|
||||
github.com/metacubex/sing v0.5.6-0.20250912172506-82b42a287539 h1:ArXEdw7JvbL3dLc3D7kBGTDmuBBI/sNIyR3O4MlfPH8=
|
||||
github.com/metacubex/sing v0.5.6-0.20250912172506-82b42a287539/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
|
||||
github.com/metacubex/sing-mux v0.3.3 h1:oqCbUAJgTLsa71vfo8otW8xIhrDfbc/Y2rmtW34sQjg=
|
||||
github.com/metacubex/sing-mux v0.3.3/go.mod h1:3rt1soewn0O6j89GCLmwAQFsq257u0jf2zQSPhTL3Bw=
|
||||
github.com/metacubex/sing-quic v0.0.0-20250909002258-06122df8f231 h1:dGvo7UahC/gYBQNBoictr14baJzBjAKUAorP63QFFtg=
|
||||
github.com/metacubex/sing-quic v0.0.0-20250909002258-06122df8f231/go.mod h1:B60FxaPHjR1SeQB0IiLrgwgvKsaoASfOWdiqhLjmMGA=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.12 h1:Wqzo8bYXrK5aWqxu/TjlTnYZzAKtKsaFQBdr6IHFaBE=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.12/go.mod h1:2e5EIaw0rxKrm1YTRmiMnDulwbGxH9hAFlrwQLQMQkU=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.6 h1:ZR1kYT0f0Vi64iQSS09OdhFfppiNkh7kjgRdMm0SB98=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.6/go.mod h1:vOEbfKC60txi0ca+yUlqEwOGc3Obl6cnSgx9Gf45KjE=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.7 h1:hSuuc0YpsfiqYqt1o+fP4m34BQz4e6wVj3PPBVhor3A=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.7/go.mod h1:vOEbfKC60txi0ca+yUlqEwOGc3Obl6cnSgx9Gf45KjE=
|
||||
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 h1:gXU+MYPm7Wme3/OAY2FFzVq9d9GxPHOqu5AQfg/ddhI=
|
||||
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2/go.mod h1:mbfboaXauKJNIHJYxQRa+NJs4JU9NZfkA+I33dS2+9E=
|
||||
github.com/metacubex/sing-tun v0.4.8-0.20250910070000-df2c1a4be299 h1:ytXxmMPndWV0w+yHMwVXjx6CO9AzFdZ1VE0VIjoGjZU=
|
||||
github.com/metacubex/sing-tun v0.4.8-0.20250910070000-df2c1a4be299/go.mod h1:e4AyoGUrhiKQjRio3npn87E4TmIk7X5LmeiRwZettUA=
|
||||
github.com/metacubex/sing-tun v0.4.8-0.20250912172659-89eba941fb22 h1:A/FVt2fbZ1a6elVOP/e3X/1ww7/vvzN5wdS1DJd6Ti8=
|
||||
github.com/metacubex/sing-tun v0.4.8-0.20250912172659-89eba941fb22/go.mod h1:e4AyoGUrhiKQjRio3npn87E4TmIk7X5LmeiRwZettUA=
|
||||
github.com/metacubex/sing-vmess v0.2.4-0.20250908094854-bc8e2a88b115 h1:Idk4GoB44BNN1cbjmV5aFHDXjRoV2taSgQypjCEemGM=
|
||||
github.com/metacubex/sing-vmess v0.2.4-0.20250908094854-bc8e2a88b115/go.mod h1:21R5R1u90uUvBQF0owoooEu96/SAYYD56nDrwm6nFaM=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f h1:Sr/DYKYofKHKc4GF3qkRGNuj6XA6c0eqPgEDN+VAsYU=
|
||||
|
@@ -231,6 +231,8 @@ func updateNTP(c *config.NTP) {
|
||||
c.DialerProxy,
|
||||
c.WriteToSystem,
|
||||
)
|
||||
} else {
|
||||
ntp.ReCreateNTPService("", 0, "", false)
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -3,6 +3,7 @@ package ntp
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
@@ -13,8 +14,8 @@ import (
|
||||
"github.com/metacubex/sing/common/ntp"
|
||||
)
|
||||
|
||||
var offset time.Duration
|
||||
var service *Service
|
||||
var globalSrv atomic.Pointer[Service]
|
||||
var globalMu sync.Mutex
|
||||
|
||||
type Service struct {
|
||||
server M.Socksaddr
|
||||
@@ -22,15 +23,22 @@ type Service struct {
|
||||
ticker *time.Ticker
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
mu sync.Mutex
|
||||
mu sync.RWMutex
|
||||
offset time.Duration
|
||||
syncSystemTime bool
|
||||
running bool
|
||||
}
|
||||
|
||||
func ReCreateNTPService(server string, interval time.Duration, dialerProxy string, syncSystemTime bool) {
|
||||
globalMu.Lock()
|
||||
defer globalMu.Unlock()
|
||||
service := globalSrv.Swap(nil)
|
||||
if service != nil {
|
||||
service.Stop()
|
||||
}
|
||||
if server == "" {
|
||||
return
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
service = &Service{
|
||||
server: M.ParseSocksaddr(server),
|
||||
@@ -41,6 +49,7 @@ func ReCreateNTPService(server string, interval time.Duration, dialerProxy strin
|
||||
syncSystemTime: syncSystemTime,
|
||||
}
|
||||
service.Start()
|
||||
globalSrv.Store(service)
|
||||
}
|
||||
|
||||
func (srv *Service) Start() {
|
||||
@@ -52,57 +61,62 @@ func (srv *Service) Start() {
|
||||
log.Errorln("Initialize NTP time failed: %s", err)
|
||||
return
|
||||
}
|
||||
service.running = true
|
||||
srv.running = true
|
||||
go srv.loopUpdate()
|
||||
}
|
||||
|
||||
func (srv *Service) Stop() {
|
||||
srv.mu.Lock()
|
||||
defer srv.mu.Unlock()
|
||||
if service.running {
|
||||
if srv.running {
|
||||
srv.ticker.Stop()
|
||||
srv.cancel()
|
||||
service.running = false
|
||||
srv.running = false
|
||||
}
|
||||
}
|
||||
|
||||
func (srv *Service) Running() bool {
|
||||
func (srv *Service) Offset() time.Duration {
|
||||
if srv == nil {
|
||||
return false
|
||||
return 0
|
||||
}
|
||||
srv.mu.Lock()
|
||||
defer srv.mu.Unlock()
|
||||
return srv.running
|
||||
srv.mu.RLock()
|
||||
defer srv.mu.RUnlock()
|
||||
if srv.running {
|
||||
return srv.offset
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (srv *Service) update() error {
|
||||
var response *ntp.Response
|
||||
var err error
|
||||
for i := 0; i < 3; i++ {
|
||||
if response, err = ntp.Exchange(context.Background(), srv.dialer, srv.server); err == nil {
|
||||
break
|
||||
response, err = ntp.Exchange(srv.ctx, srv.dialer, srv.server)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if i == 2 {
|
||||
return err
|
||||
offset := response.ClockOffset
|
||||
if offset > time.Duration(0) {
|
||||
log.Infoln("System clock is ahead of NTP time by %s", offset)
|
||||
} else if offset < time.Duration(0) {
|
||||
log.Infoln("System clock is behind NTP time by %s", -offset)
|
||||
}
|
||||
}
|
||||
offset = response.ClockOffset
|
||||
if offset > time.Duration(0) {
|
||||
log.Infoln("System clock is ahead of NTP time by %s", offset)
|
||||
} else if offset < time.Duration(0) {
|
||||
log.Infoln("System clock is behind NTP time by %s", -offset)
|
||||
}
|
||||
if srv.syncSystemTime {
|
||||
timeNow := response.Time
|
||||
syncErr := setSystemTime(timeNow)
|
||||
if syncErr == nil {
|
||||
log.Infoln("Sync system time success: %s", timeNow.Local().Format(ntp.TimeLayout))
|
||||
} else {
|
||||
log.Errorln("Write time to system: %s", syncErr)
|
||||
srv.syncSystemTime = false
|
||||
srv.mu.Lock()
|
||||
srv.offset = offset
|
||||
srv.mu.Unlock()
|
||||
if srv.syncSystemTime {
|
||||
timeNow := response.Time
|
||||
syncErr := setSystemTime(timeNow)
|
||||
if syncErr == nil {
|
||||
log.Infoln("Sync system time success: %s", timeNow.Local().Format(ntp.TimeLayout))
|
||||
} else {
|
||||
log.Errorln("Write time to system: %s", syncErr)
|
||||
srv.syncSystemTime = false
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
func (srv *Service) loopUpdate() {
|
||||
@@ -121,7 +135,7 @@ func (srv *Service) loopUpdate() {
|
||||
|
||||
func Now() time.Time {
|
||||
now := time.Now()
|
||||
if service.Running() && offset.Abs() > 0 {
|
||||
if offset := globalSrv.Load().Offset(); offset.Abs() > 0 {
|
||||
now = now.Add(offset)
|
||||
}
|
||||
return now
|
||||
|
@@ -2,7 +2,7 @@
|
||||
"manifest_version": 1,
|
||||
"latest": {
|
||||
"mihomo": "v1.19.13",
|
||||
"mihomo_alpha": "alpha-7061c5a",
|
||||
"mihomo_alpha": "alpha-909729c",
|
||||
"clash_rs": "v0.9.0",
|
||||
"clash_premium": "2023-09-05-gdcc8d87",
|
||||
"clash_rs_alpha": "0.9.0-alpha+sha.50f295d"
|
||||
@@ -69,5 +69,5 @@
|
||||
"linux-armv7hf": "clash-armv7-unknown-linux-gnueabihf"
|
||||
}
|
||||
},
|
||||
"updated_at": "2025-09-10T22:20:55.939Z"
|
||||
"updated_at": "2025-09-11T22:20:53.894Z"
|
||||
}
|
||||
|
@@ -13,5 +13,5 @@ func getsockopt(s, level, name uintptr, val unsafe.Pointer, vallen *uint32) (err
|
||||
if e != 0 {
|
||||
err = e
|
||||
}
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
@@ -19,5 +19,5 @@ func getsockopt(s, level, name uintptr, val unsafe.Pointer, vallen *uint32) (err
|
||||
if e != 0 {
|
||||
err = e
|
||||
}
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
@@ -42,15 +42,15 @@ func (e *UnsupportedError) Error() string {
|
||||
func (o *SocketOptions) ListenUDP() (uconn net.PacketConn, err error) {
|
||||
uconn, err = net.ListenUDP("udp", nil)
|
||||
if err != nil {
|
||||
return
|
||||
return uconn, err
|
||||
}
|
||||
err = o.applyToUDPConn(uconn.(*net.UDPConn))
|
||||
if err != nil {
|
||||
uconn.Close()
|
||||
uconn = nil
|
||||
return
|
||||
return uconn, err
|
||||
}
|
||||
return
|
||||
return uconn, err
|
||||
}
|
||||
|
||||
func (o *SocketOptions) applyToUDPConn(c *net.UDPConn) error {
|
||||
|
@@ -24,19 +24,19 @@ func init() {
|
||||
func controlUDPConn(c *net.UDPConn, cb func(fd int) error) (err error) {
|
||||
rconn, err := c.SyscallConn()
|
||||
if err != nil {
|
||||
return
|
||||
return err
|
||||
}
|
||||
cerr := rconn.Control(func(fd uintptr) {
|
||||
err = cb(int(fd))
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
return err
|
||||
}
|
||||
if cerr != nil {
|
||||
err = fmt.Errorf("failed to control fd: %w", cerr)
|
||||
return
|
||||
return err
|
||||
}
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
func bindInterfaceImpl(c *net.UDPConn, device string) error {
|
||||
|
@@ -42,12 +42,12 @@ func Test_fdControlUnixSocketImpl(t *testing.T) {
|
||||
err = controlUDPConn(conn.(*net.UDPConn), func(fd int) (err error) {
|
||||
rcvbuf, err := unix.GetsockoptInt(fd, unix.SOL_SOCKET, unix.SO_RCVBUF)
|
||||
if err != nil {
|
||||
return
|
||||
return err
|
||||
}
|
||||
// The test server called setsockopt(fd, SOL_SOCKET, SO_RCVBUF, 2500),
|
||||
// and kernel will double this value for getsockopt().
|
||||
assert.Equal(t, 5000, rcvbuf)
|
||||
return
|
||||
return err
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
@@ -1173,7 +1173,7 @@ func splitHostPort(hostPort string) (host, port string) {
|
||||
host = host[1 : len(host)-1]
|
||||
}
|
||||
|
||||
return
|
||||
return host, port
|
||||
}
|
||||
|
||||
// Marshaling interface implementations.
|
||||
@@ -1263,8 +1263,8 @@ func stringContainsCTLByte(s string) bool {
|
||||
func JoinPath(base string, elem ...string) (result string, err error) {
|
||||
url, err := Parse(base)
|
||||
if err != nil {
|
||||
return
|
||||
return result, err
|
||||
}
|
||||
result = url.JoinPath(elem...).String()
|
||||
return
|
||||
return result, err
|
||||
}
|
||||
|
@@ -68,17 +68,17 @@ func (l *LocalCertificateLoader) checkModTime() (certModTime, keyModTime time.Ti
|
||||
fi, err := os.Stat(l.CertFile)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to stat certificate file: %w", err)
|
||||
return
|
||||
return certModTime, keyModTime, err
|
||||
}
|
||||
certModTime = fi.ModTime()
|
||||
|
||||
fi, err = os.Stat(l.KeyFile)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to stat key file: %w", err)
|
||||
return
|
||||
return certModTime, keyModTime, err
|
||||
}
|
||||
keyModTime = fi.ModTime()
|
||||
return
|
||||
return certModTime, keyModTime, err
|
||||
}
|
||||
|
||||
func (l *LocalCertificateLoader) makeCache() (cache *localCertificateCache, err error) {
|
||||
@@ -86,24 +86,24 @@ func (l *LocalCertificateLoader) makeCache() (cache *localCertificateCache, err
|
||||
|
||||
c.certModTime, c.keyModTime, err = l.checkModTime()
|
||||
if err != nil {
|
||||
return
|
||||
return cache, err
|
||||
}
|
||||
|
||||
cert, err := tls.LoadX509KeyPair(l.CertFile, l.KeyFile)
|
||||
if err != nil {
|
||||
return
|
||||
return cache, err
|
||||
}
|
||||
c.certificate = &cert
|
||||
if c.certificate.Leaf == nil {
|
||||
// certificate.Leaf was left nil by tls.LoadX509KeyPair before Go 1.23
|
||||
c.certificate.Leaf, err = x509.ParseCertificate(cert.Certificate[0])
|
||||
if err != nil {
|
||||
return
|
||||
return cache, err
|
||||
}
|
||||
}
|
||||
|
||||
cache = c
|
||||
return
|
||||
return cache, err
|
||||
}
|
||||
|
||||
func (l *LocalCertificateLoader) getCertificateWithCache() (*tls.Certificate, error) {
|
||||
|
@@ -60,7 +60,7 @@ func newUDPSessionEntry(
|
||||
ExitFunc: exitFunc,
|
||||
}
|
||||
|
||||
return
|
||||
return e
|
||||
}
|
||||
|
||||
// CloseWithErr closes the session and calls ExitFunc with the given error.
|
||||
@@ -259,10 +259,9 @@ func (m *udpSessionManager) idleCleanupLoop(stopCh <-chan struct{}) {
|
||||
}
|
||||
|
||||
func (m *udpSessionManager) cleanup(idleOnly bool) {
|
||||
timeoutEntry := make([]*udpSessionEntry, 0, len(m.m))
|
||||
|
||||
// We use RLock here as we are only scanning the map, not deleting from it.
|
||||
m.mutex.RLock()
|
||||
timeoutEntry := make([]*udpSessionEntry, 0, len(m.m))
|
||||
now := time.Now()
|
||||
for _, entry := range m.m {
|
||||
if !idleOnly || now.Sub(entry.Last.Get()) > m.idleTimeout {
|
||||
@@ -289,14 +288,14 @@ func (m *udpSessionManager) feed(msg *protocol.UDPMessage) {
|
||||
// Call the hook
|
||||
err = m.io.Hook(firstMsgData, &addr)
|
||||
if err != nil {
|
||||
return
|
||||
return conn, actualAddr, err
|
||||
}
|
||||
actualAddr = addr
|
||||
// Log the event
|
||||
m.eventLogger.New(msg.SessionID, addr)
|
||||
// Dial target
|
||||
conn, err = m.io.UDP(addr)
|
||||
return
|
||||
return conn, actualAddr, err
|
||||
}
|
||||
exitFunc := func(err error) {
|
||||
// Log the event
|
||||
|
@@ -64,12 +64,12 @@ func (c *obfsPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
||||
n, addr, err = c.Conn.ReadFrom(c.readBuf)
|
||||
if n <= 0 {
|
||||
c.readMutex.Unlock()
|
||||
return
|
||||
return n, addr, err
|
||||
}
|
||||
n = c.Obfs.Deobfuscate(c.readBuf[:n], p)
|
||||
c.readMutex.Unlock()
|
||||
if n > 0 || err != nil {
|
||||
return
|
||||
return n, addr, err
|
||||
}
|
||||
// Invalid packet, try again
|
||||
}
|
||||
@@ -83,7 +83,7 @@ func (c *obfsPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||
if err == nil {
|
||||
n = len(p)
|
||||
}
|
||||
return
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (c *obfsPacketConn) Close() error {
|
||||
|
@@ -258,7 +258,7 @@ func addrExToSOCKS5Addr(addr *AddrEx) (atyp byte, dstAddr, dstPort []byte) {
|
||||
// Port
|
||||
dstPort = make([]byte, 2)
|
||||
binary.BigEndian.PutUint16(dstPort, addr.Port)
|
||||
return
|
||||
return atyp, dstAddr, dstPort
|
||||
}
|
||||
|
||||
func socks5AddrToAddrEx(atyp byte, dstAddr, dstPort []byte) *AddrEx {
|
||||
|
@@ -20,7 +20,7 @@ func splitIPv4IPv6(ips []net.IP) (ipv4, ipv6 net.IP) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
return ipv4, ipv6
|
||||
}
|
||||
|
||||
// tryParseIP tries to parse the host string in the AddrEx as an IP address.
|
||||
|
@@ -1,12 +1,12 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=r8125
|
||||
PKG_VERSION:=9.016.00
|
||||
PKG_VERSION:=9.016.01
|
||||
PKG_RELEASE:=1
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.bz2
|
||||
PKG_SOURCE_URL:=https://github.com/openwrt/rtl8125/releases/download/$(PKG_VERSION)
|
||||
PKG_HASH:=cd1955dd07d2f5a6faaa210ffc4e8af992421295a32ab6ddcfa759bed9eba922
|
||||
PKG_HASH:=5434b26500538a62541c55cd09eea099177f59bd9cc48d16969089a9bcdbbd41
|
||||
|
||||
PKG_BUILD_PARALLEL:=1
|
||||
PKG_LICENSE:=GPLv2
|
||||
|
@@ -37,7 +37,7 @@ Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
|
||||
#include <linux/netdevice.h>
|
||||
#include <linux/etherdevice.h>
|
||||
#include <linux/delay.h>
|
||||
@@ -5045,6 +5046,38 @@ rtl8125_link_down_patch(struct net_devic
|
||||
@@ -5051,6 +5052,38 @@ rtl8125_link_down_patch(struct net_devic
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
|
||||
static void
|
||||
_rtl8125_check_link_status(struct net_device *dev, unsigned int link_state)
|
||||
{
|
||||
@@ -5057,11 +5090,18 @@ _rtl8125_check_link_status(struct net_de
|
||||
@@ -5063,11 +5096,18 @@ _rtl8125_check_link_status(struct net_de
|
||||
if (link_state == R8125_LINK_STATE_ON) {
|
||||
rtl8125_link_on_patch(dev);
|
||||
|
||||
|
@@ -8,7 +8,7 @@
|
||||
#include <linux/if_vlan.h>
|
||||
#include <linux/crc32.h>
|
||||
#include <linux/interrupt.h>
|
||||
@@ -14808,6 +14809,22 @@ rtl8125_restore_phy_fuse_dout(struct rtl
|
||||
@@ -14801,6 +14802,22 @@ rtl8125_restore_phy_fuse_dout(struct rtl
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -31,7 +31,7 @@
|
||||
rtl8125_init_software_variable(struct net_device *dev)
|
||||
{
|
||||
struct rtl8125_private *tp = netdev_priv(dev);
|
||||
@@ -15309,6 +15326,7 @@ rtl8125_init_software_variable(struct ne
|
||||
@@ -15316,6 +15333,7 @@ rtl8125_init_software_variable(struct ne
|
||||
else if (tp->InitRxDescType == RX_DESC_RING_TYPE_4)
|
||||
tp->rtl8125_rx_config &= ~EnableRxDescV4_1;
|
||||
|
||||
|
@@ -1,12 +1,12 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=r8126
|
||||
PKG_VERSION:=10.015.00
|
||||
PKG_VERSION:=10.016.00
|
||||
PKG_RELEASE:=1
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.bz2
|
||||
PKG_SOURCE_URL:=https://github.com/openwrt/rtl8126/releases/download/$(PKG_VERSION)
|
||||
PKG_HASH:=fac513aa925264a95b053e7532fcda56022d29db288f6625fafee2759a8a6124
|
||||
PKG_HASH:=50c8d3d49592d2e8f372bd7ece8e7df9b50a71b055c077d42eacc42302914440
|
||||
|
||||
PKG_BUILD_PARALLEL:=1
|
||||
PKG_LICENSE:=GPLv2
|
||||
|
@@ -1,116 +0,0 @@
|
||||
--- a/src/r8126_n.c
|
||||
+++ b/src/r8126_n.c
|
||||
@@ -6929,7 +6929,11 @@ rtl8126_device_lpi_t_to_ethtool_lpi_t(st
|
||||
}
|
||||
|
||||
static int
|
||||
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(6,9,0)
|
||||
+rtl_ethtool_get_eee(struct net_device *net, struct ethtool_keee *edata)
|
||||
+#else
|
||||
rtl_ethtool_get_eee(struct net_device *net, struct ethtool_eee *edata)
|
||||
+#endif
|
||||
{
|
||||
struct rtl8126_private *tp = netdev_priv(net);
|
||||
struct ethtool_eee *eee = &tp->eee;
|
||||
@@ -6962,9 +6966,15 @@ rtl_ethtool_get_eee(struct net_device *n
|
||||
|
||||
edata->eee_enabled = !!val;
|
||||
edata->eee_active = !!(supported & adv & lp);
|
||||
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(6,9,0)
|
||||
+ ethtool_convert_legacy_u32_to_link_mode(edata->supported, supported);
|
||||
+ ethtool_convert_legacy_u32_to_link_mode(edata->advertised, adv);
|
||||
+ ethtool_convert_legacy_u32_to_link_mode(edata->lp_advertised, lp);
|
||||
+#else
|
||||
edata->supported = supported;
|
||||
edata->advertised = adv;
|
||||
edata->lp_advertised = lp;
|
||||
+#endif
|
||||
edata->tx_lpi_enabled = edata->eee_enabled;
|
||||
edata->tx_lpi_timer = tx_lpi_timer;
|
||||
|
||||
@@ -6972,11 +6982,19 @@ rtl_ethtool_get_eee(struct net_device *n
|
||||
}
|
||||
|
||||
static int
|
||||
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(6,9,0)
|
||||
+rtl_ethtool_set_eee(struct net_device *net, struct ethtool_keee *edata)
|
||||
+#else
|
||||
rtl_ethtool_set_eee(struct net_device *net, struct ethtool_eee *edata)
|
||||
+#endif
|
||||
{
|
||||
struct rtl8126_private *tp = netdev_priv(net);
|
||||
struct ethtool_eee *eee = &tp->eee;
|
||||
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(6,9,0)
|
||||
+ u32 advertising, adv;
|
||||
+#else
|
||||
u32 advertising;
|
||||
+#endif
|
||||
int rc = 0;
|
||||
|
||||
if (!HW_HAS_WRITE_PHY_MCU_RAM_CODE(tp) ||
|
||||
@@ -7008,6 +7026,18 @@ rtl_ethtool_set_eee(struct net_device *n
|
||||
*/
|
||||
|
||||
advertising = tp->advertising;
|
||||
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(6,9,0)
|
||||
+ ethtool_convert_link_mode_to_legacy_u32(&adv, edata->advertised);
|
||||
+ if (linkmode_empty(edata->advertised)) {
|
||||
+ adv = advertising & eee->supported;
|
||||
+ ethtool_convert_legacy_u32_to_link_mode(edata->advertised, adv);
|
||||
+ } else if (!linkmode_empty(edata->advertised) & ~advertising) {
|
||||
+ dev_printk(KERN_WARNING, tp_to_dev(tp), "EEE advertised %x must be a subset of autoneg advertised speeds %x\n",
|
||||
+ adv, advertising);
|
||||
+ rc = -EINVAL;
|
||||
+ goto out;
|
||||
+ }
|
||||
+#else
|
||||
if (!edata->advertised) {
|
||||
edata->advertised = advertising & eee->supported;
|
||||
} else if (edata->advertised & ~advertising) {
|
||||
@@ -7016,13 +7046,23 @@ rtl_ethtool_set_eee(struct net_device *n
|
||||
rc = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
+#endif
|
||||
|
||||
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(6,9,0)
|
||||
+ if (!linkmode_empty(edata->advertised) & ~eee->supported) {
|
||||
+ dev_printk(KERN_WARNING, tp_to_dev(tp), "EEE advertised %x must be a subset of support %x\n",
|
||||
+ adv, eee->supported);
|
||||
+ rc = -EINVAL;
|
||||
+ goto out;
|
||||
+ }
|
||||
+#else
|
||||
if (edata->advertised & ~eee->supported) {
|
||||
dev_printk(KERN_WARNING, tp_to_dev(tp), "EEE advertised %x must be a subset of support %x\n",
|
||||
edata->advertised, eee->supported);
|
||||
rc = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
+#endif
|
||||
|
||||
//tp->eee.eee_enabled = edata->eee_enabled;
|
||||
//tp->eee_adv_t = ethtool_adv_to_mmd_eee_adv_t(edata->advertised);
|
||||
@@ -7030,7 +7070,11 @@ rtl_ethtool_set_eee(struct net_device *n
|
||||
dev_printk(KERN_WARNING, tp_to_dev(tp), "EEE tx_lpi_timer %x must be a subset of support %x\n",
|
||||
edata->tx_lpi_timer, eee->tx_lpi_timer);
|
||||
|
||||
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(6,9,0)
|
||||
+ ethtool_convert_link_mode_to_legacy_u32(&eee->advertised, edata->advertised);
|
||||
+#else
|
||||
eee->advertised = edata->advertised;
|
||||
+#endif
|
||||
//eee->tx_lpi_enabled = edata->tx_lpi_enabled;
|
||||
//eee->tx_lpi_timer = edata->tx_lpi_timer;
|
||||
eee->eee_enabled = edata->eee_enabled;
|
||||
@@ -7106,8 +7150,10 @@ static const struct ethtool_ops rtl8126_
|
||||
.set_rxnfc = rtl8126_set_rxnfc,
|
||||
.get_rxfh_indir_size = rtl8126_rss_indir_size,
|
||||
.get_rxfh_key_size = rtl8126_get_rxfh_key_size,
|
||||
+#if LINUX_VERSION_CODE < KERNEL_VERSION(6,9,0)
|
||||
.get_rxfh = rtl8126_get_rxfh,
|
||||
.set_rxfh = rtl8126_set_rxfh,
|
||||
+#endif
|
||||
#endif //ENABLE_RSS_SUPPORT
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,5,0)
|
||||
#ifdef ENABLE_PTP_SUPPORT
|
@@ -17,7 +17,7 @@ Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
|
||||
|
||||
--- a/src/r8126.h
|
||||
+++ b/src/r8126.h
|
||||
@@ -1756,6 +1756,10 @@ enum RTL8126_register_content {
|
||||
@@ -1752,6 +1752,10 @@ enum RTL8126_register_content {
|
||||
LinkStatus = 0x02,
|
||||
FullDup = 0x01,
|
||||
|
||||
@@ -38,8 +38,8 @@ Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
|
||||
#include <linux/netdevice.h>
|
||||
#include <linux/etherdevice.h>
|
||||
#include <linux/delay.h>
|
||||
@@ -4661,6 +4662,40 @@ rtl8126_link_down_patch(struct net_devic
|
||||
#endif
|
||||
@@ -4410,6 +4411,40 @@ rtl8126_link_down_patch(struct net_devic
|
||||
//rtl8126_set_speed(dev, tp->autoneg, tp->speed, tp->duplex, tp->advertising);
|
||||
}
|
||||
|
||||
+static unsigned int rtl8126_phy_duplex(u32 status)
|
||||
@@ -79,7 +79,7 @@ Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
|
||||
static void
|
||||
_rtl8126_check_link_status(struct net_device *dev, unsigned int link_state)
|
||||
{
|
||||
@@ -4673,11 +4708,18 @@ _rtl8126_check_link_status(struct net_de
|
||||
@@ -4422,11 +4457,18 @@ _rtl8126_check_link_status(struct net_de
|
||||
if (link_state == R8126_LINK_STATE_ON) {
|
||||
rtl8126_link_on_patch(dev);
|
||||
|
||||
|
141
mieru/apis/common/early_conn.go
Normal file
141
mieru/apis/common/early_conn.go
Normal file
@@ -0,0 +1,141 @@
|
||||
// Copyright (C) 2025 mieru authors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/enfein/mieru/v3/apis/constant"
|
||||
"github.com/enfein/mieru/v3/apis/model"
|
||||
)
|
||||
|
||||
// EarlyConn implements net.Conn interface.
|
||||
// When the Write() method on the net.Conn is called for the first time,
|
||||
// it performs the initial handshake and writes the request to the server.
|
||||
type EarlyConn struct {
|
||||
net.Conn
|
||||
handshakeOnce sync.Once
|
||||
handshakeErr error
|
||||
handshaked chan struct{}
|
||||
netAddrSpec model.NetAddrSpec
|
||||
}
|
||||
|
||||
// NewEarlyConn creates a new EarlyConn.
|
||||
func NewEarlyConn(conn net.Conn, netAddrSpec model.NetAddrSpec) *EarlyConn {
|
||||
return &EarlyConn{
|
||||
Conn: conn,
|
||||
handshaked: make(chan struct{}),
|
||||
netAddrSpec: netAddrSpec,
|
||||
}
|
||||
}
|
||||
|
||||
// Read will block until a message is received or an error occurs.
|
||||
// It waits for the handshake to complete.
|
||||
func (c *EarlyConn) Read(b []byte) (n int, err error) {
|
||||
<-c.handshaked
|
||||
if c.handshakeErr != nil {
|
||||
return 0, c.handshakeErr
|
||||
}
|
||||
return c.Conn.Read(b)
|
||||
}
|
||||
|
||||
// Write will block until the message is sent or an error occurs.
|
||||
// It triggers the initial handshake if it has not been performed yet,
|
||||
// and sends the data in the same packet as the handshake request.
|
||||
func (c *EarlyConn) Write(b []byte) (n int, err error) {
|
||||
var writtenDuringHandshake bool
|
||||
c.handshakeOnce.Do(func() {
|
||||
c.handshakeErr = c.doHandshakeAndWrite(b)
|
||||
close(c.handshaked)
|
||||
writtenDuringHandshake = true
|
||||
})
|
||||
|
||||
if c.handshakeErr != nil {
|
||||
return 0, c.handshakeErr
|
||||
}
|
||||
if writtenDuringHandshake {
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
return c.Conn.Write(b)
|
||||
}
|
||||
|
||||
func (c *EarlyConn) Close() error {
|
||||
c.handshakeOnce.Do(func() {
|
||||
close(c.handshaked) // unblock Read() method
|
||||
})
|
||||
return c.Conn.Close()
|
||||
}
|
||||
|
||||
// NeedHandshake returns true if the handshake has not been performed yet.
|
||||
func (c *EarlyConn) NeedHandshake() bool {
|
||||
select {
|
||||
case <-c.handshaked:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func (c *EarlyConn) doHandshakeAndWrite(b []byte) error {
|
||||
var req bytes.Buffer
|
||||
isTCP := strings.HasPrefix(c.netAddrSpec.Network(), "tcp")
|
||||
isUDP := strings.HasPrefix(c.netAddrSpec.Network(), "udp")
|
||||
|
||||
if isTCP {
|
||||
req.Write([]byte{constant.Socks5Version, constant.Socks5ConnectCmd, 0})
|
||||
} else if isUDP {
|
||||
req.Write([]byte{constant.Socks5Version, constant.Socks5UDPAssociateCmd, 0})
|
||||
} else {
|
||||
return fmt.Errorf("unsupported network type %s", c.netAddrSpec.Network())
|
||||
}
|
||||
|
||||
if err := c.netAddrSpec.WriteToSocks5(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(b) > 0 {
|
||||
req.Write(b)
|
||||
}
|
||||
|
||||
if _, err := c.Conn.Write(req.Bytes()); err != nil {
|
||||
return fmt.Errorf("failed to write socks5 connection request to the server: %w", err)
|
||||
}
|
||||
|
||||
c.Conn.SetReadDeadline(time.Now().Add(10 * time.Second))
|
||||
defer func() {
|
||||
c.Conn.SetReadDeadline(time.Time{})
|
||||
}()
|
||||
|
||||
resp := make([]byte, 3)
|
||||
if _, err := io.ReadFull(c.Conn, resp); err != nil {
|
||||
return fmt.Errorf("failed to read socks5 connection response from the server: %w", err)
|
||||
}
|
||||
var respAddr model.NetAddrSpec
|
||||
if err := respAddr.ReadFromSocks5(c.Conn); err != nil {
|
||||
return fmt.Errorf("failed to read socks5 connection address response from the server: %w", err)
|
||||
}
|
||||
if resp[1] != 0 {
|
||||
return fmt.Errorf("server returned socks5 error code %d", resp[1])
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
106
mieru/apis/common/early_conn_test.go
Normal file
106
mieru/apis/common/early_conn_test.go
Normal file
@@ -0,0 +1,106 @@
|
||||
// Copyright (C) 2025 mieru authors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package common_test
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/enfein/mieru/v3/apis/common"
|
||||
"github.com/enfein/mieru/v3/apis/constant"
|
||||
"github.com/enfein/mieru/v3/apis/model"
|
||||
"github.com/enfein/mieru/v3/pkg/testtool"
|
||||
)
|
||||
|
||||
func TestEarlyConn(t *testing.T) {
|
||||
clientConn, serverConn := testtool.BufPipe()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
|
||||
// Run a fake socks5 server.
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
defer serverConn.Close()
|
||||
|
||||
// Read and discard socks5 request header.
|
||||
reqHeader := make([]byte, 3)
|
||||
if _, err := io.ReadFull(serverConn, reqHeader); err != nil {
|
||||
t.Errorf("server: failed to read request header: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Read destination address.
|
||||
var addr model.NetAddrSpec
|
||||
if err := addr.ReadFromSocks5(serverConn); err != nil {
|
||||
t.Errorf("server: failed to read destination address: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Send reply using a dummy IPv4 address 0.0.0.0:0.
|
||||
reply := []byte{constant.Socks5Version, 0, 0, constant.Socks5IPv4Address, 0, 0, 0, 0, 0, 0}
|
||||
if _, err := serverConn.Write(reply); err != nil {
|
||||
t.Errorf("server: failed to write reply: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Read client data ("ping").
|
||||
ping := make([]byte, 4)
|
||||
if _, err := io.ReadFull(serverConn, ping); err != nil {
|
||||
t.Errorf("server: failed to read data: %v", err)
|
||||
return
|
||||
}
|
||||
if string(ping) != "ping" {
|
||||
t.Errorf("server: expected client to send 'ping', got '%s'", string(ping))
|
||||
return
|
||||
}
|
||||
|
||||
// Send server data ("pong").
|
||||
if _, err := serverConn.Write([]byte("pong")); err != nil {
|
||||
t.Errorf("server: failed to write data: %v", err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
// Create client early connection.
|
||||
target := model.NetAddrSpec{
|
||||
AddrSpec: model.AddrSpec{
|
||||
FQDN: "example.com",
|
||||
Port: 80,
|
||||
},
|
||||
Net: "tcp",
|
||||
}
|
||||
conn := common.NewEarlyConn(clientConn, target)
|
||||
defer conn.Close()
|
||||
|
||||
// The first write triggers the handshake.
|
||||
if _, err := conn.Write([]byte("ping")); err != nil {
|
||||
t.Fatalf("client: failed to write data: %v", err)
|
||||
}
|
||||
|
||||
// The server should respond with "pong" after the handshake is complete.
|
||||
pong := make([]byte, 4)
|
||||
if _, err := io.ReadFull(conn, pong); err != nil {
|
||||
t.Fatalf("client: failed to read data: %v", err)
|
||||
}
|
||||
|
||||
if string(pong) != "pong" {
|
||||
t.Fatalf("client: expected server to send 'pong', got '%s'", string(pong))
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/component/proxydialer"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/ntp"
|
||||
gost "github.com/metacubex/mihomo/transport/gost-plugin"
|
||||
"github.com/metacubex/mihomo/transport/restls"
|
||||
obfs "github.com/metacubex/mihomo/transport/simple-obfs"
|
||||
@@ -251,8 +252,9 @@ func (ss *ShadowSocks) SupportUOT() bool {
|
||||
|
||||
func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||
method, err := shadowsocks.CreateMethod(context.Background(), option.Cipher, shadowsocks.MethodOptions{
|
||||
method, err := shadowsocks.CreateMethod(option.Cipher, shadowsocks.MethodOptions{
|
||||
Password: option.Password,
|
||||
TimeFunc: ntp.Now,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ss %s cipher: %s initialize error: %w", addr, option.Cipher, err)
|
||||
|
@@ -331,15 +331,22 @@ func (cp *CompatibleProvider) Close() error {
|
||||
}
|
||||
|
||||
func NewProxiesParser(filter string, excludeFilter string, excludeType string, dialerProxy string, override OverrideSchema) (resource.Parser[[]C.Proxy], error) {
|
||||
excludeFilterReg, err := regexp2.Compile(excludeFilter, regexp2.None)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid excludeFilter regex: %w", err)
|
||||
}
|
||||
var excludeTypeArray []string
|
||||
if excludeType != "" {
|
||||
excludeTypeArray = strings.Split(excludeType, "|")
|
||||
}
|
||||
|
||||
var excludeFilterRegs []*regexp2.Regexp
|
||||
if excludeFilter != "" {
|
||||
for _, excludeFilter := range strings.Split(excludeFilter, "`") {
|
||||
excludeFilterReg, err := regexp2.Compile(excludeFilter, regexp2.None)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid excludeFilter regex: %w", err)
|
||||
}
|
||||
excludeFilterRegs = append(excludeFilterRegs, excludeFilterReg)
|
||||
}
|
||||
}
|
||||
|
||||
var filterRegs []*regexp2.Regexp
|
||||
for _, filter := range strings.Split(filter, "`") {
|
||||
filterReg, err := regexp2.Compile(filter, regexp2.None)
|
||||
@@ -367,8 +374,9 @@ func NewProxiesParser(filter string, excludeFilter string, excludeType string, d
|
||||
proxies := []C.Proxy{}
|
||||
proxiesSet := map[string]struct{}{}
|
||||
for _, filterReg := range filterRegs {
|
||||
LOOP1:
|
||||
for idx, mapping := range schema.Proxies {
|
||||
if nil != excludeTypeArray && len(excludeTypeArray) > 0 {
|
||||
if len(excludeTypeArray) > 0 {
|
||||
mType, ok := mapping["type"]
|
||||
if !ok {
|
||||
continue
|
||||
@@ -377,18 +385,11 @@ func NewProxiesParser(filter string, excludeFilter string, excludeType string, d
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
flag := false
|
||||
for i := range excludeTypeArray {
|
||||
if strings.EqualFold(pType, excludeTypeArray[i]) {
|
||||
flag = true
|
||||
break
|
||||
for _, excludeType := range excludeTypeArray {
|
||||
if strings.EqualFold(pType, excludeType) {
|
||||
continue LOOP1
|
||||
}
|
||||
|
||||
}
|
||||
if flag {
|
||||
continue
|
||||
}
|
||||
|
||||
}
|
||||
mName, ok := mapping["name"]
|
||||
if !ok {
|
||||
@@ -398,9 +399,11 @@ func NewProxiesParser(filter string, excludeFilter string, excludeType string, d
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if len(excludeFilter) > 0 {
|
||||
if mat, _ := excludeFilterReg.MatchString(name); mat {
|
||||
continue
|
||||
if len(excludeFilterRegs) > 0 {
|
||||
for _, excludeFilterReg := range excludeFilterRegs {
|
||||
if mat, _ := excludeFilterReg.MatchString(name); mat {
|
||||
continue LOOP1
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(filter) > 0 {
|
||||
|
@@ -24,13 +24,13 @@ require (
|
||||
github.com/metacubex/quic-go v0.54.1-0.20250730114134-a1ae705fe295
|
||||
github.com/metacubex/randv2 v0.2.0
|
||||
github.com/metacubex/restls-client-go v0.1.7
|
||||
github.com/metacubex/sing v0.5.6-0.20250904143031-f1a62fab1489
|
||||
github.com/metacubex/sing v0.5.6-0.20250912172506-82b42a287539
|
||||
github.com/metacubex/sing-mux v0.3.3
|
||||
github.com/metacubex/sing-quic v0.0.0-20250909002258-06122df8f231
|
||||
github.com/metacubex/sing-shadowsocks v0.2.12
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.6
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.7
|
||||
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2
|
||||
github.com/metacubex/sing-tun v0.4.8-0.20250910070000-df2c1a4be299
|
||||
github.com/metacubex/sing-tun v0.4.8-0.20250912172659-89eba941fb22
|
||||
github.com/metacubex/sing-vmess v0.2.4-0.20250908094854-bc8e2a88b115
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f
|
||||
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee
|
||||
|
@@ -117,20 +117,20 @@ github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFq
|
||||
github.com/metacubex/restls-client-go v0.1.7 h1:eCwiXCTQb5WJu9IlgYvDBA1OgrINv58dEe7hcN5H15k=
|
||||
github.com/metacubex/restls-client-go v0.1.7/go.mod h1:BN/U52vPw7j8VTSh2vleD/MnmVKCov84mS5VcjVHH4g=
|
||||
github.com/metacubex/sing v0.5.2/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
|
||||
github.com/metacubex/sing v0.5.6-0.20250904143031-f1a62fab1489 h1:jKOFzhHTbxqhCluh5ONxjDe6CJMNHvgniXAf1RWuzlE=
|
||||
github.com/metacubex/sing v0.5.6-0.20250904143031-f1a62fab1489/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
|
||||
github.com/metacubex/sing v0.5.6-0.20250912172506-82b42a287539 h1:ArXEdw7JvbL3dLc3D7kBGTDmuBBI/sNIyR3O4MlfPH8=
|
||||
github.com/metacubex/sing v0.5.6-0.20250912172506-82b42a287539/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
|
||||
github.com/metacubex/sing-mux v0.3.3 h1:oqCbUAJgTLsa71vfo8otW8xIhrDfbc/Y2rmtW34sQjg=
|
||||
github.com/metacubex/sing-mux v0.3.3/go.mod h1:3rt1soewn0O6j89GCLmwAQFsq257u0jf2zQSPhTL3Bw=
|
||||
github.com/metacubex/sing-quic v0.0.0-20250909002258-06122df8f231 h1:dGvo7UahC/gYBQNBoictr14baJzBjAKUAorP63QFFtg=
|
||||
github.com/metacubex/sing-quic v0.0.0-20250909002258-06122df8f231/go.mod h1:B60FxaPHjR1SeQB0IiLrgwgvKsaoASfOWdiqhLjmMGA=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.12 h1:Wqzo8bYXrK5aWqxu/TjlTnYZzAKtKsaFQBdr6IHFaBE=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.12/go.mod h1:2e5EIaw0rxKrm1YTRmiMnDulwbGxH9hAFlrwQLQMQkU=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.6 h1:ZR1kYT0f0Vi64iQSS09OdhFfppiNkh7kjgRdMm0SB98=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.6/go.mod h1:vOEbfKC60txi0ca+yUlqEwOGc3Obl6cnSgx9Gf45KjE=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.7 h1:hSuuc0YpsfiqYqt1o+fP4m34BQz4e6wVj3PPBVhor3A=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.7/go.mod h1:vOEbfKC60txi0ca+yUlqEwOGc3Obl6cnSgx9Gf45KjE=
|
||||
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 h1:gXU+MYPm7Wme3/OAY2FFzVq9d9GxPHOqu5AQfg/ddhI=
|
||||
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2/go.mod h1:mbfboaXauKJNIHJYxQRa+NJs4JU9NZfkA+I33dS2+9E=
|
||||
github.com/metacubex/sing-tun v0.4.8-0.20250910070000-df2c1a4be299 h1:ytXxmMPndWV0w+yHMwVXjx6CO9AzFdZ1VE0VIjoGjZU=
|
||||
github.com/metacubex/sing-tun v0.4.8-0.20250910070000-df2c1a4be299/go.mod h1:e4AyoGUrhiKQjRio3npn87E4TmIk7X5LmeiRwZettUA=
|
||||
github.com/metacubex/sing-tun v0.4.8-0.20250912172659-89eba941fb22 h1:A/FVt2fbZ1a6elVOP/e3X/1ww7/vvzN5wdS1DJd6Ti8=
|
||||
github.com/metacubex/sing-tun v0.4.8-0.20250912172659-89eba941fb22/go.mod h1:e4AyoGUrhiKQjRio3npn87E4TmIk7X5LmeiRwZettUA=
|
||||
github.com/metacubex/sing-vmess v0.2.4-0.20250908094854-bc8e2a88b115 h1:Idk4GoB44BNN1cbjmV5aFHDXjRoV2taSgQypjCEemGM=
|
||||
github.com/metacubex/sing-vmess v0.2.4-0.20250908094854-bc8e2a88b115/go.mod h1:21R5R1u90uUvBQF0owoooEu96/SAYYD56nDrwm6nFaM=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f h1:Sr/DYKYofKHKc4GF3qkRGNuj6XA6c0eqPgEDN+VAsYU=
|
||||
|
@@ -231,6 +231,8 @@ func updateNTP(c *config.NTP) {
|
||||
c.DialerProxy,
|
||||
c.WriteToSystem,
|
||||
)
|
||||
} else {
|
||||
ntp.ReCreateNTPService("", 0, "", false)
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -3,6 +3,7 @@ package ntp
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
@@ -13,8 +14,8 @@ import (
|
||||
"github.com/metacubex/sing/common/ntp"
|
||||
)
|
||||
|
||||
var offset time.Duration
|
||||
var service *Service
|
||||
var globalSrv atomic.Pointer[Service]
|
||||
var globalMu sync.Mutex
|
||||
|
||||
type Service struct {
|
||||
server M.Socksaddr
|
||||
@@ -22,15 +23,22 @@ type Service struct {
|
||||
ticker *time.Ticker
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
mu sync.Mutex
|
||||
mu sync.RWMutex
|
||||
offset time.Duration
|
||||
syncSystemTime bool
|
||||
running bool
|
||||
}
|
||||
|
||||
func ReCreateNTPService(server string, interval time.Duration, dialerProxy string, syncSystemTime bool) {
|
||||
globalMu.Lock()
|
||||
defer globalMu.Unlock()
|
||||
service := globalSrv.Swap(nil)
|
||||
if service != nil {
|
||||
service.Stop()
|
||||
}
|
||||
if server == "" {
|
||||
return
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
service = &Service{
|
||||
server: M.ParseSocksaddr(server),
|
||||
@@ -41,6 +49,7 @@ func ReCreateNTPService(server string, interval time.Duration, dialerProxy strin
|
||||
syncSystemTime: syncSystemTime,
|
||||
}
|
||||
service.Start()
|
||||
globalSrv.Store(service)
|
||||
}
|
||||
|
||||
func (srv *Service) Start() {
|
||||
@@ -52,57 +61,62 @@ func (srv *Service) Start() {
|
||||
log.Errorln("Initialize NTP time failed: %s", err)
|
||||
return
|
||||
}
|
||||
service.running = true
|
||||
srv.running = true
|
||||
go srv.loopUpdate()
|
||||
}
|
||||
|
||||
func (srv *Service) Stop() {
|
||||
srv.mu.Lock()
|
||||
defer srv.mu.Unlock()
|
||||
if service.running {
|
||||
if srv.running {
|
||||
srv.ticker.Stop()
|
||||
srv.cancel()
|
||||
service.running = false
|
||||
srv.running = false
|
||||
}
|
||||
}
|
||||
|
||||
func (srv *Service) Running() bool {
|
||||
func (srv *Service) Offset() time.Duration {
|
||||
if srv == nil {
|
||||
return false
|
||||
return 0
|
||||
}
|
||||
srv.mu.Lock()
|
||||
defer srv.mu.Unlock()
|
||||
return srv.running
|
||||
srv.mu.RLock()
|
||||
defer srv.mu.RUnlock()
|
||||
if srv.running {
|
||||
return srv.offset
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (srv *Service) update() error {
|
||||
var response *ntp.Response
|
||||
var err error
|
||||
for i := 0; i < 3; i++ {
|
||||
if response, err = ntp.Exchange(context.Background(), srv.dialer, srv.server); err == nil {
|
||||
break
|
||||
response, err = ntp.Exchange(srv.ctx, srv.dialer, srv.server)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if i == 2 {
|
||||
return err
|
||||
offset := response.ClockOffset
|
||||
if offset > time.Duration(0) {
|
||||
log.Infoln("System clock is ahead of NTP time by %s", offset)
|
||||
} else if offset < time.Duration(0) {
|
||||
log.Infoln("System clock is behind NTP time by %s", -offset)
|
||||
}
|
||||
}
|
||||
offset = response.ClockOffset
|
||||
if offset > time.Duration(0) {
|
||||
log.Infoln("System clock is ahead of NTP time by %s", offset)
|
||||
} else if offset < time.Duration(0) {
|
||||
log.Infoln("System clock is behind NTP time by %s", -offset)
|
||||
}
|
||||
if srv.syncSystemTime {
|
||||
timeNow := response.Time
|
||||
syncErr := setSystemTime(timeNow)
|
||||
if syncErr == nil {
|
||||
log.Infoln("Sync system time success: %s", timeNow.Local().Format(ntp.TimeLayout))
|
||||
} else {
|
||||
log.Errorln("Write time to system: %s", syncErr)
|
||||
srv.syncSystemTime = false
|
||||
srv.mu.Lock()
|
||||
srv.offset = offset
|
||||
srv.mu.Unlock()
|
||||
if srv.syncSystemTime {
|
||||
timeNow := response.Time
|
||||
syncErr := setSystemTime(timeNow)
|
||||
if syncErr == nil {
|
||||
log.Infoln("Sync system time success: %s", timeNow.Local().Format(ntp.TimeLayout))
|
||||
} else {
|
||||
log.Errorln("Write time to system: %s", syncErr)
|
||||
srv.syncSystemTime = false
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
func (srv *Service) loopUpdate() {
|
||||
@@ -121,7 +135,7 @@ func (srv *Service) loopUpdate() {
|
||||
|
||||
func Now() time.Time {
|
||||
now := time.Now()
|
||||
if service.Running() && offset.Abs() > 0 {
|
||||
if offset := globalSrv.Load().Offset(); offset.Abs() > 0 {
|
||||
now = now.Add(offset)
|
||||
}
|
||||
return now
|
||||
|
@@ -10,11 +10,11 @@ include $(TOPDIR)/rules.mk
|
||||
PKG_ARCH_quickstart:=$(ARCH)
|
||||
|
||||
PKG_NAME:=quickstart
|
||||
PKG_VERSION:=0.11.6
|
||||
PKG_VERSION:=0.11.7
|
||||
PKG_RELEASE:=1
|
||||
PKG_SOURCE:=$(PKG_NAME)-binary-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://github.com/linkease/istore-packages/releases/download/prebuilt/
|
||||
PKG_HASH:=1b3d206156b615cc227b3936d4e2cabba429f205b8cbd4d1a297ebc6870efce6
|
||||
PKG_HASH:=8d70a4e8a6c17c767d3507037567185aa6f80e388b22a2eede9d2e253fbad233
|
||||
|
||||
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-binary-$(PKG_VERSION)
|
||||
|
||||
|
@@ -55,22 +55,18 @@ local api = require "luci.passwall.api"
|
||||
"gfwlist_update","chnroute_update","chnroute6_update",
|
||||
"chnlist_update","geoip_update","geosite_update"
|
||||
];
|
||||
const targetNode = document.querySelector('form') || document.body;
|
||||
const observer = new MutationObserver(() => {
|
||||
const bindFlags = () => {
|
||||
let allBound = true;
|
||||
flags.forEach(flag => {
|
||||
const orig = Array.from(document.querySelectorAll(`input[name$=".${flag}"]`)).find(i => i.type === 'checkbox');
|
||||
if (!orig) {
|
||||
return;
|
||||
}
|
||||
if (!orig) { allBound = false; return; }
|
||||
// 隐藏最外层 div
|
||||
const wrapper = orig.closest('.cbi-value');
|
||||
if (wrapper && wrapper.style.display !== 'none') {
|
||||
wrapper.style.display = 'none';
|
||||
}
|
||||
const custom = document.querySelector(`.cbi-input-checkbox[name="${flag.replace('_update','')}"]`);
|
||||
if (!custom) {
|
||||
return;
|
||||
}
|
||||
if (!custom) { allBound = false; return; }
|
||||
custom.checked = orig.checked;
|
||||
// 自定义选择框与原生Flag双向绑定
|
||||
if (!custom._binded) {
|
||||
@@ -84,8 +80,13 @@ local api = require "luci.passwall.api"
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
observer.observe(targetNode, { childList: true, subtree: true });
|
||||
return allBound;
|
||||
};
|
||||
const target = document.querySelector('form') || document.body;
|
||||
const observer = new MutationObserver(() => bindFlags() ? observer.disconnect() : 0);
|
||||
observer.observe(target, { childList: true, subtree: true });
|
||||
const timer = setInterval(() => bindFlags() ? (clearInterval(timer), observer.disconnect()) : 0, 300);
|
||||
setTimeout(() => { clearInterval(timer); observer.disconnect(); }, 5000);
|
||||
});
|
||||
|
||||
function update_rules(btn) {
|
||||
|
11
sing-box/.github/workflows/build.yml
vendored
11
sing-box/.github/workflows/build.yml
vendored
@@ -432,7 +432,8 @@ jobs:
|
||||
SERVICE_ACCOUNT_CREDENTIALS: ${{ secrets.SERVICE_ACCOUNT_CREDENTIALS }}
|
||||
build_apple:
|
||||
name: Build Apple clients
|
||||
runs-on: macos-15
|
||||
runs-on: macos-26
|
||||
if: false
|
||||
needs:
|
||||
- calculate_version
|
||||
strategy:
|
||||
@@ -479,14 +480,6 @@ jobs:
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.25.1
|
||||
- name: Setup Xcode stable
|
||||
if: matrix.if && github.ref == 'refs/heads/main-next'
|
||||
run: |-
|
||||
sudo xcode-select -s /Applications/Xcode_16.4.app
|
||||
- name: Setup Xcode beta
|
||||
if: matrix.if && github.ref == 'refs/heads/dev-next'
|
||||
run: |-
|
||||
sudo xcode-select -s /Applications/Xcode_16.4.app
|
||||
- name: Set tag
|
||||
if: matrix.if
|
||||
run: |-
|
||||
|
@@ -17,6 +17,10 @@ build:
|
||||
export GOTOOLCHAIN=local && \
|
||||
go build $(MAIN_PARAMS) $(MAIN)
|
||||
|
||||
race:
|
||||
export GOTOOLCHAIN=local && \
|
||||
go build -race $(MAIN_PARAMS) $(MAIN)
|
||||
|
||||
ci_build:
|
||||
export GOTOOLCHAIN=local && \
|
||||
go build $(PARAMS) $(MAIN) && \
|
||||
|
@@ -1,3 +1,3 @@
|
||||
VERSION_CODE=564
|
||||
VERSION_NAME=1.12.5
|
||||
VERSION_CODE=566
|
||||
VERSION_NAME=1.12.6
|
||||
GO_VERSION=go1.25.1
|
||||
|
@@ -7,6 +7,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/sagernet/fswatch"
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
@@ -21,6 +22,7 @@ import (
|
||||
var _ adapter.CertificateStore = (*Store)(nil)
|
||||
|
||||
type Store struct {
|
||||
access sync.RWMutex
|
||||
systemPool *x509.CertPool
|
||||
currentPool *x509.CertPool
|
||||
certificate string
|
||||
@@ -115,10 +117,14 @@ func (s *Store) Close() error {
|
||||
}
|
||||
|
||||
func (s *Store) Pool() *x509.CertPool {
|
||||
s.access.RLock()
|
||||
defer s.access.RUnlock()
|
||||
return s.currentPool
|
||||
}
|
||||
|
||||
func (s *Store) update() error {
|
||||
s.access.Lock()
|
||||
defer s.access.Unlock()
|
||||
var currentPool *x509.CertPool
|
||||
if s.systemPool == nil {
|
||||
currentPool = x509.NewCertPool()
|
||||
|
@@ -69,11 +69,7 @@ func parseECHServerConfig(ctx context.Context, options option.InboundTLSOptions,
|
||||
} else {
|
||||
return E.New("missing ECH keys")
|
||||
}
|
||||
block, rest := pem.Decode(echKey)
|
||||
if block == nil || block.Type != "ECH KEYS" || len(rest) > 0 {
|
||||
return E.New("invalid ECH keys pem")
|
||||
}
|
||||
echKeys, err := UnmarshalECHKeys(block.Bytes)
|
||||
echKeys, err := parseECHKeys(echKey)
|
||||
if err != nil {
|
||||
return E.Cause(err, "parse ECH keys")
|
||||
}
|
||||
@@ -85,21 +81,29 @@ func parseECHServerConfig(ctx context.Context, options option.InboundTLSOptions,
|
||||
return nil
|
||||
}
|
||||
|
||||
func reloadECHKeys(echKeyPath string, tlsConfig *tls.Config) error {
|
||||
echKey, err := os.ReadFile(echKeyPath)
|
||||
func (c *STDServerConfig) setECHServerConfig(echKey []byte) error {
|
||||
echKeys, err := parseECHKeys(echKey)
|
||||
if err != nil {
|
||||
return E.Cause(err, "reload ECH keys from ", echKeyPath)
|
||||
return err
|
||||
}
|
||||
c.access.Lock()
|
||||
config := c.config.Clone()
|
||||
config.EncryptedClientHelloKeys = echKeys
|
||||
c.config = config
|
||||
c.access.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseECHKeys(echKey []byte) ([]tls.EncryptedClientHelloKey, error) {
|
||||
block, _ := pem.Decode(echKey)
|
||||
if block == nil || block.Type != "ECH KEYS" {
|
||||
return E.New("invalid ECH keys pem")
|
||||
return nil, E.New("invalid ECH keys pem")
|
||||
}
|
||||
echKeys, err := UnmarshalECHKeys(block.Bytes)
|
||||
if err != nil {
|
||||
return E.Cause(err, "parse ECH keys")
|
||||
return nil, E.Cause(err, "parse ECH keys")
|
||||
}
|
||||
tlsConfig.EncryptedClientHelloKeys = echKeys
|
||||
return nil
|
||||
return echKeys, nil
|
||||
}
|
||||
|
||||
type ECHClientConfig struct {
|
||||
|
@@ -18,6 +18,6 @@ func parseECHServerConfig(ctx context.Context, options option.InboundTLSOptions,
|
||||
return E.New("ECH requires go1.24, please recompile your binary.")
|
||||
}
|
||||
|
||||
func reloadECHKeys(echKeyPath string, tlsConfig *tls.Config) error {
|
||||
return E.New("ECH requires go1.24, please recompile your binary.")
|
||||
func (c *STDServerConfig) setECHServerConfig(echKey []byte) error {
|
||||
panic("unreachable")
|
||||
}
|
||||
|
@@ -6,6 +6,7 @@ import (
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/fswatch"
|
||||
@@ -21,6 +22,7 @@ import (
|
||||
var errInsecureUnused = E.New("tls: insecure unused")
|
||||
|
||||
type STDServerConfig struct {
|
||||
access sync.RWMutex
|
||||
config *tls.Config
|
||||
logger log.Logger
|
||||
acmeService adapter.SimpleLifecycle
|
||||
@@ -33,14 +35,22 @@ type STDServerConfig struct {
|
||||
}
|
||||
|
||||
func (c *STDServerConfig) ServerName() string {
|
||||
c.access.RLock()
|
||||
defer c.access.RUnlock()
|
||||
return c.config.ServerName
|
||||
}
|
||||
|
||||
func (c *STDServerConfig) SetServerName(serverName string) {
|
||||
c.config.ServerName = serverName
|
||||
c.access.Lock()
|
||||
defer c.access.Unlock()
|
||||
config := c.config.Clone()
|
||||
config.ServerName = serverName
|
||||
c.config = config
|
||||
}
|
||||
|
||||
func (c *STDServerConfig) NextProtos() []string {
|
||||
c.access.RLock()
|
||||
defer c.access.RUnlock()
|
||||
if c.acmeService != nil && len(c.config.NextProtos) > 1 && c.config.NextProtos[0] == ACMETLS1Protocol {
|
||||
return c.config.NextProtos[1:]
|
||||
} else {
|
||||
@@ -49,11 +59,15 @@ func (c *STDServerConfig) NextProtos() []string {
|
||||
}
|
||||
|
||||
func (c *STDServerConfig) SetNextProtos(nextProto []string) {
|
||||
c.access.Lock()
|
||||
defer c.access.Unlock()
|
||||
config := c.config.Clone()
|
||||
if c.acmeService != nil && len(c.config.NextProtos) > 1 && c.config.NextProtos[0] == ACMETLS1Protocol {
|
||||
c.config.NextProtos = append(c.config.NextProtos[:1], nextProto...)
|
||||
config.NextProtos = append(c.config.NextProtos[:1], nextProto...)
|
||||
} else {
|
||||
c.config.NextProtos = nextProto
|
||||
config.NextProtos = nextProto
|
||||
}
|
||||
c.config = config
|
||||
}
|
||||
|
||||
func (c *STDServerConfig) STDConfig() (*STDConfig, error) {
|
||||
@@ -78,9 +92,6 @@ func (c *STDServerConfig) Start() error {
|
||||
if c.acmeService != nil {
|
||||
return c.acmeService.Start()
|
||||
} else {
|
||||
if c.certificatePath == "" && c.keyPath == "" {
|
||||
return nil
|
||||
}
|
||||
err := c.startWatcher()
|
||||
if err != nil {
|
||||
c.logger.Warn("create fsnotify watcher: ", err)
|
||||
@@ -100,6 +111,9 @@ func (c *STDServerConfig) startWatcher() error {
|
||||
if c.echKeyPath != "" {
|
||||
watchPath = append(watchPath, c.echKeyPath)
|
||||
}
|
||||
if len(watchPath) == 0 {
|
||||
return nil
|
||||
}
|
||||
watcher, err := fswatch.NewWatcher(fswatch.Options{
|
||||
Path: watchPath,
|
||||
Callback: func(path string) {
|
||||
@@ -139,10 +153,18 @@ func (c *STDServerConfig) certificateUpdated(path string) error {
|
||||
if err != nil {
|
||||
return E.Cause(err, "reload key pair")
|
||||
}
|
||||
c.config.Certificates = []tls.Certificate{keyPair}
|
||||
c.access.Lock()
|
||||
config := c.config.Clone()
|
||||
config.Certificates = []tls.Certificate{keyPair}
|
||||
c.config = config
|
||||
c.access.Unlock()
|
||||
c.logger.Info("reloaded TLS certificate")
|
||||
} else if path == c.echKeyPath {
|
||||
err := reloadECHKeys(c.echKeyPath, c.config)
|
||||
echKey, err := os.ReadFile(c.echKeyPath)
|
||||
if err != nil {
|
||||
return E.Cause(err, "reload ECH keys from ", c.echKeyPath)
|
||||
}
|
||||
err = c.setECHServerConfig(echKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -263,7 +285,7 @@ func NewSTDServer(ctx context.Context, logger log.ContextLogger, options option.
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
var config ServerConfig = &STDServerConfig{
|
||||
serverConfig := &STDServerConfig{
|
||||
config: tlsConfig,
|
||||
logger: logger,
|
||||
acmeService: acmeService,
|
||||
@@ -273,6 +295,12 @@ func NewSTDServer(ctx context.Context, logger log.ContextLogger, options option.
|
||||
keyPath: options.KeyPath,
|
||||
echKeyPath: echKeyPath,
|
||||
}
|
||||
serverConfig.config.GetConfigForClient = func(info *tls.ClientHelloInfo) (*tls.Config, error) {
|
||||
serverConfig.access.Lock()
|
||||
defer serverConfig.access.Unlock()
|
||||
return serverConfig.config, nil
|
||||
}
|
||||
var config ServerConfig = serverConfig
|
||||
if options.KernelTx || options.KernelRx {
|
||||
if !C.IsLinux {
|
||||
return nil, E.New("kTLS is only supported on Linux")
|
||||
|
@@ -46,15 +46,15 @@ func (s *HistoryStorage) LoadURLTestHistory(tag string) *adapter.URLTestHistory
|
||||
func (s *HistoryStorage) DeleteURLTestHistory(tag string) {
|
||||
s.access.Lock()
|
||||
delete(s.delayHistory, tag)
|
||||
s.access.Unlock()
|
||||
s.notifyUpdated()
|
||||
s.access.Unlock()
|
||||
}
|
||||
|
||||
func (s *HistoryStorage) StoreURLTestHistory(tag string, history *adapter.URLTestHistory) {
|
||||
s.access.Lock()
|
||||
s.delayHistory[tag] = history
|
||||
s.access.Unlock()
|
||||
s.notifyUpdated()
|
||||
s.access.Unlock()
|
||||
}
|
||||
|
||||
func (s *HistoryStorage) notifyUpdated() {
|
||||
@@ -68,6 +68,8 @@ func (s *HistoryStorage) notifyUpdated() {
|
||||
}
|
||||
|
||||
func (s *HistoryStorage) Close() error {
|
||||
s.access.Lock()
|
||||
defer s.access.Unlock()
|
||||
s.updateHook = nil
|
||||
return nil
|
||||
}
|
||||
|
@@ -280,7 +280,7 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
||||
}
|
||||
}
|
||||
logExchangedResponse(c.logger, ctx, response, timeToLive)
|
||||
return response, err
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (c *Client) Lookup(ctx context.Context, transport adapter.DNSTransport, domain string, options adapter.DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) ([]netip.Addr, error) {
|
||||
|
@@ -5,6 +5,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
RcodeSuccess RcodeError = mDNS.RcodeSuccess
|
||||
RcodeFormatError RcodeError = mDNS.RcodeFormatError
|
||||
RcodeNameError RcodeError = mDNS.RcodeNameError
|
||||
RcodeRefused RcodeError = mDNS.RcodeRefused
|
||||
|
@@ -2,12 +2,13 @@ package dhcp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"time"
|
||||
"syscall"
|
||||
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/dns"
|
||||
"github.com/sagernet/sing-box/dns/transport"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
@@ -43,7 +44,7 @@ func (t *Transport) exchangeParallel(ctx context.Context, servers []M.Socksaddr,
|
||||
if response.Rcode != mDNS.RcodeSuccess {
|
||||
err = dns.RcodeError(response.Rcode)
|
||||
} else if len(dns.MessageToAddresses(response)) == 0 {
|
||||
err = E.New(fqdn, ": empty result")
|
||||
err = dns.RcodeSuccess
|
||||
}
|
||||
}
|
||||
select {
|
||||
@@ -83,7 +84,7 @@ func (t *Transport) tryOneName(ctx context.Context, servers []M.Socksaddr, fqdn
|
||||
server := servers[j]
|
||||
question := message.Question[0]
|
||||
question.Name = fqdn
|
||||
response, err := t.exchangeOne(ctx, server, question, C.DNSTimeout, false, true)
|
||||
response, err := t.exchangeOne(ctx, server, question)
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
continue
|
||||
@@ -94,62 +95,77 @@ func (t *Transport) tryOneName(ctx context.Context, servers []M.Socksaddr, fqdn
|
||||
return nil, E.Cause(lastErr, fqdn)
|
||||
}
|
||||
|
||||
func (t *Transport) exchangeOne(ctx context.Context, server M.Socksaddr, question mDNS.Question, timeout time.Duration, useTCP, ad bool) (*mDNS.Msg, error) {
|
||||
func (t *Transport) exchangeOne(ctx context.Context, server M.Socksaddr, question mDNS.Question) (*mDNS.Msg, error) {
|
||||
if server.Port == 0 {
|
||||
server.Port = 53
|
||||
}
|
||||
var networks []string
|
||||
if useTCP {
|
||||
networks = []string{N.NetworkTCP}
|
||||
} else {
|
||||
networks = []string{N.NetworkUDP, N.NetworkTCP}
|
||||
}
|
||||
request := &mDNS.Msg{
|
||||
MsgHdr: mDNS.MsgHdr{
|
||||
Id: uint16(rand.Uint32()),
|
||||
RecursionDesired: true,
|
||||
AuthenticatedData: ad,
|
||||
AuthenticatedData: true,
|
||||
},
|
||||
Question: []mDNS.Question{question},
|
||||
Compress: true,
|
||||
}
|
||||
request.SetEdns0(buf.UDPBufferSize, false)
|
||||
buffer := buf.Get(buf.UDPBufferSize)
|
||||
defer buf.Put(buffer)
|
||||
for _, network := range networks {
|
||||
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(timeout))
|
||||
defer cancel()
|
||||
conn, err := t.dialer.DialContext(ctx, network, server)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer conn.Close()
|
||||
if deadline, loaded := ctx.Deadline(); loaded && !deadline.IsZero() {
|
||||
conn.SetDeadline(deadline)
|
||||
}
|
||||
rawMessage, err := request.PackBuffer(buffer)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "pack request")
|
||||
}
|
||||
_, err = conn.Write(rawMessage)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "write request")
|
||||
}
|
||||
n, err := conn.Read(buffer)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "read response")
|
||||
}
|
||||
var response mDNS.Msg
|
||||
err = response.Unpack(buffer[:n])
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "unpack response")
|
||||
}
|
||||
if response.Truncated && network == N.NetworkUDP {
|
||||
continue
|
||||
}
|
||||
return &response, nil
|
||||
return t.exchangeUDP(ctx, server, request)
|
||||
}
|
||||
|
||||
func (t *Transport) exchangeUDP(ctx context.Context, server M.Socksaddr, request *mDNS.Msg) (*mDNS.Msg, error) {
|
||||
conn, err := t.dialer.DialContext(ctx, N.NetworkUDP, server)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
panic("unexpected")
|
||||
defer conn.Close()
|
||||
if deadline, loaded := ctx.Deadline(); loaded && !deadline.IsZero() {
|
||||
conn.SetDeadline(deadline)
|
||||
}
|
||||
buffer := buf.Get(1 + request.Len())
|
||||
defer buf.Put(buffer)
|
||||
rawMessage, err := request.PackBuffer(buffer)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "pack request")
|
||||
}
|
||||
_, err = conn.Write(rawMessage)
|
||||
if err != nil {
|
||||
if errors.Is(err, syscall.EMSGSIZE) {
|
||||
return t.exchangeTCP(ctx, server, request)
|
||||
}
|
||||
return nil, E.Cause(err, "write request")
|
||||
}
|
||||
n, err := conn.Read(buffer)
|
||||
if err != nil {
|
||||
if errors.Is(err, syscall.EMSGSIZE) {
|
||||
return t.exchangeTCP(ctx, server, request)
|
||||
}
|
||||
return nil, E.Cause(err, "read response")
|
||||
}
|
||||
var response mDNS.Msg
|
||||
err = response.Unpack(buffer[:n])
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "unpack response")
|
||||
}
|
||||
if response.Truncated {
|
||||
return t.exchangeTCP(ctx, server, request)
|
||||
}
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
func (t *Transport) exchangeTCP(ctx context.Context, server M.Socksaddr, request *mDNS.Msg) (*mDNS.Msg, error) {
|
||||
conn, err := t.dialer.DialContext(ctx, N.NetworkTCP, server)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer conn.Close()
|
||||
if deadline, loaded := ctx.Deadline(); loaded && !deadline.IsZero() {
|
||||
conn.SetDeadline(deadline)
|
||||
}
|
||||
err = transport.WriteMessage(conn, 0, request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return transport.ReadMessage(conn)
|
||||
}
|
||||
|
||||
func (t *Transport) nameList(name string) []string {
|
||||
|
@@ -2,11 +2,13 @@ package local
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"errors"
|
||||
"math/rand"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/dns"
|
||||
"github.com/sagernet/sing-box/dns/transport"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
@@ -17,7 +19,6 @@ import (
|
||||
|
||||
func (t *Transport) exchange(ctx context.Context, message *mDNS.Msg, domain string) (*mDNS.Msg, error) {
|
||||
systemConfig := getSystemDNSConfig(t.ctx)
|
||||
fmt.Println(systemConfig.servers)
|
||||
if systemConfig.singleRequest || !(message.Question[0].Qtype == mDNS.TypeA || message.Question[0].Qtype == mDNS.TypeAAAA) {
|
||||
return t.exchangeSingleRequest(ctx, systemConfig, message, domain)
|
||||
} else {
|
||||
@@ -108,12 +109,6 @@ func (t *Transport) exchangeOne(ctx context.Context, server M.Socksaddr, questio
|
||||
if server.Port == 0 {
|
||||
server.Port = 53
|
||||
}
|
||||
var networks []string
|
||||
if useTCP {
|
||||
networks = []string{N.NetworkTCP}
|
||||
} else {
|
||||
networks = []string{N.NetworkUDP, N.NetworkTCP}
|
||||
}
|
||||
request := &mDNS.Msg{
|
||||
MsgHdr: mDNS.MsgHdr{
|
||||
Id: uint16(rand.Uint32()),
|
||||
@@ -124,40 +119,73 @@ func (t *Transport) exchangeOne(ctx context.Context, server M.Socksaddr, questio
|
||||
Compress: true,
|
||||
}
|
||||
request.SetEdns0(buf.UDPBufferSize, false)
|
||||
buffer := buf.Get(buf.UDPBufferSize)
|
||||
defer buf.Put(buffer)
|
||||
for _, network := range networks {
|
||||
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(timeout))
|
||||
defer cancel()
|
||||
conn, err := t.dialer.DialContext(ctx, network, server)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer conn.Close()
|
||||
if deadline, loaded := ctx.Deadline(); loaded && !deadline.IsZero() {
|
||||
conn.SetDeadline(deadline)
|
||||
}
|
||||
rawMessage, err := request.PackBuffer(buffer)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "pack request")
|
||||
}
|
||||
_, err = conn.Write(rawMessage)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "write request")
|
||||
}
|
||||
n, err := conn.Read(buffer)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "read response")
|
||||
}
|
||||
var response mDNS.Msg
|
||||
err = response.Unpack(buffer[:n])
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "unpack response")
|
||||
}
|
||||
if response.Truncated && network == N.NetworkUDP {
|
||||
continue
|
||||
}
|
||||
return &response, nil
|
||||
if !useTCP {
|
||||
return t.exchangeUDP(ctx, server, request, timeout)
|
||||
} else {
|
||||
return t.exchangeTCP(ctx, server, request, timeout)
|
||||
}
|
||||
panic("unexpected")
|
||||
}
|
||||
|
||||
func (t *Transport) exchangeUDP(ctx context.Context, server M.Socksaddr, request *mDNS.Msg, timeout time.Duration) (*mDNS.Msg, error) {
|
||||
conn, err := t.dialer.DialContext(ctx, N.NetworkUDP, server)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer conn.Close()
|
||||
if deadline, loaded := ctx.Deadline(); loaded && !deadline.IsZero() {
|
||||
newDeadline := time.Now().Add(timeout)
|
||||
if deadline.After(newDeadline) {
|
||||
deadline = newDeadline
|
||||
}
|
||||
conn.SetDeadline(deadline)
|
||||
}
|
||||
buffer := buf.Get(1 + request.Len())
|
||||
defer buf.Put(buffer)
|
||||
rawMessage, err := request.PackBuffer(buffer)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "pack request")
|
||||
}
|
||||
_, err = conn.Write(rawMessage)
|
||||
if err != nil {
|
||||
if errors.Is(err, syscall.EMSGSIZE) {
|
||||
return t.exchangeTCP(ctx, server, request, timeout)
|
||||
}
|
||||
return nil, E.Cause(err, "write request")
|
||||
}
|
||||
n, err := conn.Read(buffer)
|
||||
if err != nil {
|
||||
if errors.Is(err, syscall.EMSGSIZE) {
|
||||
return t.exchangeTCP(ctx, server, request, timeout)
|
||||
}
|
||||
return nil, E.Cause(err, "read response")
|
||||
}
|
||||
var response mDNS.Msg
|
||||
err = response.Unpack(buffer[:n])
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "unpack response")
|
||||
}
|
||||
if response.Truncated {
|
||||
return t.exchangeTCP(ctx, server, request, timeout)
|
||||
}
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
func (t *Transport) exchangeTCP(ctx context.Context, server M.Socksaddr, request *mDNS.Msg, timeout time.Duration) (*mDNS.Msg, error) {
|
||||
conn, err := t.dialer.DialContext(ctx, N.NetworkTCP, server)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer conn.Close()
|
||||
if deadline, loaded := ctx.Deadline(); loaded && !deadline.IsZero() {
|
||||
newDeadline := time.Now().Add(timeout)
|
||||
if deadline.After(newDeadline) {
|
||||
deadline = newDeadline
|
||||
}
|
||||
conn.SetDeadline(deadline)
|
||||
}
|
||||
err = transport.WriteMessage(conn, 0, request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return transport.ReadMessage(conn)
|
||||
}
|
||||
|
@@ -2,6 +2,14 @@
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
#### 1.13.0-alpha.12
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.12.6
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.13.0-alpha.11
|
||||
|
||||
* Fixes and improvements
|
||||
|
@@ -14,7 +14,7 @@ import (
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
tun "github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/batch"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
|
@@ -27,16 +27,16 @@ import (
|
||||
var _ adapter.RuleSet = (*LocalRuleSet)(nil)
|
||||
|
||||
type LocalRuleSet struct {
|
||||
ctx context.Context
|
||||
logger logger.Logger
|
||||
tag string
|
||||
rules []adapter.HeadlessRule
|
||||
metadata adapter.RuleSetMetadata
|
||||
fileFormat string
|
||||
watcher *fswatch.Watcher
|
||||
callbackAccess sync.Mutex
|
||||
callbacks list.List[adapter.RuleSetUpdateCallback]
|
||||
refs atomic.Int32
|
||||
ctx context.Context
|
||||
logger logger.Logger
|
||||
tag string
|
||||
access sync.RWMutex
|
||||
rules []adapter.HeadlessRule
|
||||
metadata adapter.RuleSetMetadata
|
||||
fileFormat string
|
||||
watcher *fswatch.Watcher
|
||||
callbacks list.List[adapter.RuleSetUpdateCallback]
|
||||
refs atomic.Int32
|
||||
}
|
||||
|
||||
func NewLocalRuleSet(ctx context.Context, logger logger.Logger, options option.RuleSet) (*LocalRuleSet, error) {
|
||||
@@ -141,11 +141,11 @@ func (s *LocalRuleSet) reloadRules(headlessRules []option.HeadlessRule) error {
|
||||
metadata.ContainsProcessRule = HasHeadlessRule(headlessRules, isProcessHeadlessRule)
|
||||
metadata.ContainsWIFIRule = HasHeadlessRule(headlessRules, isWIFIHeadlessRule)
|
||||
metadata.ContainsIPCIDRRule = HasHeadlessRule(headlessRules, isIPCIDRHeadlessRule)
|
||||
s.access.Lock()
|
||||
s.rules = rules
|
||||
s.metadata = metadata
|
||||
s.callbackAccess.Lock()
|
||||
callbacks := s.callbacks.Array()
|
||||
s.callbackAccess.Unlock()
|
||||
s.access.Unlock()
|
||||
for _, callback := range callbacks {
|
||||
callback(s)
|
||||
}
|
||||
@@ -157,10 +157,14 @@ func (s *LocalRuleSet) PostStart() error {
|
||||
}
|
||||
|
||||
func (s *LocalRuleSet) Metadata() adapter.RuleSetMetadata {
|
||||
s.access.RLock()
|
||||
defer s.access.RUnlock()
|
||||
return s.metadata
|
||||
}
|
||||
|
||||
func (s *LocalRuleSet) ExtractIPSet() []*netipx.IPSet {
|
||||
s.access.RLock()
|
||||
defer s.access.RUnlock()
|
||||
return common.FlatMap(s.rules, extractIPSetFromRule)
|
||||
}
|
||||
|
||||
@@ -181,14 +185,14 @@ func (s *LocalRuleSet) Cleanup() {
|
||||
}
|
||||
|
||||
func (s *LocalRuleSet) RegisterCallback(callback adapter.RuleSetUpdateCallback) *list.Element[adapter.RuleSetUpdateCallback] {
|
||||
s.callbackAccess.Lock()
|
||||
defer s.callbackAccess.Unlock()
|
||||
s.access.Lock()
|
||||
defer s.access.Unlock()
|
||||
return s.callbacks.PushBack(callback)
|
||||
}
|
||||
|
||||
func (s *LocalRuleSet) UnregisterCallback(element *list.Element[adapter.RuleSetUpdateCallback]) {
|
||||
s.callbackAccess.Lock()
|
||||
defer s.callbackAccess.Unlock()
|
||||
s.access.Lock()
|
||||
defer s.access.Unlock()
|
||||
s.callbacks.Remove(element)
|
||||
}
|
||||
|
||||
|
@@ -40,16 +40,16 @@ type RemoteRuleSet struct {
|
||||
logger logger.ContextLogger
|
||||
outbound adapter.OutboundManager
|
||||
options option.RuleSet
|
||||
metadata adapter.RuleSetMetadata
|
||||
updateInterval time.Duration
|
||||
dialer N.Dialer
|
||||
access sync.RWMutex
|
||||
rules []adapter.HeadlessRule
|
||||
metadata adapter.RuleSetMetadata
|
||||
lastUpdated time.Time
|
||||
lastEtag string
|
||||
updateTicker *time.Ticker
|
||||
cacheFile adapter.CacheFile
|
||||
pauseManager pause.Manager
|
||||
callbackAccess sync.Mutex
|
||||
callbacks list.List[adapter.RuleSetUpdateCallback]
|
||||
refs atomic.Int32
|
||||
}
|
||||
@@ -120,10 +120,14 @@ func (s *RemoteRuleSet) PostStart() error {
|
||||
}
|
||||
|
||||
func (s *RemoteRuleSet) Metadata() adapter.RuleSetMetadata {
|
||||
s.access.RLock()
|
||||
defer s.access.RUnlock()
|
||||
return s.metadata
|
||||
}
|
||||
|
||||
func (s *RemoteRuleSet) ExtractIPSet() []*netipx.IPSet {
|
||||
s.access.RLock()
|
||||
defer s.access.RUnlock()
|
||||
return common.FlatMap(s.rules, extractIPSetFromRule)
|
||||
}
|
||||
|
||||
@@ -144,14 +148,14 @@ func (s *RemoteRuleSet) Cleanup() {
|
||||
}
|
||||
|
||||
func (s *RemoteRuleSet) RegisterCallback(callback adapter.RuleSetUpdateCallback) *list.Element[adapter.RuleSetUpdateCallback] {
|
||||
s.callbackAccess.Lock()
|
||||
defer s.callbackAccess.Unlock()
|
||||
s.access.Lock()
|
||||
defer s.access.Unlock()
|
||||
return s.callbacks.PushBack(callback)
|
||||
}
|
||||
|
||||
func (s *RemoteRuleSet) UnregisterCallback(element *list.Element[adapter.RuleSetUpdateCallback]) {
|
||||
s.callbackAccess.Lock()
|
||||
defer s.callbackAccess.Unlock()
|
||||
s.access.Lock()
|
||||
defer s.access.Unlock()
|
||||
s.callbacks.Remove(element)
|
||||
}
|
||||
|
||||
@@ -185,13 +189,13 @@ func (s *RemoteRuleSet) loadBytes(content []byte) error {
|
||||
return E.Cause(err, "parse rule_set.rules.[", i, "]")
|
||||
}
|
||||
}
|
||||
s.access.Lock()
|
||||
s.metadata.ContainsProcessRule = HasHeadlessRule(plainRuleSet.Rules, isProcessHeadlessRule)
|
||||
s.metadata.ContainsWIFIRule = HasHeadlessRule(plainRuleSet.Rules, isWIFIHeadlessRule)
|
||||
s.metadata.ContainsIPCIDRRule = HasHeadlessRule(plainRuleSet.Rules, isIPCIDRHeadlessRule)
|
||||
s.rules = rules
|
||||
s.callbackAccess.Lock()
|
||||
callbacks := s.callbacks.Array()
|
||||
s.callbackAccess.Unlock()
|
||||
s.access.Unlock()
|
||||
for _, callback := range callbacks {
|
||||
callback(s)
|
||||
}
|
||||
|
@@ -5,12 +5,12 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=hysteria
|
||||
PKG_VERSION:=2.6.2
|
||||
PKG_VERSION:=2.6.3
|
||||
PKG_RELEASE:=1
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/apernet/hysteria/tar.gz/app/v$(PKG_VERSION)?
|
||||
PKG_HASH:=4699431f0bc826da2bbd3939c0a78c4e7bfc02773fc3a62b24615c37ee89b266
|
||||
PKG_HASH:=bed1ece93dfaa07fbf709136efadaf4ccb09e0375844de3e28c5644ebe518eb0
|
||||
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-app-v$(PKG_VERSION)
|
||||
|
||||
PKG_LICENSE:=MIT
|
||||
|
@@ -711,39 +711,37 @@ return view.extend({
|
||||
o.modalonly = true;
|
||||
}
|
||||
|
||||
if (features.with_reality_server) {
|
||||
o = s.option(form.Flag, 'tls_reality', _('REALITY'));
|
||||
o.depends({'tls': '1', 'tls_acme': '0', 'type': 'vless'});
|
||||
o.depends({'tls': '1', 'tls_acme': null, 'type': 'vless'});
|
||||
o.modalonly = true;
|
||||
o = s.option(form.Flag, 'tls_reality', _('REALITY'));
|
||||
o.depends({'tls': '1', 'tls_acme': '0', 'type': /^(anytls|vless)$/});
|
||||
o.depends({'tls': '1', 'tls_acme': null, 'type': /^(anytls|vless)$/});
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.option(form.Value, 'tls_reality_private_key', _('REALITY private key'));
|
||||
o.depends('tls_reality', '1');
|
||||
o.rmempty = false;
|
||||
o.modalonly = true;
|
||||
o = s.option(form.Value, 'tls_reality_private_key', _('REALITY private key'));
|
||||
o.depends('tls_reality', '1');
|
||||
o.rmempty = false;
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.option(form.DynamicList, 'tls_reality_short_id', _('REALITY short ID'));
|
||||
o.depends('tls_reality', '1');
|
||||
o.rmempty = false;
|
||||
o.modalonly = true;
|
||||
o = s.option(form.DynamicList, 'tls_reality_short_id', _('REALITY short ID'));
|
||||
o.depends('tls_reality', '1');
|
||||
o.rmempty = false;
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.option(form.Value, 'tls_reality_max_time_difference', _('Max time difference'),
|
||||
_('The maximum time difference between the server and the client.'));
|
||||
o.depends('tls_reality', '1');
|
||||
o.modalonly = true;
|
||||
o = s.option(form.Value, 'tls_reality_max_time_difference', _('Max time difference'),
|
||||
_('The maximum time difference between the server and the client.'));
|
||||
o.depends('tls_reality', '1');
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.option(form.Value, 'tls_reality_server_addr', _('Handshake server address'));
|
||||
o.datatype = 'hostname';
|
||||
o.depends('tls_reality', '1');
|
||||
o.rmempty = false;
|
||||
o.modalonly = true;
|
||||
o = s.option(form.Value, 'tls_reality_server_addr', _('Handshake server address'));
|
||||
o.datatype = 'hostname';
|
||||
o.depends('tls_reality', '1');
|
||||
o.rmempty = false;
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.option(form.Value, 'tls_reality_server_port', _('Handshake server port'));
|
||||
o.datatype = 'port';
|
||||
o.depends('tls_reality', '1');
|
||||
o.rmempty = false;
|
||||
o.modalonly = true;
|
||||
}
|
||||
o = s.option(form.Value, 'tls_reality_server_port', _('Handshake server port'));
|
||||
o.datatype = 'port';
|
||||
o.depends('tls_reality', '1');
|
||||
o.rmempty = false;
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.option(form.Value, 'tls_cert_path', _('Certificate path'),
|
||||
_('The server public key, in PEM format.'));
|
||||
|
@@ -31,7 +31,7 @@ const css = ' \
|
||||
|
||||
const hp_dir = '/var/run/homeproxy';
|
||||
|
||||
function getConnStat(self, site) {
|
||||
function getConnStat(o, site) {
|
||||
const callConnStat = rpc.declare({
|
||||
object: 'luci.homeproxy',
|
||||
method: 'connection_check',
|
||||
@@ -39,12 +39,12 @@ function getConnStat(self, site) {
|
||||
expect: { '': {} }
|
||||
});
|
||||
|
||||
self.default = E('div', { 'style': 'cbi-value-field' }, [
|
||||
o.default = E('div', { 'style': 'cbi-value-field' }, [
|
||||
E('button', {
|
||||
'class': 'btn cbi-button cbi-button-action',
|
||||
'click': ui.createHandlerFn(this, function() {
|
||||
return L.resolveDefault(callConnStat(site), {}).then((ret) => {
|
||||
let ele = self.default.firstElementChild.nextElementSibling;
|
||||
let ele = o.default.firstElementChild.nextElementSibling;
|
||||
if (ret.result) {
|
||||
ele.style.setProperty('color', 'green');
|
||||
ele.innerHTML = _('passed');
|
||||
@@ -60,7 +60,7 @@ function getConnStat(self, site) {
|
||||
]);
|
||||
}
|
||||
|
||||
function getResVersion(self, type) {
|
||||
function getResVersion(o, type) {
|
||||
const callResVersion = rpc.declare({
|
||||
object: 'luci.homeproxy',
|
||||
method: 'resources_get_version',
|
||||
@@ -83,23 +83,23 @@ function getResVersion(self, type) {
|
||||
return L.resolveDefault(callResUpdate(type), {}).then((res) => {
|
||||
switch (res.status) {
|
||||
case 0:
|
||||
self.description = _('Successfully updated.');
|
||||
o.description = _('Successfully updated.');
|
||||
break;
|
||||
case 1:
|
||||
self.description = _('Update failed.');
|
||||
o.description = _('Update failed.');
|
||||
break;
|
||||
case 2:
|
||||
self.description = _('Already in updating.');
|
||||
o.description = _('Already in updating.');
|
||||
break;
|
||||
case 3:
|
||||
self.description = _('Already at the latest version.');
|
||||
o.description = _('Already at the latest version.');
|
||||
break;
|
||||
default:
|
||||
self.description = _('Unknown error.');
|
||||
o.description = _('Unknown error.');
|
||||
break;
|
||||
}
|
||||
|
||||
return self.map.reset();
|
||||
return o.map.reset();
|
||||
});
|
||||
})
|
||||
}, [ _('Check update') ]),
|
||||
@@ -109,11 +109,57 @@ function getResVersion(self, type) {
|
||||
),
|
||||
]);
|
||||
|
||||
self.default = spanTemp;
|
||||
o.default = spanTemp;
|
||||
});
|
||||
}
|
||||
|
||||
function getRuntimeLog(name, filename) {
|
||||
function getRuntimeLog(o, name, _option_index, section_id, _in_table) {
|
||||
const filename = o.option.split('_')[1];
|
||||
|
||||
let section, log_level_el;
|
||||
switch (filename) {
|
||||
case 'homeproxy':
|
||||
section = null;
|
||||
break;
|
||||
case 'sing-box-c':
|
||||
section = 'config';
|
||||
break;
|
||||
case 'sing-box-s':
|
||||
section = 'server';
|
||||
break;
|
||||
}
|
||||
|
||||
if (section) {
|
||||
const selected = uci.get('homeproxy', section, 'log_level') || 'warn';
|
||||
const choices = {
|
||||
trace: _('Trace'),
|
||||
debug: _('Debug'),
|
||||
info: _('Info'),
|
||||
warn: _('Warn'),
|
||||
error: _('Error'),
|
||||
fatal: _('Fatal'),
|
||||
panic: _('Panic')
|
||||
};
|
||||
|
||||
log_level_el = E('select', {
|
||||
'id': o.cbid(section_id),
|
||||
'class': 'cbi-input-select',
|
||||
'style': 'margin-left: 4px; width: 6em;',
|
||||
'change': ui.createHandlerFn(this, function(ev) {
|
||||
uci.set('homeproxy', section, 'log_level', ev.target.value);
|
||||
ui.changes.apply(true);
|
||||
return o.map.save(null, true);
|
||||
})
|
||||
});
|
||||
|
||||
Object.keys(choices).forEach((v) => {
|
||||
log_level_el.appendChild(E('option', {
|
||||
'value': v,
|
||||
'selected': (v === selected) ? '' : null
|
||||
}, [ choices[v] ]));
|
||||
});
|
||||
}
|
||||
|
||||
const callLogClean = rpc.declare({
|
||||
object: 'luci.homeproxy',
|
||||
method: 'log_clean',
|
||||
@@ -121,7 +167,7 @@ function getRuntimeLog(name, filename) {
|
||||
expect: { '': {} }
|
||||
});
|
||||
|
||||
let log_textarea = E('div', { 'id': 'log_textarea' },
|
||||
const log_textarea = E('div', { 'id': 'log_textarea' },
|
||||
E('img', {
|
||||
'src': L.resource('icons/loading.svg'),
|
||||
'alt': _('Loading'),
|
||||
@@ -155,11 +201,12 @@ function getRuntimeLog(name, filename) {
|
||||
return E([
|
||||
E('style', [ css ]),
|
||||
E('div', {'class': 'cbi-map'}, [
|
||||
E('h3', {'name': 'content'}, [
|
||||
E('h3', {'name': 'content', 'style': 'align-items: center; display: flex;'}, [
|
||||
_('%s log').format(name),
|
||||
' ',
|
||||
log_level_el || '',
|
||||
E('button', {
|
||||
'class': 'btn cbi-button cbi-button-action',
|
||||
'style': 'margin-left: 4px;',
|
||||
'click': ui.createHandlerFn(this, function() {
|
||||
return L.resolveDefault(callLogClean(filename), {});
|
||||
})
|
||||
@@ -185,29 +232,28 @@ return view.extend({
|
||||
s.anonymous = true;
|
||||
|
||||
o = s.option(form.DummyValue, '_check_baidu', _('BaiDu'));
|
||||
o.cfgvalue = function() { return getConnStat(this, 'baidu') };
|
||||
o.cfgvalue = L.bind(getConnStat, this, o, 'baidu');
|
||||
|
||||
o = s.option(form.DummyValue, '_check_google', _('Google'));
|
||||
o.cfgvalue = function() { return getConnStat(this, 'google') };
|
||||
|
||||
o.cfgvalue = L.bind(getConnStat, this, o, 'google');
|
||||
|
||||
s = m.section(form.NamedSection, 'config', 'homeproxy', _('Resources management'));
|
||||
s.anonymous = true;
|
||||
|
||||
o = s.option(form.DummyValue, '_china_ip4_version', _('China IPv4 list version'));
|
||||
o.cfgvalue = function() { return getResVersion(this, 'china_ip4') };
|
||||
o.cfgvalue = L.bind(getResVersion, this, o, 'china_ip4');
|
||||
o.rawhtml = true;
|
||||
|
||||
o = s.option(form.DummyValue, '_china_ip6_version', _('China IPv6 list version'));
|
||||
o.cfgvalue = function() { return getResVersion(this, 'china_ip6') };
|
||||
o.cfgvalue = L.bind(getResVersion, this, o, 'china_ip6');
|
||||
o.rawhtml = true;
|
||||
|
||||
o = s.option(form.DummyValue, '_china_list_version', _('China list version'));
|
||||
o.cfgvalue = function() { return getResVersion(this, 'china_list') };
|
||||
o.cfgvalue = L.bind(getResVersion, this, o, 'china_list');
|
||||
o.rawhtml = true;
|
||||
|
||||
o = s.option(form.DummyValue, '_gfw_list_version', _('GFW list version'));
|
||||
o.cfgvalue = function() { return getResVersion(this, 'gfw_list') };
|
||||
o.cfgvalue = L.bind(getResVersion, this, o, 'gfw_list');
|
||||
o.rawhtml = true;
|
||||
|
||||
o = s.option(form.Value, 'github_token', _('GitHub token'));
|
||||
@@ -231,13 +277,13 @@ return view.extend({
|
||||
s.anonymous = true;
|
||||
|
||||
o = s.option(form.DummyValue, '_homeproxy_logview');
|
||||
o.render = L.bind(getRuntimeLog, this, _('HomeProxy'), 'homeproxy');
|
||||
o.render = L.bind(getRuntimeLog, this, o, _('HomeProxy'));
|
||||
|
||||
o = s.option(form.DummyValue, '_sing-box-c_logview');
|
||||
o.render = L.bind(getRuntimeLog, this, _('sing-box client'), 'sing-box-c');
|
||||
o.render = L.bind(getRuntimeLog, this, o, _('sing-box client'));
|
||||
|
||||
o = s.option(form.DummyValue, '_sing-box-s_logview');
|
||||
o.render = L.bind(getRuntimeLog, this, _('sing-box server'), 'sing-box-s');
|
||||
o.render = L.bind(getRuntimeLog, this, o, _('sing-box server'));
|
||||
|
||||
return m.render();
|
||||
},
|
||||
|
@@ -1,11 +1,11 @@
|
||||
msgid ""
|
||||
msgstr "Content-Type: text/plain; charset=UTF-8"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:159
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:205
|
||||
msgid "%s log"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1454
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1449
|
||||
msgid "%s nodes removed"
|
||||
msgstr ""
|
||||
|
||||
@@ -25,9 +25,9 @@ msgid ""
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1082
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1106
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:760
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:779
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1101
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:758
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:777
|
||||
msgid "<strong>Save your configuration before uploading files!</strong>"
|
||||
msgstr ""
|
||||
|
||||
@@ -123,7 +123,7 @@ msgid "Allow access from the Internet."
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1036
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1381
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1376
|
||||
msgid "Allow insecure"
|
||||
msgstr ""
|
||||
|
||||
@@ -131,7 +131,7 @@ msgstr ""
|
||||
msgid "Allow insecure connection at TLS client."
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1382
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1377
|
||||
msgid "Allow insecure connection by default when add nodes from subscriptions."
|
||||
msgstr ""
|
||||
|
||||
@@ -161,7 +161,7 @@ msgstr ""
|
||||
msgid "Alternative TLS port"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1417
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1412
|
||||
msgid "An error occurred during updating subscriptions: %s"
|
||||
msgstr ""
|
||||
|
||||
@@ -222,11 +222,11 @@ msgstr ""
|
||||
msgid "Authentication type"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1334
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1329
|
||||
msgid "Auto update"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1335
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1330
|
||||
msgid "Auto update subscriptions and geodata."
|
||||
msgstr ""
|
||||
|
||||
@@ -234,7 +234,7 @@ msgstr ""
|
||||
msgid "BBR"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:187
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:234
|
||||
msgid "BaiDu"
|
||||
msgstr ""
|
||||
|
||||
@@ -253,7 +253,7 @@ msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:453
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:1410
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:864
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:862
|
||||
msgid "Bind interface"
|
||||
msgstr ""
|
||||
|
||||
@@ -267,7 +267,7 @@ msgstr ""
|
||||
msgid "BitTorrent"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1368
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1363
|
||||
msgid "Blacklist mode"
|
||||
msgstr ""
|
||||
|
||||
@@ -279,7 +279,7 @@ msgstr ""
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:640
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:1090
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:1100
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:859
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:857
|
||||
msgid "Both"
|
||||
msgstr ""
|
||||
|
||||
@@ -307,12 +307,12 @@ msgstr ""
|
||||
msgid "CUBIC"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1238
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1233
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1073
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:748
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:746
|
||||
msgid "Certificate path"
|
||||
msgstr ""
|
||||
|
||||
@@ -328,15 +328,15 @@ msgstr ""
|
||||
msgid "China DNS server"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:197
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:243
|
||||
msgid "China IPv4 list version"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:201
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:247
|
||||
msgid "China IPv6 list version"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:205
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:251
|
||||
msgid "China list version"
|
||||
msgstr ""
|
||||
|
||||
@@ -353,7 +353,7 @@ msgstr ""
|
||||
msgid "Cisco Public DNS (208.67.222.222)"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:166
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:213
|
||||
msgid "Clean log"
|
||||
msgstr ""
|
||||
|
||||
@@ -379,7 +379,7 @@ msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:114
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:122
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:129
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:175
|
||||
msgid "Collecting data..."
|
||||
msgstr ""
|
||||
|
||||
@@ -392,7 +392,7 @@ msgstr ""
|
||||
msgid "Congestion control algorithm"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:184
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:231
|
||||
msgid "Connection check"
|
||||
msgstr ""
|
||||
|
||||
@@ -439,6 +439,10 @@ msgstr ""
|
||||
msgid "DTLS"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:136
|
||||
msgid "Debug"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/homeproxy.js:17
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:433
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:603
|
||||
@@ -482,7 +486,7 @@ msgstr ""
|
||||
msgid "Default outbound for connections not matched by any routing rules."
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1388
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1383
|
||||
msgid "Default packet encoding"
|
||||
msgstr ""
|
||||
|
||||
@@ -523,8 +527,8 @@ msgstr ""
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:498
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:554
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:566
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1117
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1367
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1111
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1362
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:267
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:279
|
||||
msgid "Disable"
|
||||
@@ -645,14 +649,14 @@ msgstr ""
|
||||
msgid "Drop requests"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1374
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1369
|
||||
msgid ""
|
||||
"Drop/keep nodes that contain the specific keywords. <a target=\"_blank\" "
|
||||
"href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/"
|
||||
"Regular_Expressions\">Regex</a> is supported."
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1366
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1361
|
||||
msgid "Drop/keep specific nodes from subscriptions."
|
||||
msgstr ""
|
||||
|
||||
@@ -664,22 +668,22 @@ msgid ""
|
||||
"a non-ACME system, such as a CA customer database."
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1091
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1090
|
||||
msgid ""
|
||||
"ECH (Encrypted Client Hello) is a TLS extension that allows a client to "
|
||||
"encrypt the first part of its ClientHello message."
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1110
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:825
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1105
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:823
|
||||
msgid "ECH config"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1099
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1094
|
||||
msgid "ECH config path"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:786
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:784
|
||||
msgid "ECH key"
|
||||
msgstr ""
|
||||
|
||||
@@ -703,7 +707,7 @@ msgstr ""
|
||||
msgid "Early data is sent in path instead of header by default."
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1210
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1205
|
||||
msgid "Edit nodes"
|
||||
msgstr ""
|
||||
|
||||
@@ -738,14 +742,10 @@ msgstr ""
|
||||
msgid "Enable ACME"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1090
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1089
|
||||
msgid "Enable ECH"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1095
|
||||
msgid "Enable PQ signature schemes"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:980
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:522
|
||||
msgid "Enable TCP Brutal"
|
||||
@@ -756,8 +756,8 @@ msgstr ""
|
||||
msgid "Enable TCP Brutal congestion control algorithm"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1167
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:845
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1162
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:843
|
||||
msgid "Enable UDP fragmentation."
|
||||
msgstr ""
|
||||
|
||||
@@ -770,11 +770,11 @@ msgstr ""
|
||||
msgid "Enable padding"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:836
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:834
|
||||
msgid "Enable tcp fast open for listener."
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1171
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1166
|
||||
msgid ""
|
||||
"Enable the SUoT protocol, requires server support. Conflict with multiplex."
|
||||
msgstr ""
|
||||
@@ -785,6 +785,10 @@ msgstr ""
|
||||
msgid "Encrypt method"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:139
|
||||
msgid "Error"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/homeproxy.js:237
|
||||
#: htdocs/luci-static/resources/homeproxy.js:271
|
||||
#: htdocs/luci-static/resources/homeproxy.js:279
|
||||
@@ -809,10 +813,10 @@ msgstr ""
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:1505
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:1537
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:487
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1133
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1301
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1355
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1358
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1127
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1296
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1350
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1353
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:226
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:628
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:630
|
||||
@@ -843,11 +847,15 @@ msgstr ""
|
||||
msgid "Failed to upload %s, error: %s."
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1373
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:140
|
||||
msgid "Fatal"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1368
|
||||
msgid "Filter keywords"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1365
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1360
|
||||
msgid "Filter nodes"
|
||||
msgstr ""
|
||||
|
||||
@@ -889,7 +897,7 @@ msgstr ""
|
||||
msgid "GET"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:209
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:255
|
||||
msgid "GFW list version"
|
||||
msgstr ""
|
||||
|
||||
@@ -915,11 +923,11 @@ msgstr ""
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:294
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:355
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:357
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:817
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:815
|
||||
msgid "Generate"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:213
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:259
|
||||
msgid "GitHub token"
|
||||
msgstr ""
|
||||
|
||||
@@ -947,7 +955,7 @@ msgstr ""
|
||||
msgid "Global settings"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:190
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:237
|
||||
msgid "Google"
|
||||
msgstr ""
|
||||
|
||||
@@ -987,11 +995,11 @@ msgstr ""
|
||||
msgid "HTTPUpgrade"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:735
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:734
|
||||
msgid "Handshake server address"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:741
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:740
|
||||
msgid "Handshake server port"
|
||||
msgstr ""
|
||||
|
||||
@@ -1007,7 +1015,7 @@ msgstr ""
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:55
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:57
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:101
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:234
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:280
|
||||
#: root/usr/share/luci/menu.d/luci-app-homeproxy.json:3
|
||||
msgid "HomeProxy"
|
||||
msgstr ""
|
||||
@@ -1150,18 +1158,18 @@ msgstr ""
|
||||
msgid "Ignore client bandwidth"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1284
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1279
|
||||
msgid "Import"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1231
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1310
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1312
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1226
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1305
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1307
|
||||
msgid "Import share links"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:336
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:850
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:848
|
||||
msgid "In seconds."
|
||||
msgstr ""
|
||||
|
||||
@@ -1184,6 +1192,10 @@ msgstr ""
|
||||
msgid "Independent cache per server"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:137
|
||||
msgid "Info"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:1403
|
||||
msgid "Interface Control"
|
||||
msgstr ""
|
||||
@@ -1217,7 +1229,7 @@ msgstr ""
|
||||
msgid "Invert match result."
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:767
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:765
|
||||
msgid "Key path"
|
||||
msgstr ""
|
||||
|
||||
@@ -1290,7 +1302,7 @@ msgstr ""
|
||||
msgid "Listen port"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:127
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:173
|
||||
msgid "Loading"
|
||||
msgstr ""
|
||||
|
||||
@@ -1302,11 +1314,11 @@ msgstr ""
|
||||
msgid "Local address"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:144
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:190
|
||||
msgid "Log file does not exist."
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:137
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:183
|
||||
msgid "Log is empty."
|
||||
msgstr ""
|
||||
|
||||
@@ -1451,7 +1463,7 @@ msgstr ""
|
||||
msgid "Max download speed in Mbps."
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:730
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:729
|
||||
msgid "Max time difference"
|
||||
msgstr ""
|
||||
|
||||
@@ -1527,8 +1539,8 @@ msgstr ""
|
||||
msgid "Mode"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1163
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:840
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1158
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:838
|
||||
msgid "MultiPath TCP"
|
||||
msgstr ""
|
||||
|
||||
@@ -1546,7 +1558,7 @@ msgstr ""
|
||||
msgid "NOT RUNNING"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1394
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1389
|
||||
msgid "NOTE: Save current settings before updating subscriptions."
|
||||
msgstr ""
|
||||
|
||||
@@ -1564,7 +1576,7 @@ msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:637
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:1097
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:856
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:854
|
||||
msgid "Network"
|
||||
msgstr ""
|
||||
|
||||
@@ -1584,15 +1596,15 @@ msgstr ""
|
||||
msgid "No additional encryption support: It's basically duplicate encryption."
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1410
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1405
|
||||
msgid "No subscription available"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1435
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1430
|
||||
msgid "No subscription node"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1270
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1265
|
||||
msgid "No valid share link found."
|
||||
msgstr ""
|
||||
|
||||
@@ -1605,7 +1617,7 @@ msgstr ""
|
||||
msgid "Node Settings"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1216
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1211
|
||||
msgid "Nodes"
|
||||
msgstr ""
|
||||
|
||||
@@ -1687,6 +1699,10 @@ msgstr ""
|
||||
msgid "Padding scheme"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:141
|
||||
msgid "Panic"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:463
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:190
|
||||
msgid "Password"
|
||||
@@ -1899,21 +1915,21 @@ msgstr ""
|
||||
msgid "RDRC timeout"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1144
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:715
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1138
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:714
|
||||
msgid "REALITY"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:720
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:719
|
||||
msgid "REALITY private key"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1148
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1143
|
||||
msgid "REALITY public key"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1153
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:725
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1148
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:724
|
||||
msgid "REALITY short ID"
|
||||
msgstr ""
|
||||
|
||||
@@ -1946,7 +1962,7 @@ msgstr ""
|
||||
msgid "Redirect TCP + Tun UDP"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:171
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:218
|
||||
msgid "Refresh every %s seconds."
|
||||
msgstr ""
|
||||
|
||||
@@ -1963,11 +1979,11 @@ msgstr ""
|
||||
msgid "Remote"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1432
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1427
|
||||
msgid "Remove %s nodes"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1422
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1417
|
||||
msgid "Remove all nodes from subscriptions"
|
||||
msgstr ""
|
||||
|
||||
@@ -1991,15 +2007,15 @@ msgstr ""
|
||||
msgid "Resolve strategy"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:194
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:240
|
||||
msgid "Resources management"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:870
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:868
|
||||
msgid "Reuse address"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:871
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:869
|
||||
msgid "Reuse listener address."
|
||||
msgstr ""
|
||||
|
||||
@@ -2081,7 +2097,7 @@ msgstr ""
|
||||
msgid "STUN"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1176
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1171
|
||||
msgid "SUoT version"
|
||||
msgstr ""
|
||||
|
||||
@@ -2098,16 +2114,16 @@ msgstr ""
|
||||
msgid "Same as main node"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:220
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:225
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:266
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:271
|
||||
msgid "Save"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1396
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1391
|
||||
msgid "Save current settings"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1393
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1388
|
||||
msgid "Save subscriptions settings"
|
||||
msgstr ""
|
||||
|
||||
@@ -2254,19 +2270,19 @@ msgstr ""
|
||||
msgid "String"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1321
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1316
|
||||
msgid "Sub (%s)"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1348
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1343
|
||||
msgid "Subscription URL-s"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1332
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1327
|
||||
msgid "Subscriptions"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1272
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1267
|
||||
msgid "Successfully imported %s nodes of total %s."
|
||||
msgstr ""
|
||||
|
||||
@@ -2274,8 +2290,8 @@ msgstr ""
|
||||
msgid "Successfully updated."
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1232
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1349
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1227
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1344
|
||||
msgid ""
|
||||
"Support Hysteria, Shadowsocks, Trojan, v2rayN (VMess), and XTLS (VLESS) "
|
||||
"online configuration delivery standard."
|
||||
@@ -2302,12 +2318,12 @@ msgstr ""
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:638
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:949
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:1098
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:857
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:855
|
||||
msgid "TCP"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1160
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:835
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1155
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:833
|
||||
msgid "TCP fast open"
|
||||
msgstr ""
|
||||
|
||||
@@ -2507,7 +2523,7 @@ msgid ""
|
||||
"allowed to open."
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:731
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:730
|
||||
msgid "The maximum time difference between the server and the client."
|
||||
msgstr ""
|
||||
|
||||
@@ -2522,7 +2538,7 @@ msgid "The modern ImmortalWrt proxy platform for ARM64/AMD64."
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:454
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:865
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:863
|
||||
msgid "The network interface to bind to."
|
||||
msgstr ""
|
||||
|
||||
@@ -2530,7 +2546,7 @@ msgstr ""
|
||||
msgid "The path of the DNS server."
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1100
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1095
|
||||
msgid ""
|
||||
"The path to the ECH config, in PEM format. If empty, load from DNS will be "
|
||||
"attempted."
|
||||
@@ -2552,11 +2568,11 @@ msgstr ""
|
||||
msgid "The response code."
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:768
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:766
|
||||
msgid "The server private key, in PEM format."
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:749
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:747
|
||||
msgid "The server public key, in PEM format."
|
||||
msgstr ""
|
||||
|
||||
@@ -2587,7 +2603,7 @@ msgid ""
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1039
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1384
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1379
|
||||
msgid ""
|
||||
"This is <strong>DANGEROUS</strong>, your traffic is almost like "
|
||||
"<strong>PLAIN TEXT</strong>! Use at your own risk!"
|
||||
@@ -2628,6 +2644,10 @@ msgid ""
|
||||
"<code>kmod-tun</code>"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:135
|
||||
msgid "Trace"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:769
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:409
|
||||
msgid "Transport"
|
||||
@@ -2657,21 +2677,21 @@ msgstr ""
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:639
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:948
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:1099
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:858
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:856
|
||||
msgid "UDP"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1166
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:844
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1161
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:842
|
||||
msgid "UDP Fragment"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:335
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:849
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:847
|
||||
msgid "UDP NAT expiration time"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1170
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1165
|
||||
msgid "UDP over TCP"
|
||||
msgstr ""
|
||||
|
||||
@@ -2712,15 +2732,15 @@ msgstr ""
|
||||
msgid "Unknown error."
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:148
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:194
|
||||
msgid "Unknown error: %s"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1137
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1131
|
||||
msgid "Unsupported fingerprint!"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1407
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1402
|
||||
msgid "Update %s subscriptions"
|
||||
msgstr ""
|
||||
|
||||
@@ -2736,23 +2756,23 @@ msgstr ""
|
||||
msgid "Update interval of rule set."
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1402
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1397
|
||||
msgid "Update nodes from subscriptions"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1345
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1340
|
||||
msgid "Update subscriptions via proxy."
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1338
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1333
|
||||
msgid "Update time"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1344
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1339
|
||||
msgid "Update via proxy"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1105
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1100
|
||||
msgid "Upload ECH config"
|
||||
msgstr ""
|
||||
|
||||
@@ -2767,18 +2787,18 @@ msgid "Upload bandwidth in Mbps."
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1081
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:759
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:757
|
||||
msgid "Upload certificate"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:778
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:776
|
||||
msgid "Upload key"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1084
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1108
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:762
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:781
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1103
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:760
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:779
|
||||
msgid "Upload..."
|
||||
msgstr ""
|
||||
|
||||
@@ -2802,7 +2822,7 @@ msgstr ""
|
||||
msgid "User"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1378
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1373
|
||||
msgid "User-Agent"
|
||||
msgstr ""
|
||||
|
||||
@@ -2830,12 +2850,16 @@ msgstr ""
|
||||
msgid "WAN IP Policy"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:138
|
||||
msgid "Warn"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:776
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:416
|
||||
msgid "WebSocket"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1369
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1364
|
||||
msgid "Whitelist mode"
|
||||
msgstr ""
|
||||
|
||||
@@ -2860,7 +2884,7 @@ msgid "Write proxy protocol in the connection header."
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:885
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1391
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1386
|
||||
msgid "Xudp (Xray-core)"
|
||||
msgstr ""
|
||||
|
||||
@@ -2873,7 +2897,7 @@ msgid "ZeroSSL"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1086
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:764
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:762
|
||||
msgid "certificate"
|
||||
msgstr ""
|
||||
|
||||
@@ -2917,19 +2941,19 @@ msgstr ""
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:504
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:1355
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:487
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1133
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1127
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:226
|
||||
msgid "non-empty value"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:628
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:883
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1389
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1384
|
||||
msgid "none"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:884
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1390
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1385
|
||||
msgid "packet addr (v2ray-core v5+)"
|
||||
msgstr ""
|
||||
|
||||
@@ -2937,7 +2961,7 @@ msgstr ""
|
||||
msgid "passed"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:783
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:781
|
||||
msgid "private key"
|
||||
msgstr ""
|
||||
|
||||
@@ -2945,19 +2969,19 @@ msgstr ""
|
||||
msgid "quic-go / uquic chrome"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:237
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:283
|
||||
msgid "sing-box client"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:240
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:286
|
||||
msgid "sing-box server"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1115
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1109
|
||||
msgid "uTLS fingerprint"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1116
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1110
|
||||
msgid ""
|
||||
"uTLS is a fork of \"crypto/tls\", which provides ClientHello fingerprinting "
|
||||
"resistance."
|
||||
@@ -2968,7 +2992,7 @@ msgid "unchecked"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/homeproxy.js:237
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1301
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1296
|
||||
msgid "unique UCI identifier"
|
||||
msgstr ""
|
||||
|
||||
@@ -2978,13 +3002,13 @@ msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:499
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:642
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1177
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1172
|
||||
msgid "v1"
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:500
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:643
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1178
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1173
|
||||
msgid "v2"
|
||||
msgstr ""
|
||||
|
||||
@@ -3003,8 +3027,8 @@ msgstr ""
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:521
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:1360
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:1363
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1355
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1358
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1350
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1353
|
||||
msgid "valid URL"
|
||||
msgstr ""
|
||||
|
||||
|
@@ -8,11 +8,11 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:159
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:205
|
||||
msgid "%s log"
|
||||
msgstr "%s 日志"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1454
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1449
|
||||
msgid "%s nodes removed"
|
||||
msgstr "移除了 %s 个节点"
|
||||
|
||||
@@ -34,9 +34,9 @@ msgstr ""
|
||||
"<code>%s</code>。"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1082
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1106
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:760
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:779
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1101
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:758
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:777
|
||||
msgid "<strong>Save your configuration before uploading files!</strong>"
|
||||
msgstr "<strong>上传文件前请先保存配置!</strong>"
|
||||
|
||||
@@ -132,7 +132,7 @@ msgid "Allow access from the Internet."
|
||||
msgstr "允许来自互联网的访问。"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1036
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1381
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1376
|
||||
msgid "Allow insecure"
|
||||
msgstr "允许不安全连接"
|
||||
|
||||
@@ -140,7 +140,7 @@ msgstr "允许不安全连接"
|
||||
msgid "Allow insecure connection at TLS client."
|
||||
msgstr "允许 TLS 客户端侧的不安全连接。"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1382
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1377
|
||||
msgid "Allow insecure connection by default when add nodes from subscriptions."
|
||||
msgstr "从订阅获取节点时,默认允许不安全连接。"
|
||||
|
||||
@@ -170,7 +170,7 @@ msgstr "替代 HTTP 端口"
|
||||
msgid "Alternative TLS port"
|
||||
msgstr "替代 HTTPS 端口"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1417
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1412
|
||||
msgid "An error occurred during updating subscriptions: %s"
|
||||
msgstr "更新订阅时发生错误:%s"
|
||||
|
||||
@@ -233,11 +233,11 @@ msgstr "认证载荷"
|
||||
msgid "Authentication type"
|
||||
msgstr "认证类型"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1334
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1329
|
||||
msgid "Auto update"
|
||||
msgstr "自动更新"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1335
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1330
|
||||
msgid "Auto update subscriptions and geodata."
|
||||
msgstr "自动更新订阅和地理数据。"
|
||||
|
||||
@@ -245,7 +245,7 @@ msgstr "自动更新订阅和地理数据。"
|
||||
msgid "BBR"
|
||||
msgstr "BBR"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:187
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:234
|
||||
msgid "BaiDu"
|
||||
msgstr "百度"
|
||||
|
||||
@@ -264,7 +264,7 @@ msgstr "二进制文件"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:453
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:1410
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:864
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:862
|
||||
msgid "Bind interface"
|
||||
msgstr "绑定接口"
|
||||
|
||||
@@ -278,7 +278,7 @@ msgstr "绑定出站流量至指定端口。留空自动检测。"
|
||||
msgid "BitTorrent"
|
||||
msgstr "BitTorrent"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1368
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1363
|
||||
msgid "Blacklist mode"
|
||||
msgstr "黑名单模式"
|
||||
|
||||
@@ -290,7 +290,7 @@ msgstr "封锁"
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:640
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:1090
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:1100
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:859
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:857
|
||||
msgid "Both"
|
||||
msgstr "全部"
|
||||
|
||||
@@ -318,12 +318,12 @@ msgstr "CNNIC 公共 DNS(210.2.4.8)"
|
||||
msgid "CUBIC"
|
||||
msgstr "CUBIC"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1238
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1233
|
||||
msgid "Cancel"
|
||||
msgstr "取消"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1073
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:748
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:746
|
||||
msgid "Certificate path"
|
||||
msgstr "证书路径"
|
||||
|
||||
@@ -339,15 +339,15 @@ msgstr "检查更新"
|
||||
msgid "China DNS server"
|
||||
msgstr "国内 DNS 服务器"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:197
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:243
|
||||
msgid "China IPv4 list version"
|
||||
msgstr "国内 IPv4 库版本"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:201
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:247
|
||||
msgid "China IPv6 list version"
|
||||
msgstr "国内 IPv6 库版本"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:205
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:251
|
||||
msgid "China list version"
|
||||
msgstr "国内域名列表版本"
|
||||
|
||||
@@ -364,7 +364,7 @@ msgstr "密码套件"
|
||||
msgid "Cisco Public DNS (208.67.222.222)"
|
||||
msgstr "思科公共 DNS(208.67.222.222)"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:166
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:213
|
||||
msgid "Clean log"
|
||||
msgstr "清空日志"
|
||||
|
||||
@@ -390,7 +390,7 @@ msgstr "Cloudflare"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:114
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:122
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:129
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:175
|
||||
msgid "Collecting data..."
|
||||
msgstr "正在收集数据中..."
|
||||
|
||||
@@ -403,7 +403,7 @@ msgstr "仅常用端口(绕过 P2P 流量)"
|
||||
msgid "Congestion control algorithm"
|
||||
msgstr "拥塞控制算法"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:184
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:231
|
||||
msgid "Connection check"
|
||||
msgstr "连接检查"
|
||||
|
||||
@@ -450,6 +450,10 @@ msgstr "DNS01 验证"
|
||||
msgid "DTLS"
|
||||
msgstr "DTLS"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:136
|
||||
msgid "Debug"
|
||||
msgstr "调试"
|
||||
|
||||
#: htdocs/luci-static/resources/homeproxy.js:17
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:433
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:603
|
||||
@@ -493,7 +497,7 @@ msgstr "默认出站 DNS"
|
||||
msgid "Default outbound for connections not matched by any routing rules."
|
||||
msgstr "用于未被任何路由规则匹配的连接的默认出站。"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1388
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1383
|
||||
msgid "Default packet encoding"
|
||||
msgstr "默认包封装格式"
|
||||
|
||||
@@ -534,8 +538,8 @@ msgstr "直连 MAC 地址"
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:498
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:554
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:566
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1117
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1367
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1111
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1362
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:267
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:279
|
||||
msgid "Disable"
|
||||
@@ -658,7 +662,7 @@ msgstr "丢弃数据包"
|
||||
msgid "Drop requests"
|
||||
msgstr "丢弃请求"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1374
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1369
|
||||
msgid ""
|
||||
"Drop/keep nodes that contain the specific keywords. <a target=\"_blank\" "
|
||||
"href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/"
|
||||
@@ -668,7 +672,7 @@ msgstr ""
|
||||
"developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Regular_Expressions\">"
|
||||
"正则表达式</a>。"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1366
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1361
|
||||
msgid "Drop/keep specific nodes from subscriptions."
|
||||
msgstr "从订阅中 丢弃/保留 指定节点"
|
||||
|
||||
@@ -683,7 +687,7 @@ msgstr ""
|
||||
"<br/>外部帐户绑定“用于将 ACME 帐户与非 ACME 系统中的现有帐户相关联,例如 CA "
|
||||
"客户数据库。"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1091
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1090
|
||||
msgid ""
|
||||
"ECH (Encrypted Client Hello) is a TLS extension that allows a client to "
|
||||
"encrypt the first part of its ClientHello message."
|
||||
@@ -691,16 +695,16 @@ msgstr ""
|
||||
"ECH(Encrypted Client Hello)是一个 TLS 扩展,它允许客户端加密其 ClientHello "
|
||||
"信息的第一部分。"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1110
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:825
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1105
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:823
|
||||
msgid "ECH config"
|
||||
msgstr "ECH 配置"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1099
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1094
|
||||
msgid "ECH config path"
|
||||
msgstr "ECH 配置路径"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:786
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:784
|
||||
msgid "ECH key"
|
||||
msgstr "ECH 密钥"
|
||||
|
||||
@@ -724,7 +728,7 @@ msgstr "前置数据标头"
|
||||
msgid "Early data is sent in path instead of header by default."
|
||||
msgstr "前置数据默认发送在路径而不是标头中。"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1210
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1205
|
||||
msgid "Edit nodes"
|
||||
msgstr "修改节点"
|
||||
|
||||
@@ -761,14 +765,10 @@ msgstr "启用 0-RTT 握手"
|
||||
msgid "Enable ACME"
|
||||
msgstr "启用 ACME"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1090
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1089
|
||||
msgid "Enable ECH"
|
||||
msgstr "启用 ECH"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1095
|
||||
msgid "Enable PQ signature schemes"
|
||||
msgstr "启用 PQ 签名方案"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:980
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:522
|
||||
msgid "Enable TCP Brutal"
|
||||
@@ -779,8 +779,8 @@ msgstr "启用 TCP Brutal"
|
||||
msgid "Enable TCP Brutal congestion control algorithm"
|
||||
msgstr "启用 TCP Brutal 拥塞控制算法。"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1167
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:845
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1162
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:843
|
||||
msgid "Enable UDP fragmentation."
|
||||
msgstr "启用 UDP 分片。"
|
||||
|
||||
@@ -793,11 +793,11 @@ msgstr "启用端点独立 NAT"
|
||||
msgid "Enable padding"
|
||||
msgstr "启用填充"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:836
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:834
|
||||
msgid "Enable tcp fast open for listener."
|
||||
msgstr "为监听器启用 TCP 快速打开。"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1171
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1166
|
||||
msgid ""
|
||||
"Enable the SUoT protocol, requires server support. Conflict with multiplex."
|
||||
msgstr "启用 SUoT 协议,需要服务端支持。与多路复用冲突。"
|
||||
@@ -808,6 +808,10 @@ msgstr "启用 SUoT 协议,需要服务端支持。与多路复用冲突。"
|
||||
msgid "Encrypt method"
|
||||
msgstr "加密方式"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:139
|
||||
msgid "Error"
|
||||
msgstr "错误"
|
||||
|
||||
#: htdocs/luci-static/resources/homeproxy.js:237
|
||||
#: htdocs/luci-static/resources/homeproxy.js:271
|
||||
#: htdocs/luci-static/resources/homeproxy.js:279
|
||||
@@ -832,10 +836,10 @@ msgstr "加密方式"
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:1505
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:1537
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:487
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1133
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1301
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1355
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1358
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1127
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1296
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1350
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1353
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:226
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:628
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:630
|
||||
@@ -866,11 +870,15 @@ msgstr "生成 %s 失败,错误:%s。"
|
||||
msgid "Failed to upload %s, error: %s."
|
||||
msgstr "上传 %s 失败,错误:%s。"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1373
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:140
|
||||
msgid "Fatal"
|
||||
msgstr "致命"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1368
|
||||
msgid "Filter keywords"
|
||||
msgstr "过滤关键词"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1365
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1360
|
||||
msgid "Filter nodes"
|
||||
msgstr "过滤节点"
|
||||
|
||||
@@ -912,7 +920,7 @@ msgstr "分片回退延迟"
|
||||
msgid "GET"
|
||||
msgstr "GET"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:209
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:255
|
||||
msgid "GFW list version"
|
||||
msgstr "GFW 域名列表版本"
|
||||
|
||||
@@ -938,11 +946,11 @@ msgstr "游戏模式 MAC 地址"
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:294
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:355
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:357
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:817
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:815
|
||||
msgid "Generate"
|
||||
msgstr "生成"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:213
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:259
|
||||
msgid "GitHub token"
|
||||
msgstr "GitHub 令牌"
|
||||
|
||||
@@ -970,7 +978,7 @@ msgstr "全局代理 MAC 地址"
|
||||
msgid "Global settings"
|
||||
msgstr "全局设置"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:190
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:237
|
||||
msgid "Google"
|
||||
msgstr "谷歌"
|
||||
|
||||
@@ -1010,11 +1018,11 @@ msgstr "HTTPS"
|
||||
msgid "HTTPUpgrade"
|
||||
msgstr "HTTPUpgrade"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:735
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:734
|
||||
msgid "Handshake server address"
|
||||
msgstr "握手服务器地址"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:741
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:740
|
||||
msgid "Handshake server port"
|
||||
msgstr "握手服务器端口"
|
||||
|
||||
@@ -1030,7 +1038,7 @@ msgstr "心跳间隔"
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:55
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:57
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:101
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:234
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:280
|
||||
#: root/usr/share/luci/menu.d/luci-app-homeproxy.json:3
|
||||
msgid "HomeProxy"
|
||||
msgstr "HomeProxy"
|
||||
@@ -1177,18 +1185,18 @@ msgstr "如果你拥有根证书,使用此选项而不是允许不安全连接
|
||||
msgid "Ignore client bandwidth"
|
||||
msgstr "忽略客户端带宽"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1284
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1279
|
||||
msgid "Import"
|
||||
msgstr "导入"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1231
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1310
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1312
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1226
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1305
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1307
|
||||
msgid "Import share links"
|
||||
msgstr "导入分享链接"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:336
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:850
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:848
|
||||
msgid "In seconds."
|
||||
msgstr "单位:秒。"
|
||||
|
||||
@@ -1211,6 +1219,10 @@ msgstr "在检查中,关闭空闲时间超过此值的会话(单位:秒)
|
||||
msgid "Independent cache per server"
|
||||
msgstr "独立缓存"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:137
|
||||
msgid "Info"
|
||||
msgstr "信息"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:1403
|
||||
msgid "Interface Control"
|
||||
msgstr "接口控制"
|
||||
@@ -1244,7 +1256,7 @@ msgstr "反转"
|
||||
msgid "Invert match result."
|
||||
msgstr "反转匹配结果"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:767
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:765
|
||||
msgid "Key path"
|
||||
msgstr "证书路径"
|
||||
|
||||
@@ -1319,7 +1331,7 @@ msgstr "监听接口"
|
||||
msgid "Listen port"
|
||||
msgstr "监听端口"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:127
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:173
|
||||
msgid "Loading"
|
||||
msgstr "加载中"
|
||||
|
||||
@@ -1331,11 +1343,11 @@ msgstr "本地"
|
||||
msgid "Local address"
|
||||
msgstr "本地地址"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:144
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:190
|
||||
msgid "Log file does not exist."
|
||||
msgstr "日志文件不存在。"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:137
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:183
|
||||
msgid "Log is empty."
|
||||
msgstr "日志为空。"
|
||||
|
||||
@@ -1480,7 +1492,7 @@ msgstr "最大下载速度"
|
||||
msgid "Max download speed in Mbps."
|
||||
msgstr "最大下载速度(Mbps)。"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:730
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:729
|
||||
msgid "Max time difference"
|
||||
msgstr "最大时间差"
|
||||
|
||||
@@ -1558,8 +1570,8 @@ msgstr "混合<code>系统</code> TCP 栈和 <code>gVisor</code> UDP 栈。"
|
||||
msgid "Mode"
|
||||
msgstr "模式"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1163
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:840
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1158
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:838
|
||||
msgid "MultiPath TCP"
|
||||
msgstr "多路径 TCP(MPTCP)"
|
||||
|
||||
@@ -1577,7 +1589,7 @@ msgstr "多路复用协议。"
|
||||
msgid "NOT RUNNING"
|
||||
msgstr "未运行"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1394
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1389
|
||||
msgid "NOTE: Save current settings before updating subscriptions."
|
||||
msgstr "注意:更新订阅前先保存当前配置。"
|
||||
|
||||
@@ -1595,7 +1607,7 @@ msgstr "NaïveProxy"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:637
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:1097
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:856
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:854
|
||||
msgid "Network"
|
||||
msgstr "网络"
|
||||
|
||||
@@ -1615,15 +1627,15 @@ msgstr "无 TCP 传输层, 纯 HTTP 已合并到 HTTP 传输层。"
|
||||
msgid "No additional encryption support: It's basically duplicate encryption."
|
||||
msgstr "无额外加密支持:它基本上是重复加密。"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1410
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1405
|
||||
msgid "No subscription available"
|
||||
msgstr "无可用订阅"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1435
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1430
|
||||
msgid "No subscription node"
|
||||
msgstr "无订阅节点"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1270
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1265
|
||||
msgid "No valid share link found."
|
||||
msgstr "找不到有效分享链接。"
|
||||
|
||||
@@ -1636,7 +1648,7 @@ msgstr "节点"
|
||||
msgid "Node Settings"
|
||||
msgstr "节点设置"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1216
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1211
|
||||
msgid "Nodes"
|
||||
msgstr "节点"
|
||||
|
||||
@@ -1718,6 +1730,10 @@ msgstr "数据包编码"
|
||||
msgid "Padding scheme"
|
||||
msgstr "填充方案"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:141
|
||||
msgid "Panic"
|
||||
msgstr "崩溃"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:463
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:190
|
||||
msgid "Password"
|
||||
@@ -1930,21 +1946,21 @@ msgstr "RDP"
|
||||
msgid "RDRC timeout"
|
||||
msgstr "RDRC 超时"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1144
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:715
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1138
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:714
|
||||
msgid "REALITY"
|
||||
msgstr "REALITY"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:720
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:719
|
||||
msgid "REALITY private key"
|
||||
msgstr "REALITY 私钥"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1148
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1143
|
||||
msgid "REALITY public key"
|
||||
msgstr "REALITY 公钥"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1153
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:725
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1148
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:724
|
||||
msgid "REALITY short ID"
|
||||
msgstr "REALITY 标识符"
|
||||
|
||||
@@ -1977,7 +1993,7 @@ msgstr "Redirect TCP + TProxy UDP"
|
||||
msgid "Redirect TCP + Tun UDP"
|
||||
msgstr "Redirect TCP + Tun UDP"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:171
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:218
|
||||
msgid "Refresh every %s seconds."
|
||||
msgstr "每 %s 秒刷新。"
|
||||
|
||||
@@ -1994,11 +2010,11 @@ msgstr "拒绝"
|
||||
msgid "Remote"
|
||||
msgstr "远程"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1432
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1427
|
||||
msgid "Remove %s nodes"
|
||||
msgstr "移除 %s 个节点"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1422
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1417
|
||||
msgid "Remove all nodes from subscriptions"
|
||||
msgstr "移除所有订阅节点"
|
||||
|
||||
@@ -2022,15 +2038,15 @@ msgstr "解析"
|
||||
msgid "Resolve strategy"
|
||||
msgstr "解析策略"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:194
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:240
|
||||
msgid "Resources management"
|
||||
msgstr "资源管理"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:870
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:868
|
||||
msgid "Reuse address"
|
||||
msgstr "复用地址"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:871
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:869
|
||||
msgid "Reuse listener address."
|
||||
msgstr "复用监听地址。"
|
||||
|
||||
@@ -2112,7 +2128,7 @@ msgstr "SSH"
|
||||
msgid "STUN"
|
||||
msgstr "STUN"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1176
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1171
|
||||
msgid "SUoT version"
|
||||
msgstr "SUoT 版本"
|
||||
|
||||
@@ -2129,16 +2145,16 @@ msgstr "Salamander"
|
||||
msgid "Same as main node"
|
||||
msgstr "保持与主节点一致"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:220
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:225
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:266
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:271
|
||||
msgid "Save"
|
||||
msgstr "保存"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1396
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1391
|
||||
msgid "Save current settings"
|
||||
msgstr "保存当前设置"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1393
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1388
|
||||
msgid "Save subscriptions settings"
|
||||
msgstr "保存订阅设置"
|
||||
|
||||
@@ -2296,19 +2312,19 @@ msgstr ""
|
||||
msgid "String"
|
||||
msgstr "字符串"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1321
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1316
|
||||
msgid "Sub (%s)"
|
||||
msgstr "订阅(%s)"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1348
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1343
|
||||
msgid "Subscription URL-s"
|
||||
msgstr "订阅地址"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1332
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1327
|
||||
msgid "Subscriptions"
|
||||
msgstr "订阅"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1272
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1267
|
||||
msgid "Successfully imported %s nodes of total %s."
|
||||
msgstr "成功导入 %s 个节点,共 %s 个。"
|
||||
|
||||
@@ -2316,8 +2332,8 @@ msgstr "成功导入 %s 个节点,共 %s 个。"
|
||||
msgid "Successfully updated."
|
||||
msgstr "更新成功。"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1232
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1349
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1227
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1344
|
||||
msgid ""
|
||||
"Support Hysteria, Shadowsocks, Trojan, v2rayN (VMess), and XTLS (VLESS) "
|
||||
"online configuration delivery standard."
|
||||
@@ -2346,12 +2362,12 @@ msgstr "系统 DNS"
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:638
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:949
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:1098
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:857
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:855
|
||||
msgid "TCP"
|
||||
msgstr "TCP"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1160
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:835
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1155
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:833
|
||||
msgid "TCP fast open"
|
||||
msgstr "TCP 快速打开"
|
||||
|
||||
@@ -2567,7 +2583,7 @@ msgid ""
|
||||
"allowed to open."
|
||||
msgstr "允许对等点打开的 QUIC 并发双向流的最大数量。"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:731
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:730
|
||||
msgid "The maximum time difference between the server and the client."
|
||||
msgstr "服务器和客户端之间的最大时间差。"
|
||||
|
||||
@@ -2582,7 +2598,7 @@ msgid "The modern ImmortalWrt proxy platform for ARM64/AMD64."
|
||||
msgstr "为 ARM64/AMD64 设计的现代 ImmortalWrt 代理平台。"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:454
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:865
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:863
|
||||
msgid "The network interface to bind to."
|
||||
msgstr "绑定到的网络接口。"
|
||||
|
||||
@@ -2590,7 +2606,7 @@ msgstr "绑定到的网络接口。"
|
||||
msgid "The path of the DNS server."
|
||||
msgstr "DNS 服务器的路径。"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1100
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1095
|
||||
msgid ""
|
||||
"The path to the ECH config, in PEM format. If empty, load from DNS will be "
|
||||
"attempted."
|
||||
@@ -2612,11 +2628,11 @@ msgstr "DNS 服务器的端口。"
|
||||
msgid "The response code."
|
||||
msgstr "响应代码。"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:768
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:766
|
||||
msgid "The server private key, in PEM format."
|
||||
msgstr "服务端私钥,需要 PEM 格式。"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:749
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:747
|
||||
msgid "The server public key, in PEM format."
|
||||
msgstr "服务端公钥,需要 PEM 格式。"
|
||||
|
||||
@@ -2649,7 +2665,7 @@ msgstr ""
|
||||
"检测到任何活动,则会关闭连接。"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1039
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1384
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1379
|
||||
msgid ""
|
||||
"This is <strong>DANGEROUS</strong>, your traffic is almost like "
|
||||
"<strong>PLAIN TEXT</strong>! Use at your own risk!"
|
||||
@@ -2697,6 +2713,10 @@ msgid ""
|
||||
msgstr ""
|
||||
"要启用 Tun 支持,您需要安装 <code>ip-full</code> 和 <code>kmod-tun</code>。"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:135
|
||||
msgid "Trace"
|
||||
msgstr "跟踪"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:769
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:409
|
||||
msgid "Transport"
|
||||
@@ -2726,21 +2746,21 @@ msgstr "类型"
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:639
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:948
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:1099
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:858
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:856
|
||||
msgid "UDP"
|
||||
msgstr "UDP"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1166
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:844
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1161
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:842
|
||||
msgid "UDP Fragment"
|
||||
msgstr "UDP 分片"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:335
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:849
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:847
|
||||
msgid "UDP NAT expiration time"
|
||||
msgstr "UDP NAT 过期时间"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1170
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1165
|
||||
msgid "UDP over TCP"
|
||||
msgstr "UDP over TCP"
|
||||
|
||||
@@ -2781,15 +2801,15 @@ msgstr "UUID"
|
||||
msgid "Unknown error."
|
||||
msgstr "未知错误。"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:148
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:194
|
||||
msgid "Unknown error: %s"
|
||||
msgstr "未知错误:%s"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1137
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1131
|
||||
msgid "Unsupported fingerprint!"
|
||||
msgstr "不支持的指纹!"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1407
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1402
|
||||
msgid "Update %s subscriptions"
|
||||
msgstr "更新 %s 个订阅"
|
||||
|
||||
@@ -2805,23 +2825,23 @@ msgstr "更新间隔"
|
||||
msgid "Update interval of rule set."
|
||||
msgstr "规则集更新间隔。"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1402
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1397
|
||||
msgid "Update nodes from subscriptions"
|
||||
msgstr "从订阅更新节点"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1345
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1340
|
||||
msgid "Update subscriptions via proxy."
|
||||
msgstr "使用代理更新订阅。"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1338
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1333
|
||||
msgid "Update time"
|
||||
msgstr "更新时间"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1344
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1339
|
||||
msgid "Update via proxy"
|
||||
msgstr "使用代理更新"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1105
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1100
|
||||
msgid "Upload ECH config"
|
||||
msgstr "上传 ECH 配置"
|
||||
|
||||
@@ -2836,18 +2856,18 @@ msgid "Upload bandwidth in Mbps."
|
||||
msgstr "上传带宽(单位:Mbps)。"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1081
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:759
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:757
|
||||
msgid "Upload certificate"
|
||||
msgstr "上传证书"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:778
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:776
|
||||
msgid "Upload key"
|
||||
msgstr "上传密钥"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1084
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1108
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:762
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:781
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1103
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:760
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:779
|
||||
msgid "Upload..."
|
||||
msgstr "上传..."
|
||||
|
||||
@@ -2871,7 +2891,7 @@ msgstr "用于验证返回证书上的主机名。"
|
||||
msgid "User"
|
||||
msgstr "用户"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1378
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1373
|
||||
msgid "User-Agent"
|
||||
msgstr "用户代理"
|
||||
|
||||
@@ -2899,12 +2919,16 @@ msgstr "WAN DNS(从接口获取)"
|
||||
msgid "WAN IP Policy"
|
||||
msgstr "WAN IP 策略"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:138
|
||||
msgid "Warn"
|
||||
msgstr "警告"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:776
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:416
|
||||
msgid "WebSocket"
|
||||
msgstr "WebSocket"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1369
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1364
|
||||
msgid "Whitelist mode"
|
||||
msgstr "白名单模式"
|
||||
|
||||
@@ -2929,7 +2953,7 @@ msgid "Write proxy protocol in the connection header."
|
||||
msgstr "在连接头中写入代理协议。"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:885
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1391
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1386
|
||||
msgid "Xudp (Xray-core)"
|
||||
msgstr "Xudp (Xray-core)"
|
||||
|
||||
@@ -2942,7 +2966,7 @@ msgid "ZeroSSL"
|
||||
msgstr "ZeroSSL"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1086
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:764
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:762
|
||||
msgid "certificate"
|
||||
msgstr "证书"
|
||||
|
||||
@@ -2986,19 +3010,19 @@ msgstr "gVisor"
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:504
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:1355
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:487
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1133
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1127
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:226
|
||||
msgid "non-empty value"
|
||||
msgstr "非空值"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:628
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:883
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1389
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1384
|
||||
msgid "none"
|
||||
msgstr "无"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:884
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1390
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1385
|
||||
msgid "packet addr (v2ray-core v5+)"
|
||||
msgstr "packet addr (v2ray-core v5+)"
|
||||
|
||||
@@ -3006,7 +3030,7 @@ msgstr "packet addr (v2ray-core v5+)"
|
||||
msgid "passed"
|
||||
msgstr "通过"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:783
|
||||
#: htdocs/luci-static/resources/view/homeproxy/server.js:781
|
||||
msgid "private key"
|
||||
msgstr "私钥"
|
||||
|
||||
@@ -3014,19 +3038,19 @@ msgstr "私钥"
|
||||
msgid "quic-go / uquic chrome"
|
||||
msgstr "quic-go / uquic chrome"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:237
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:283
|
||||
msgid "sing-box client"
|
||||
msgstr "sing-box 客户端"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:240
|
||||
#: htdocs/luci-static/resources/view/homeproxy/status.js:286
|
||||
msgid "sing-box server"
|
||||
msgstr "sing-box 服务端"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1115
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1109
|
||||
msgid "uTLS fingerprint"
|
||||
msgstr "uTLS 指纹"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1116
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1110
|
||||
msgid ""
|
||||
"uTLS is a fork of \"crypto/tls\", which provides ClientHello fingerprinting "
|
||||
"resistance."
|
||||
@@ -3038,7 +3062,7 @@ msgid "unchecked"
|
||||
msgstr "未检查"
|
||||
|
||||
#: htdocs/luci-static/resources/homeproxy.js:237
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1301
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1296
|
||||
msgid "unique UCI identifier"
|
||||
msgstr "独立 UCI 标识"
|
||||
|
||||
@@ -3048,13 +3072,13 @@ msgstr "独立值"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:499
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:642
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1177
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1172
|
||||
msgid "v1"
|
||||
msgstr "v1"
|
||||
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:500
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:643
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1178
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1173
|
||||
msgid "v2"
|
||||
msgstr "v2"
|
||||
|
||||
@@ -3073,8 +3097,8 @@ msgstr "有效 DNS 服务器地址"
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:521
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:1360
|
||||
#: htdocs/luci-static/resources/view/homeproxy/client.js:1363
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1355
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1358
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1350
|
||||
#: htdocs/luci-static/resources/view/homeproxy/node.js:1353
|
||||
msgid "valid URL"
|
||||
msgstr "有效网址"
|
||||
|
||||
|
@@ -31,6 +31,7 @@ config homeproxy 'config'
|
||||
option proxy_mode 'redirect_tproxy'
|
||||
option ipv6_support '1'
|
||||
option github_token ''
|
||||
option log_level 'warn'
|
||||
|
||||
config homeproxy 'control'
|
||||
option lan_proxy_mode 'disabled'
|
||||
@@ -53,6 +54,7 @@ config homeproxy 'control'
|
||||
config homeproxy 'routing'
|
||||
option sniff_override '1'
|
||||
option default_outbound 'direct-out'
|
||||
option default_outbound_dns 'default-dns'
|
||||
|
||||
config homeproxy 'dns'
|
||||
option dns_strategy 'prefer_ipv4'
|
||||
@@ -71,11 +73,5 @@ config homeproxy 'subscription'
|
||||
|
||||
config homeproxy 'server'
|
||||
option enabled '0'
|
||||
|
||||
config dns_rule 'nodes_domain'
|
||||
option label 'NodesDomain'
|
||||
option enabled '1'
|
||||
option mode 'default'
|
||||
list outbound 'any-out'
|
||||
option server 'default-dns'
|
||||
option log_level 'warn'
|
||||
|
||||
|
@@ -13,6 +13,7 @@
|
||||
1.116.0.0/15
|
||||
1.118.2.0/24
|
||||
1.118.32.0/22
|
||||
1.118.36.0/24
|
||||
1.119.0.0/17
|
||||
1.119.128.0/18
|
||||
1.119.192.0/20
|
||||
@@ -126,6 +127,7 @@
|
||||
36.255.128.0/22
|
||||
36.255.164.0/24
|
||||
36.255.192.0/24
|
||||
38.84.220.0/24
|
||||
38.111.220.0/23
|
||||
39.64.0.0/11
|
||||
39.96.0.0/13
|
||||
@@ -198,7 +200,8 @@
|
||||
42.240.8.0/22
|
||||
42.240.12.0/24
|
||||
42.240.16.0/24
|
||||
42.240.20.0/24
|
||||
42.240.20.0/23
|
||||
42.240.22.0/24
|
||||
42.240.128.0/17
|
||||
42.242.0.0/15
|
||||
42.244.0.0/14
|
||||
@@ -209,10 +212,13 @@
|
||||
43.102.152.0/22
|
||||
43.136.0.0/13
|
||||
43.144.0.0/15
|
||||
43.176.0.0/14
|
||||
43.180.0.0/16
|
||||
43.192.0.0/16
|
||||
43.193.0.0/18
|
||||
43.193.64.0/24
|
||||
43.194.0.0/20
|
||||
43.194.16.0/24
|
||||
43.195.0.0/20
|
||||
43.196.0.0/16
|
||||
43.224.12.0/22
|
||||
@@ -404,8 +410,7 @@
|
||||
45.116.152.0/22
|
||||
45.116.208.0/22
|
||||
45.117.8.0/22
|
||||
45.117.68.0/24
|
||||
45.117.70.0/23
|
||||
45.117.68.0/22
|
||||
45.119.60.0/22
|
||||
45.119.68.0/22
|
||||
45.119.105.0/24
|
||||
@@ -444,7 +449,7 @@
|
||||
45.249.212.0/22
|
||||
45.250.32.0/21
|
||||
45.250.40.0/22
|
||||
45.250.152.0/24
|
||||
45.250.152.0/23
|
||||
45.250.180.0/23
|
||||
45.250.184.0/22
|
||||
45.250.188.0/24
|
||||
@@ -513,7 +518,6 @@
|
||||
49.4.124.0/23
|
||||
49.4.126.0/24
|
||||
49.4.128.0/22
|
||||
49.5.13.0/24
|
||||
49.7.0.0/16
|
||||
49.52.0.0/14
|
||||
49.64.0.0/11
|
||||
@@ -557,7 +561,7 @@
|
||||
54.222.48.0/21
|
||||
54.222.57.0/24
|
||||
54.222.60.0/22
|
||||
54.222.64.0/24
|
||||
54.222.64.0/23
|
||||
54.222.70.0/23
|
||||
54.222.72.0/21
|
||||
54.222.80.0/21
|
||||
@@ -842,6 +846,7 @@
|
||||
101.132.0.0/15
|
||||
101.197.0.0/16
|
||||
101.198.0.0/22
|
||||
101.198.4.0/24
|
||||
101.198.160.0/19
|
||||
101.198.192.0/19
|
||||
101.199.48.0/20
|
||||
@@ -882,6 +887,8 @@
|
||||
101.251.0.0/22
|
||||
101.251.80.0/20
|
||||
101.251.128.0/19
|
||||
101.251.160.0/20
|
||||
101.251.176.0/22
|
||||
101.251.192.0/18
|
||||
101.254.0.0/20
|
||||
101.254.32.0/19
|
||||
@@ -1101,6 +1108,7 @@
|
||||
103.73.204.0/22
|
||||
103.74.24.0/21
|
||||
103.74.48.0/22
|
||||
103.74.80.0/22
|
||||
103.75.107.0/24
|
||||
103.75.152.0/22
|
||||
103.76.60.0/22
|
||||
@@ -1116,6 +1124,7 @@
|
||||
103.79.24.0/22
|
||||
103.79.120.0/22
|
||||
103.79.200.0/22
|
||||
103.79.228.0/24
|
||||
103.81.4.0/22
|
||||
103.81.48.0/22
|
||||
103.81.72.0/22
|
||||
@@ -1182,7 +1191,6 @@
|
||||
103.102.214.0/24
|
||||
103.103.12.0/24
|
||||
103.103.36.0/24
|
||||
103.103.200.0/22
|
||||
103.104.252.0/22
|
||||
103.105.0.0/22
|
||||
103.105.12.0/22
|
||||
@@ -1235,7 +1243,7 @@
|
||||
103.123.4.0/23
|
||||
103.125.236.0/22
|
||||
103.126.1.0/24
|
||||
103.126.18.0/23
|
||||
103.126.19.0/24
|
||||
103.126.101.0/24
|
||||
103.126.102.0/23
|
||||
103.126.124.0/22
|
||||
@@ -1315,7 +1323,6 @@
|
||||
103.177.44.0/24
|
||||
103.179.78.0/23
|
||||
103.180.108.0/23
|
||||
103.181.164.0/23
|
||||
103.181.234.0/24
|
||||
103.183.66.0/23
|
||||
103.183.122.0/23
|
||||
@@ -1392,6 +1399,7 @@
|
||||
103.215.36.0/22
|
||||
103.215.44.0/24
|
||||
103.215.140.0/22
|
||||
103.216.136.0/22
|
||||
103.216.152.0/22
|
||||
103.216.252.0/23
|
||||
103.217.184.0/21
|
||||
@@ -1425,6 +1433,7 @@
|
||||
103.227.80.0/22
|
||||
103.227.120.0/22
|
||||
103.227.136.0/22
|
||||
103.227.228.0/22
|
||||
103.228.12.0/22
|
||||
103.228.136.0/22
|
||||
103.228.160.0/22
|
||||
@@ -1713,7 +1722,6 @@
|
||||
110.236.0.0/15
|
||||
110.240.0.0/12
|
||||
111.0.0.0/10
|
||||
111.67.192.0/20
|
||||
111.72.0.0/13
|
||||
111.85.0.0/16
|
||||
111.112.0.0/14
|
||||
@@ -1787,11 +1795,7 @@
|
||||
113.45.112.0/22
|
||||
113.45.120.0/22
|
||||
113.45.128.0/17
|
||||
113.46.0.0/17
|
||||
113.46.128.0/18
|
||||
113.46.192.0/19
|
||||
113.46.224.0/20
|
||||
113.46.240.0/21
|
||||
113.46.0.0/16
|
||||
113.47.0.0/18
|
||||
113.47.64.0/19
|
||||
113.47.96.0/21
|
||||
@@ -1900,7 +1904,11 @@
|
||||
114.112.200.0/21
|
||||
114.112.208.0/20
|
||||
114.113.63.0/24
|
||||
114.113.64.0/18
|
||||
114.113.64.0/20
|
||||
114.113.80.0/22
|
||||
114.113.84.0/24
|
||||
114.113.88.0/21
|
||||
114.113.96.0/19
|
||||
114.113.144.0/20
|
||||
114.113.196.0/22
|
||||
114.113.200.0/24
|
||||
@@ -1961,10 +1969,10 @@
|
||||
115.175.224.0/20
|
||||
115.182.0.0/15
|
||||
115.190.0.0/17
|
||||
115.190.128.0/19
|
||||
115.190.128.0/18
|
||||
115.190.192.0/20
|
||||
115.192.0.0/11
|
||||
115.224.0.0/12
|
||||
116.0.81.0/24
|
||||
116.0.89.0/24
|
||||
116.1.0.0/16
|
||||
116.2.0.0/15
|
||||
@@ -2253,8 +2261,8 @@
|
||||
118.194.128.0/21
|
||||
118.194.240.0/21
|
||||
118.195.128.0/17
|
||||
118.196.0.0/19
|
||||
118.196.32.0/20
|
||||
118.196.0.0/18
|
||||
118.196.64.0/19
|
||||
118.199.0.0/16
|
||||
118.202.0.0/15
|
||||
118.212.0.0/15
|
||||
@@ -2704,7 +2712,6 @@
|
||||
124.64.0.0/15
|
||||
124.66.0.0/17
|
||||
124.67.0.0/16
|
||||
124.68.252.0/23
|
||||
124.70.0.0/16
|
||||
124.71.0.0/17
|
||||
124.71.128.0/18
|
||||
@@ -2773,7 +2780,10 @@
|
||||
125.112.0.0/12
|
||||
125.171.0.0/16
|
||||
125.208.0.0/19
|
||||
125.208.32.0/20
|
||||
125.208.32.0/21
|
||||
125.208.40.0/22
|
||||
125.208.44.0/23
|
||||
125.208.46.0/24
|
||||
125.208.49.0/24
|
||||
125.210.0.0/15
|
||||
125.213.32.0/20
|
||||
@@ -2953,6 +2963,10 @@
|
||||
154.72.44.0/24
|
||||
154.72.47.0/24
|
||||
154.89.32.0/20
|
||||
154.89.49.0/24
|
||||
154.89.50.0/23
|
||||
154.89.52.0/22
|
||||
154.89.56.0/21
|
||||
154.91.158.0/23
|
||||
154.208.140.0/22
|
||||
154.208.144.0/20
|
||||
@@ -2972,7 +2986,9 @@
|
||||
155.102.26.0/23
|
||||
155.102.28.0/22
|
||||
155.102.32.0/19
|
||||
155.102.64.0/23
|
||||
155.102.72.0/24
|
||||
155.102.98.0/23
|
||||
155.102.111.0/24
|
||||
155.102.112.0/21
|
||||
155.102.120.0/23
|
||||
@@ -2986,8 +3002,12 @@
|
||||
155.102.164.0/23
|
||||
155.102.166.0/24
|
||||
155.102.168.0/23
|
||||
155.102.171.0/24
|
||||
155.102.174.0/23
|
||||
155.102.176.0/23
|
||||
155.102.178.0/24
|
||||
155.102.180.0/22
|
||||
155.102.184.0/21
|
||||
155.102.193.0/24
|
||||
155.102.194.0/23
|
||||
155.102.196.0/23
|
||||
@@ -2995,16 +3015,12 @@
|
||||
155.102.200.0/23
|
||||
155.102.202.0/24
|
||||
155.102.204.0/23
|
||||
155.102.207.0/24
|
||||
155.102.208.0/23
|
||||
155.102.211.0/24
|
||||
155.102.216.0/22
|
||||
155.102.220.0/23
|
||||
155.102.224.0/20
|
||||
155.102.240.0/23
|
||||
155.102.242.0/24
|
||||
155.102.247.0/24
|
||||
155.102.248.0/23
|
||||
155.102.253.0/24
|
||||
155.102.224.0/19
|
||||
155.126.176.0/23
|
||||
156.59.108.0/24
|
||||
156.59.202.0/23
|
||||
@@ -3023,7 +3039,6 @@
|
||||
157.0.0.0/16
|
||||
157.10.34.0/24
|
||||
157.10.105.0/24
|
||||
157.15.74.0/23
|
||||
157.15.94.0/23
|
||||
157.15.104.0/23
|
||||
157.18.0.0/16
|
||||
@@ -3088,10 +3103,10 @@
|
||||
163.181.40.0/24
|
||||
163.181.42.0/23
|
||||
163.181.44.0/22
|
||||
163.181.48.0/23
|
||||
163.181.50.0/24
|
||||
163.181.48.0/22
|
||||
163.181.52.0/24
|
||||
163.181.56.0/22
|
||||
163.181.56.0/23
|
||||
163.181.58.0/24
|
||||
163.181.60.0/23
|
||||
163.181.66.0/23
|
||||
163.181.69.0/24
|
||||
@@ -3137,9 +3152,9 @@
|
||||
163.181.192.0/23
|
||||
163.181.196.0/22
|
||||
163.181.200.0/21
|
||||
163.181.209.0/24
|
||||
163.181.210.0/23
|
||||
163.181.212.0/22
|
||||
163.181.213.0/24
|
||||
163.181.214.0/23
|
||||
163.181.216.0/21
|
||||
163.181.224.0/23
|
||||
163.181.228.0/22
|
||||
@@ -3157,10 +3172,6 @@
|
||||
166.111.0.0/16
|
||||
167.139.0.0/16
|
||||
167.220.244.0/22
|
||||
168.159.144.0/21
|
||||
168.159.152.0/22
|
||||
168.159.156.0/23
|
||||
168.159.158.0/24
|
||||
168.160.0.0/17
|
||||
168.160.152.0/24
|
||||
168.160.158.0/23
|
||||
@@ -3183,6 +3194,8 @@
|
||||
175.16.0.0/13
|
||||
175.24.0.0/15
|
||||
175.27.0.0/16
|
||||
175.29.107.0/24
|
||||
175.29.108.0/22
|
||||
175.30.0.0/15
|
||||
175.42.0.0/15
|
||||
175.44.0.0/16
|
||||
@@ -3263,6 +3276,7 @@
|
||||
180.233.0.0/18
|
||||
180.235.64.0/21
|
||||
180.235.72.0/23
|
||||
181.233.128.0/22
|
||||
182.18.5.0/24
|
||||
182.18.32.0/19
|
||||
182.18.72.0/21
|
||||
@@ -3282,7 +3296,7 @@
|
||||
182.61.128.0/19
|
||||
182.61.192.0/22
|
||||
182.61.200.0/21
|
||||
182.61.216.0/21
|
||||
182.61.208.0/20
|
||||
182.61.224.0/19
|
||||
182.80.0.0/13
|
||||
182.88.0.0/14
|
||||
@@ -3321,8 +3335,6 @@
|
||||
185.234.212.0/24
|
||||
188.131.128.0/17
|
||||
192.55.46.0/24
|
||||
192.55.68.0/22
|
||||
192.102.204.0/23
|
||||
192.140.160.0/19
|
||||
192.140.208.0/21
|
||||
192.144.128.0/17
|
||||
@@ -3335,8 +3347,10 @@
|
||||
193.119.10.0/23
|
||||
193.119.12.0/23
|
||||
193.119.15.0/24
|
||||
193.119.17.0/24
|
||||
193.119.19.0/24
|
||||
193.119.20.0/23
|
||||
193.119.22.0/24
|
||||
193.119.25.0/24
|
||||
193.119.28.0/24
|
||||
193.119.30.0/24
|
||||
@@ -3347,7 +3361,6 @@
|
||||
194.138.202.0/23
|
||||
194.138.245.0/24
|
||||
195.114.203.0/24
|
||||
198.175.100.0/22
|
||||
198.208.17.0/24
|
||||
198.208.19.0/24
|
||||
198.208.30.0/24
|
||||
@@ -3658,6 +3671,7 @@
|
||||
203.86.60.0/23
|
||||
203.86.62.0/24
|
||||
203.86.64.0/19
|
||||
203.86.112.0/24
|
||||
203.86.116.0/24
|
||||
203.86.254.0/23
|
||||
203.88.32.0/19
|
||||
@@ -4088,6 +4102,7 @@
|
||||
211.167.176.0/20
|
||||
211.167.224.0/19
|
||||
212.64.0.0/17
|
||||
212.100.186.0/24
|
||||
212.129.128.0/17
|
||||
218.0.0.0/11
|
||||
218.56.0.0/13
|
||||
@@ -4342,7 +4357,10 @@
|
||||
222.126.128.0/22
|
||||
222.126.132.0/23
|
||||
222.126.140.0/22
|
||||
222.126.144.0/20
|
||||
222.126.144.0/24
|
||||
222.126.146.0/23
|
||||
222.126.148.0/22
|
||||
222.126.152.0/21
|
||||
222.126.160.0/20
|
||||
222.126.176.0/21
|
||||
222.126.184.0/22
|
||||
|
@@ -1 +1 @@
|
||||
20250822033443
|
||||
20250912032015
|
||||
|
@@ -40,6 +40,7 @@
|
||||
2400:7fc0:2a0::/44
|
||||
2400:7fc0:2c0::/44
|
||||
2400:7fc0:4000::/40
|
||||
2400:7fc0:4100::/48
|
||||
2400:7fc0:6000::/40
|
||||
2400:7fc0:8000::/36
|
||||
2400:7fc0:a000::/36
|
||||
@@ -131,6 +132,7 @@
|
||||
2401:3480:3000::/36
|
||||
2401:34a0::/31
|
||||
2401:3800::/32
|
||||
2401:5560:1000::/48
|
||||
2401:5c20:10::/48
|
||||
2401:70e0::/32
|
||||
2401:71c0::/48
|
||||
@@ -171,9 +173,8 @@
|
||||
2401:f860:86::/47
|
||||
2401:f860:88::/47
|
||||
2401:f860:90::/46
|
||||
2401:f860:94::/48
|
||||
2401:f860:94::/47
|
||||
2401:f860:100::/40
|
||||
2401:f860:f100::/40
|
||||
2401:fa00:40::/43
|
||||
2402:840:d000::/46
|
||||
2402:840:e000::/46
|
||||
@@ -194,9 +195,7 @@
|
||||
2402:6f40:2::/48
|
||||
2402:7d80::/48
|
||||
2402:7d80:240::/47
|
||||
2402:7d80:6666::/48
|
||||
2402:7d80:8888::/48
|
||||
2402:7d80:9999::/48
|
||||
2402:8bc0::/32
|
||||
2402:8cc0::/40
|
||||
2402:8cc0:200::/40
|
||||
@@ -341,6 +340,8 @@
|
||||
2404:2280:1f0::/45
|
||||
2404:2280:1f8::/46
|
||||
2404:2280:1fd::/48
|
||||
2404:2280:1fe::/48
|
||||
2404:2280:201::/48
|
||||
2404:2280:202::/47
|
||||
2404:2280:204::/46
|
||||
2404:2280:208::/46
|
||||
@@ -349,18 +350,22 @@
|
||||
2404:2280:210::/46
|
||||
2404:2280:214::/48
|
||||
2404:2280:216::/47
|
||||
2404:2280:218::/48
|
||||
2404:2280:21a::/48
|
||||
2404:2280:218::/46
|
||||
2404:2280:21d::/48
|
||||
2404:2280:221::/48
|
||||
2404:2280:259::/48
|
||||
2404:2280:25a::/47
|
||||
2404:2280:25c::/48
|
||||
2404:2280:265::/48
|
||||
2404:2280:266::/47
|
||||
2404:2280:268::/46
|
||||
2404:2280:26c::/48
|
||||
2404:2280:271::/48
|
||||
2404:2280:268::/45
|
||||
2404:2280:270::/47
|
||||
2404:2280:272::/48
|
||||
2404:2280:274::/48
|
||||
2404:2280:27a::/48
|
||||
2404:2280:27c::/47
|
||||
2404:2280:27f::/48
|
||||
2404:2280:282::/48
|
||||
2404:3700::/48
|
||||
2404:4dc0::/32
|
||||
2404:6380::/48
|
||||
@@ -391,6 +396,7 @@
|
||||
2404:c2c0:2c0::/44
|
||||
2404:c2c0:501::/48
|
||||
2404:c2c0:4000::/40
|
||||
2404:c2c0:4100::/48
|
||||
2404:c2c0:6000::/40
|
||||
2404:c2c0:8000::/36
|
||||
2404:c2c0:bb00::/40
|
||||
@@ -447,9 +453,9 @@
|
||||
2406:840:e230::/44
|
||||
2406:840:e260::/48
|
||||
2406:840:e2cf::/48
|
||||
2406:840:e500::/47
|
||||
2406:840:e621::/48
|
||||
2406:840:e666::/47
|
||||
2406:840:e720::/44
|
||||
2406:840:e80f::/48
|
||||
2406:840:eb00::/46
|
||||
2406:840:eb04::/47
|
||||
@@ -595,6 +601,7 @@
|
||||
2408:8181:a000::/40
|
||||
2408:8181:a220::/44
|
||||
2408:8181:e000::/40
|
||||
2408:8182:6000::/40
|
||||
2408:8182:c000::/40
|
||||
2408:8183:4000::/40
|
||||
2408:8183:8000::/40
|
||||
@@ -737,7 +744,6 @@
|
||||
2408:8406:b4c0::/42
|
||||
2408:8406:b500::/41
|
||||
2408:8406:b580::/42
|
||||
2408:8407:500::/43
|
||||
2408:8409::/40
|
||||
2408:8409:100::/41
|
||||
2408:8409:180::/42
|
||||
@@ -1187,6 +1193,8 @@
|
||||
240e::/20
|
||||
2602:2e0:ff::/48
|
||||
2602:f7ee:ee::/48
|
||||
2602:f92a:a478::/48
|
||||
2602:f92a:dead::/48
|
||||
2602:f92a:e100::/44
|
||||
2602:f93b:400::/38
|
||||
2602:f9ba:a8::/48
|
||||
@@ -1242,28 +1250,25 @@
|
||||
2a06:3603::/32
|
||||
2a06:3604::/30
|
||||
2a06:9f81:4600::/43
|
||||
2a06:9f81:4640::/44
|
||||
2a06:a005:260::/43
|
||||
2a06:a005:280::/43
|
||||
2a06:a005:2a0::/44
|
||||
2a06:a005:8d0::/44
|
||||
2a06:a005:9c0::/48
|
||||
2a06:a005:9e0::/44
|
||||
2a06:a005:a13::/48
|
||||
2a06:a005:e9a::/48
|
||||
2a06:a005:1c40::/44
|
||||
2a09:b280:ff81::/48
|
||||
2a09:b280:ff83::/48
|
||||
2a09:b280:ff84::/47
|
||||
2a0a:2840::/30
|
||||
2a0a:2840:20::/43
|
||||
2a0a:2840:2000::/47
|
||||
2a0a:2842::/32
|
||||
2a0a:2845:aab8::/46
|
||||
2a0a:2845:d647::/48
|
||||
2a0a:2846::/48
|
||||
2a0a:6040:ec00::/40
|
||||
2a0a:6044:6600::/40
|
||||
2a0b:b87:ffb5::/48
|
||||
2a0b:2542::/48
|
||||
2a0b:4340:a6::/48
|
||||
2a0b:4b81:1001::/48
|
||||
2a0b:4e07:b8::/47
|
||||
2a0c:9a40:84e0::/48
|
||||
2a0c:9a40:9e00::/43
|
||||
@@ -1285,10 +1290,9 @@
|
||||
2a0e:aa07:e044::/48
|
||||
2a0e:aa07:e151::/48
|
||||
2a0e:aa07:e155::/48
|
||||
2a0e:aa07:e160::/47
|
||||
2a0e:aa07:e162::/48
|
||||
2a0e:aa07:e16a::/48
|
||||
2a0e:aa07:e1a0::/44
|
||||
2a0e:aa07:e1e1::/48
|
||||
2a0e:aa07:e1e2::/47
|
||||
2a0e:aa07:e1e4::/47
|
||||
2a0e:aa07:e1e6::/48
|
||||
@@ -1324,19 +1328,17 @@
|
||||
2a0f:7d07::/32
|
||||
2a0f:85c1:ba5::/48
|
||||
2a0f:85c1:ca0::/44
|
||||
2a0f:85c1:cf1::/48
|
||||
2a0f:9400:6110::/48
|
||||
2a0f:9400:7700::/48
|
||||
2a0f:ac00::/29
|
||||
2a10:2f00:15a::/48
|
||||
2a10:cc40:190::/48
|
||||
2a10:ccc0:d00::/46
|
||||
2a10:ccc0:d0a::/47
|
||||
2a10:ccc0:d0c::/47
|
||||
2a10:ccc6:66c4::/48
|
||||
2a10:ccc6:66c6::/48
|
||||
2a10:ccc6:66c9::/48
|
||||
2a10:ccc6:66c8::/47
|
||||
2a10:ccc6:66ca::/48
|
||||
2a10:ccc6:66cc::/47
|
||||
2a12:f8c3::/36
|
||||
2a13:1800::/48
|
||||
2a13:1800:10::/48
|
||||
@@ -1351,7 +1353,6 @@
|
||||
2a13:a5c7:2102::/48
|
||||
2a13:a5c7:2121::/48
|
||||
2a13:a5c7:2801::/48
|
||||
2a13:a5c7:2803::/48
|
||||
2a13:a5c7:3108::/48
|
||||
2a13:a5c7:31a0::/43
|
||||
2a13:aac4:f000::/44
|
||||
@@ -1372,9 +1373,7 @@
|
||||
2a14:67c1:a040::/47
|
||||
2a14:67c1:a061::/48
|
||||
2a14:67c1:a064::/48
|
||||
2a14:67c1:a090::/46
|
||||
2a14:67c1:a095::/48
|
||||
2a14:67c1:a096::/48
|
||||
2a14:67c1:a090::/45
|
||||
2a14:67c1:a099::/48
|
||||
2a14:67c1:a100::/43
|
||||
2a14:67c1:b000::/48
|
||||
@@ -1384,15 +1383,15 @@
|
||||
2a14:67c1:b100::/46
|
||||
2a14:67c1:b105::/48
|
||||
2a14:67c1:b107::/48
|
||||
2a14:67c1:b130::/48
|
||||
2a14:67c1:b132::/47
|
||||
2a14:67c1:b130::/46
|
||||
2a14:67c1:b134::/47
|
||||
2a14:67c1:b140::/48
|
||||
2a14:67c1:b4a1::/48
|
||||
2a14:67c1:b4a2::/48
|
||||
2a14:67c1:b4c0::/45
|
||||
2a14:67c1:b4d0::/44
|
||||
2a14:67c1:b4d0::/45
|
||||
2a14:67c1:b4e0::/43
|
||||
2a14:67c1:b500::/48
|
||||
2a14:67c1:b500::/47
|
||||
2a14:67c1:b549::/48
|
||||
2a14:67c1:b561::/48
|
||||
2a14:67c1:b563::/48
|
||||
@@ -1401,16 +1400,17 @@
|
||||
2a14:67c1:b582::/48
|
||||
2a14:67c1:b588::/47
|
||||
2a14:67c1:b590::/48
|
||||
2a14:67c1:b599::/48
|
||||
2a14:67c5:1900::/40
|
||||
2a14:7580:9200::/40
|
||||
2a14:7580:9400::/39
|
||||
2a14:7580:d000::/37
|
||||
2a14:7580:d800::/39
|
||||
2a14:7580:da00::/40
|
||||
2a14:7580:e200::/40
|
||||
2a14:7580:fe00::/40
|
||||
2a14:7581:3100::/40
|
||||
2a14:7581:9010::/44
|
||||
2a14:7583:f4fe::/48
|
||||
2a14:7583:f500::/48
|
||||
2c0f:f7a8:8011::/48
|
||||
2c0f:f7a8:8050::/48
|
||||
2c0f:f7a8:805f::/48
|
||||
|
@@ -1 +1 @@
|
||||
20250822033443
|
||||
20250912032015
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -1 +1 @@
|
||||
202508212214
|
||||
202509112212
|
||||
|
@@ -735,6 +735,8 @@ brutaltgp.com
|
||||
bsky.app
|
||||
bsky.network
|
||||
bsky.social
|
||||
bt4g.org
|
||||
bt4gprx.com
|
||||
bt95.com
|
||||
btaia.com
|
||||
btbit.net
|
||||
@@ -1131,6 +1133,7 @@ costco.com
|
||||
cotweet.com
|
||||
counter.social
|
||||
coursehero.com
|
||||
covenantswatch.org.tw
|
||||
coze.com
|
||||
cpj.org
|
||||
cpu-monkey.com
|
||||
@@ -1679,6 +1682,7 @@ fdc64.org
|
||||
fdc89.jp
|
||||
feedburner.com
|
||||
feeder.co
|
||||
feedly.com
|
||||
feeds.fileforum.com
|
||||
feedx.net
|
||||
feelssh.com
|
||||
@@ -1851,6 +1855,8 @@ ftvnews.com.tw
|
||||
ftx.com
|
||||
fucd.com
|
||||
fuchsia.dev
|
||||
fuckccp.com
|
||||
fuckccp.xyz
|
||||
fuckgfw.org
|
||||
fulione.com
|
||||
fullerconsideration.com
|
||||
@@ -2702,6 +2708,7 @@ iphone4hongkong.com
|
||||
iphonetaiwan.org
|
||||
iphonix.fr
|
||||
ipicture.ru
|
||||
ipify.org
|
||||
ipjetable.net
|
||||
ipobar.com
|
||||
ipoock.com
|
||||
@@ -3307,6 +3314,7 @@ mofos.com
|
||||
mog.com
|
||||
mohu.club
|
||||
mohu.rocks
|
||||
moj.gov.tw
|
||||
mojim.com
|
||||
mol.gov.tw
|
||||
molihua.org
|
||||
@@ -4676,6 +4684,7 @@ talkcc.com
|
||||
talkonly.net
|
||||
tanc.org
|
||||
tangren.us
|
||||
tanks.gg
|
||||
taoism.net
|
||||
tapanwap.com
|
||||
tapatalk.com
|
||||
|
@@ -1 +1 @@
|
||||
202508212214
|
||||
202509112212
|
||||
|
@@ -5,7 +5,7 @@
|
||||
|
||||
NAME="homeproxy"
|
||||
|
||||
log_max_size="10" #KB
|
||||
log_max_size="50" #KB
|
||||
main_log_file="/var/run/$NAME/$NAME.log"
|
||||
singc_log_file="/var/run/$NAME/sing-box-c.log"
|
||||
sings_log_file="/var/run/$NAME/sing-box-s.log"
|
||||
|
@@ -135,6 +135,8 @@ if (match(proxy_mode), /tun/) {
|
||||
endpoint_independent_nat = uci.get(uciconfig, uciroutingsetting, 'endpoint_independent_nat');
|
||||
}
|
||||
}
|
||||
|
||||
const log_level = uci.get(uciconfig, ucimain, 'log_level') || 'warn';
|
||||
/* UCI config end */
|
||||
|
||||
/* Config helper start */
|
||||
@@ -399,7 +401,7 @@ const config = {};
|
||||
/* Log */
|
||||
config.log = {
|
||||
disabled: false,
|
||||
level: 'warn',
|
||||
level: log_level,
|
||||
output: RUN_DIR + '/sing-box-c.log',
|
||||
timestamp: true
|
||||
};
|
||||
|
@@ -23,12 +23,15 @@ uci.load(uciconfig);
|
||||
|
||||
const uciserver = 'server';
|
||||
|
||||
const log_level = uci.get(uciconfig, uciserver, 'log_level') || 'warn';
|
||||
/* UCI config end */
|
||||
|
||||
const config = {};
|
||||
|
||||
/* Log */
|
||||
config.log = {
|
||||
disabled: false,
|
||||
level: 'warn',
|
||||
level: log_level,
|
||||
output: RUN_DIR + '/sing-box-s.log',
|
||||
timestamp: true
|
||||
};
|
||||
|
@@ -92,7 +92,7 @@ export function strToInt(str) {
|
||||
};
|
||||
|
||||
export function strToTime(str) {
|
||||
return str ? (str + 's') : null;
|
||||
return !isEmpty(str) ? (str + 's') : null;
|
||||
};
|
||||
|
||||
export function removeBlankAttrs(res) {
|
||||
|
@@ -50,8 +50,7 @@ if (github_token) {
|
||||
}
|
||||
|
||||
/* tun_gso was deprecated in sb 1.11 */
|
||||
const tun_gso = uci.get(uciconfig, uciinfra, 'tun_gso');
|
||||
if (tun_gso || tun_gso === '0')
|
||||
if (!isEmpty(uci.get(uciconfig, uciinfra, 'tun_gso')))
|
||||
uci.delete(uciconfig, uciinfra, 'tun_gso');
|
||||
|
||||
/* create migration section */
|
||||
@@ -65,6 +64,13 @@ if (!migration_crontab) {
|
||||
uci.set(uciconfig, ucimigration, 'crontab', '1');
|
||||
}
|
||||
|
||||
/* log_level was introduced */
|
||||
if (isEmpty(uci.get(uciconfig, ucimain, 'log_level'))
|
||||
uci.set(uciconfig, ucimain, 'log_level', 'warn');
|
||||
|
||||
if (isEmpty(uci.get(uciconfig, uciserver, 'log_level'))
|
||||
uci.set(uciconfig, uciserver, 'log_level', 'warn');
|
||||
|
||||
/* empty value defaults to all ports now */
|
||||
if (uci.get(uciconfig, ucimain, 'routing_port') === 'all')
|
||||
uci.delete(uciconfig, ucimain, 'routing_port');
|
||||
@@ -204,6 +210,10 @@ uci.foreach(uciconfig, ucinode, (cfg) => {
|
||||
if (!isEmpty(cfg.tls_ech_tls_disable_drs))
|
||||
uci.delete(uciconfig, cfg['.name'], 'tls_ech_tls_disable_drs');
|
||||
|
||||
/* tls_ech_enable_pqss is useless and deprecated in sb 1.12 */
|
||||
if (!isEmpty(cfg.tls_ech_enable_pqss))
|
||||
uci.delete(uciconfig, cfg['.name'], 'tls_ech_enable_pqss');
|
||||
|
||||
/* wireguard_gso was deprecated in sb 1.11 */
|
||||
if (!isEmpty(cfg.wireguard_gso))
|
||||
uci.delete(uciconfig, cfg['.name'], 'wireguard_gso');
|
||||
@@ -228,7 +238,7 @@ uci.foreach(uciconfig, uciroutingrule, (cfg) => {
|
||||
/* server options */
|
||||
/* auto_firewall was moved into server options */
|
||||
const auto_firewall = uci.get(uciconfig, uciserver, 'auto_firewall');
|
||||
if (auto_firewall || auto_firewall === '0')
|
||||
if (!isEmpty(auto_firewall))
|
||||
uci.delete(uciconfig, uciserver, 'auto_firewall');
|
||||
|
||||
uci.foreach(uciconfig, uciserver, (cfg) => {
|
||||
|
@@ -569,7 +569,7 @@ function main() {
|
||||
|
||||
log(sprintf('Removing node: %s.', cfg.label || cfg['name']));
|
||||
} else {
|
||||
map(keys(node_cache[cfg.grouphash][cfg['.name']]), (v) => {
|
||||
map(keys(cfg), (v) => {
|
||||
if (v in node_cache[cfg.grouphash][cfg['.name']])
|
||||
uci.set(uciconfig, cfg['.name'], v, node_cache[cfg.grouphash][cfg['.name']][v]);
|
||||
else
|
||||
|
@@ -55,22 +55,18 @@ local api = require "luci.passwall.api"
|
||||
"gfwlist_update","chnroute_update","chnroute6_update",
|
||||
"chnlist_update","geoip_update","geosite_update"
|
||||
];
|
||||
const targetNode = document.querySelector('form') || document.body;
|
||||
const observer = new MutationObserver(() => {
|
||||
const bindFlags = () => {
|
||||
let allBound = true;
|
||||
flags.forEach(flag => {
|
||||
const orig = Array.from(document.querySelectorAll(`input[name$=".${flag}"]`)).find(i => i.type === 'checkbox');
|
||||
if (!orig) {
|
||||
return;
|
||||
}
|
||||
if (!orig) { allBound = false; return; }
|
||||
// 隐藏最外层 div
|
||||
const wrapper = orig.closest('.cbi-value');
|
||||
if (wrapper && wrapper.style.display !== 'none') {
|
||||
wrapper.style.display = 'none';
|
||||
}
|
||||
const custom = document.querySelector(`.cbi-input-checkbox[name="${flag.replace('_update','')}"]`);
|
||||
if (!custom) {
|
||||
return;
|
||||
}
|
||||
if (!custom) { allBound = false; return; }
|
||||
custom.checked = orig.checked;
|
||||
// 自定义选择框与原生Flag双向绑定
|
||||
if (!custom._binded) {
|
||||
@@ -84,8 +80,13 @@ local api = require "luci.passwall.api"
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
observer.observe(targetNode, { childList: true, subtree: true });
|
||||
return allBound;
|
||||
};
|
||||
const target = document.querySelector('form') || document.body;
|
||||
const observer = new MutationObserver(() => bindFlags() ? observer.disconnect() : 0);
|
||||
observer.observe(target, { childList: true, subtree: true });
|
||||
const timer = setInterval(() => bindFlags() ? (clearInterval(timer), observer.disconnect()) : 0, 300);
|
||||
setTimeout(() => { clearInterval(timer); observer.disconnect(); }, 5000);
|
||||
});
|
||||
|
||||
function update_rules(btn) {
|
||||
|
@@ -9,6 +9,31 @@ public class JsonUtils
|
||||
{
|
||||
private static readonly string _tag = "JsonUtils";
|
||||
|
||||
private static readonly JsonSerializerOptions _defaultDeserializeOptions = new()
|
||||
{
|
||||
PropertyNameCaseInsensitive = true,
|
||||
ReadCommentHandling = JsonCommentHandling.Skip
|
||||
};
|
||||
|
||||
private static readonly JsonSerializerOptions _defaultSerializeOptions = new()
|
||||
{
|
||||
WriteIndented = true,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||
};
|
||||
|
||||
private static readonly JsonSerializerOptions _nullValueSerializeOptions = new()
|
||||
{
|
||||
WriteIndented = true,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.Never,
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||
};
|
||||
|
||||
private static readonly JsonDocumentOptions _defaultDocumentOptions = new()
|
||||
{
|
||||
CommentHandling = JsonCommentHandling.Skip
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// DeepCopy
|
||||
/// </summary>
|
||||
@@ -34,11 +59,7 @@ public class JsonUtils
|
||||
{
|
||||
return default;
|
||||
}
|
||||
var options = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNameCaseInsensitive = true
|
||||
};
|
||||
return JsonSerializer.Deserialize<T>(strJson, options);
|
||||
return JsonSerializer.Deserialize<T>(strJson, _defaultDeserializeOptions);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -59,7 +80,7 @@ public class JsonUtils
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return JsonNode.Parse(strJson);
|
||||
return JsonNode.Parse(strJson, nodeOptions: null, _defaultDocumentOptions);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -84,12 +105,7 @@ public class JsonUtils
|
||||
{
|
||||
return result;
|
||||
}
|
||||
var options = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = indented,
|
||||
DefaultIgnoreCondition = nullValue ? JsonIgnoreCondition.Never : JsonIgnoreCondition.WhenWritingNull,
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||
};
|
||||
var options = nullValue ? _nullValueSerializeOptions : _defaultSerializeOptions;
|
||||
result = JsonSerializer.Serialize(obj, options);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
@@ -331,6 +331,32 @@ public class Utils
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public static Dictionary<string, List<string>> ParseHostsToDictionary(string hostsContent)
|
||||
{
|
||||
var userHostsMap = hostsContent
|
||||
.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(line => line.Trim())
|
||||
// skip full-line comments
|
||||
.Where(line => !string.IsNullOrWhiteSpace(line) && !line.StartsWith("#"))
|
||||
// strip inline comments (truncate at '#')
|
||||
.Select(line =>
|
||||
{
|
||||
var index = line.IndexOf('#');
|
||||
return index >= 0 ? line.Substring(0, index).Trim() : line;
|
||||
})
|
||||
// ensure line still contains valid parts
|
||||
.Where(line => !string.IsNullOrWhiteSpace(line) && line.Contains(' '))
|
||||
.Select(line => line.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries))
|
||||
.Where(parts => parts.Length >= 2)
|
||||
.GroupBy(parts => parts[0])
|
||||
.ToDictionary(
|
||||
group => group.Key,
|
||||
group => group.SelectMany(parts => parts.Skip(1)).ToList()
|
||||
);
|
||||
|
||||
return userHostsMap;
|
||||
}
|
||||
|
||||
#endregion 转换函数
|
||||
|
||||
#region 数据检查
|
||||
@@ -857,6 +883,55 @@ public class Utils
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool IsPackagedInstall()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (IsWindows() || IsOSX())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("APPIMAGE")))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var exePath = GetExePath();
|
||||
var baseDir = string.IsNullOrEmpty(exePath) ? StartupPath() : Path.GetDirectoryName(exePath) ?? "";
|
||||
var p = baseDir.Replace('\\', '/');
|
||||
|
||||
if (string.IsNullOrEmpty(p))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (p.Contains("/.mount_", StringComparison.Ordinal))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (p.StartsWith("/opt/v2rayN", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (p.StartsWith("/usr/lib/v2rayN", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (p.StartsWith("/usr/share/v2rayN", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static async Task<string?> GetLinuxUserId()
|
||||
{
|
||||
var arg = new List<string>() { "-c", "id -u" };
|
||||
|
@@ -4,7 +4,7 @@ public class ClashFmt : BaseFmt
|
||||
{
|
||||
public static ProfileItem? ResolveFull(string strData, string? subRemarks)
|
||||
{
|
||||
if (Contains(strData, "port", "socks-port", "proxies"))
|
||||
if (Contains(strData, "external-controller", "-port", "proxies"))
|
||||
{
|
||||
var fileName = WriteAllText(strData, "yaml");
|
||||
|
||||
|
@@ -94,17 +94,7 @@ public partial class CoreConfigSingboxService
|
||||
|
||||
if (!simpleDNSItem.Hosts.IsNullOrEmpty())
|
||||
{
|
||||
var userHostsMap = simpleDNSItem.Hosts
|
||||
.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(line => line.Trim())
|
||||
.Where(line => !string.IsNullOrWhiteSpace(line) && line.Contains(' '))
|
||||
.Select(line => line.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries))
|
||||
.Where(parts => parts.Length >= 2)
|
||||
.GroupBy(parts => parts[0])
|
||||
.ToDictionary(
|
||||
group => group.Key,
|
||||
group => group.SelectMany(parts => parts.Skip(1)).ToList()
|
||||
);
|
||||
var userHostsMap = Utils.ParseHostsToDictionary(simpleDNSItem.Hosts);
|
||||
|
||||
foreach (var kvp in userHostsMap)
|
||||
{
|
||||
|
@@ -71,6 +71,31 @@ public partial class CoreConfigSingboxService
|
||||
});
|
||||
}
|
||||
|
||||
var hostsDomains = new List<string>();
|
||||
var systemHostsMap = Utils.GetSystemHosts();
|
||||
foreach (var kvp in systemHostsMap)
|
||||
{
|
||||
hostsDomains.Add(kvp.Key);
|
||||
}
|
||||
var dnsItem = await AppManager.Instance.GetDNSItem(ECoreType.sing_box);
|
||||
if (dnsItem == null || dnsItem.Enabled == false)
|
||||
{
|
||||
var simpleDNSItem = _config.SimpleDNSItem;
|
||||
if (!simpleDNSItem.Hosts.IsNullOrEmpty())
|
||||
{
|
||||
var userHostsMap = Utils.ParseHostsToDictionary(simpleDNSItem.Hosts);
|
||||
foreach (var kvp in userHostsMap)
|
||||
{
|
||||
hostsDomains.Add(kvp.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
singboxConfig.route.rules.Add(new()
|
||||
{
|
||||
action = "resolve",
|
||||
domain = hostsDomains,
|
||||
});
|
||||
|
||||
singboxConfig.route.rules.Add(new()
|
||||
{
|
||||
outbound = Global.DirectTag,
|
||||
|
@@ -261,17 +261,7 @@ public partial class CoreConfigV2rayService
|
||||
|
||||
if (!simpleDNSItem.Hosts.IsNullOrEmpty())
|
||||
{
|
||||
var userHostsMap = simpleDNSItem.Hosts
|
||||
.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(line => line.Trim())
|
||||
.Where(line => !string.IsNullOrWhiteSpace(line) && line.Contains(' '))
|
||||
.Select(line => line.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries))
|
||||
.Where(parts => parts.Length >= 2)
|
||||
.GroupBy(parts => parts[0])
|
||||
.ToDictionary(
|
||||
group => group.Key,
|
||||
group => group.SelectMany(parts => parts.Skip(1)).ToList()
|
||||
);
|
||||
var userHostsMap = Utils.ParseHostsToDictionary(simpleDNSItem.Hosts);
|
||||
|
||||
foreach (var kvp in userHostsMap)
|
||||
{
|
||||
|
@@ -63,6 +63,16 @@ public class CheckUpdateViewModel : MyReactiveObject
|
||||
|
||||
private CheckUpdateModel GetCheckUpdateModel(string coreType)
|
||||
{
|
||||
if (coreType == _v2rayN && Utils.IsPackagedInstall())
|
||||
{
|
||||
return new()
|
||||
{
|
||||
IsSelected = false,
|
||||
CoreType = coreType,
|
||||
Remarks = ResUI.menuCheckUpdate + " (Not Support)",
|
||||
};
|
||||
}
|
||||
|
||||
return new()
|
||||
{
|
||||
IsSelected = _config.CheckUpdateItem.SelectedCoreTypes?.Contains(coreType) ?? true,
|
||||
@@ -104,6 +114,11 @@ public class CheckUpdateViewModel : MyReactiveObject
|
||||
}
|
||||
else if (item.CoreType == _v2rayN)
|
||||
{
|
||||
if (Utils.IsPackagedInstall())
|
||||
{
|
||||
await UpdateView(_v2rayN, "Not Support");
|
||||
continue;
|
||||
}
|
||||
await CheckUpdateN(EnableCheckPreReleaseUpdate);
|
||||
}
|
||||
else if (item.CoreType == ECoreType.Xray.ToString())
|
||||
|
@@ -400,7 +400,7 @@
|
||||
Grid.Column="1"
|
||||
Width="400"
|
||||
Margin="{StaticResource Margin4}"
|
||||
Watermark="1000:2000,3000:4000" />
|
||||
Watermark="1000-2000,3000,4000" />
|
||||
<TextBlock
|
||||
Grid.Row="3"
|
||||
Grid.Column="2"
|
||||
|
@@ -176,6 +176,7 @@
|
||||
<DataGrid
|
||||
x:Name="lstRules"
|
||||
AutoGenerateColumns="False"
|
||||
Background="Transparent"
|
||||
BorderThickness="1"
|
||||
CanUserResizeColumns="True"
|
||||
GridLinesVisibility="All"
|
||||
|
@@ -92,6 +92,7 @@
|
||||
<DataGrid
|
||||
x:Name="lstRoutings"
|
||||
AutoGenerateColumns="False"
|
||||
Background="Transparent"
|
||||
BorderThickness="1"
|
||||
CanUserResizeColumns="True"
|
||||
GridLinesVisibility="All"
|
||||
|
@@ -538,7 +538,7 @@
|
||||
Width="400"
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Left"
|
||||
materialDesign:HintAssist.Hint="1000:2000,3000:4000"
|
||||
materialDesign:HintAssist.Hint="1000-2000,3000,4000"
|
||||
Style="{StaticResource DefTextBox}" />
|
||||
<TextBlock
|
||||
Grid.Row="3"
|
||||
|
@@ -122,6 +122,8 @@ public partial class RoutingRuleSettingWindow
|
||||
|
||||
private void RoutingRuleSettingWindow_PreviewKeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (!lstRules.IsKeyboardFocusWithin)
|
||||
return;
|
||||
if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
|
||||
{
|
||||
if (e.Key == Key.A)
|
||||
|
@@ -424,7 +424,6 @@ from .cpac import (
|
||||
CPACPlaylistIE,
|
||||
)
|
||||
from .cracked import CrackedIE
|
||||
from .crackle import CrackleIE
|
||||
from .craftsy import CraftsyIE
|
||||
from .crooksandliars import CrooksAndLiarsIE
|
||||
from .crowdbunker import (
|
||||
@@ -444,10 +443,6 @@ from .curiositystream import (
|
||||
CuriosityStreamIE,
|
||||
CuriosityStreamSeriesIE,
|
||||
)
|
||||
from .cwtv import (
|
||||
CWTVIE,
|
||||
CWTVMovieIE,
|
||||
)
|
||||
from .cybrary import (
|
||||
CybraryCourseIE,
|
||||
CybraryIE,
|
||||
@@ -1433,6 +1428,7 @@ from .onet import (
|
||||
OnetPlIE,
|
||||
)
|
||||
from .onionstudios import OnionStudiosIE
|
||||
from .onsen import OnsenIE
|
||||
from .opencast import (
|
||||
OpencastIE,
|
||||
OpencastPlaylistIE,
|
||||
@@ -1466,10 +1462,6 @@ from .panopto import (
|
||||
PanoptoListIE,
|
||||
PanoptoPlaylistIE,
|
||||
)
|
||||
from .paramountplus import (
|
||||
ParamountPlusIE,
|
||||
ParamountPlusSeriesIE,
|
||||
)
|
||||
from .parler import ParlerIE
|
||||
from .parlview import ParlviewIE
|
||||
from .parti import (
|
||||
@@ -1779,7 +1771,6 @@ from .rutube import (
|
||||
RutubePlaylistIE,
|
||||
RutubeTagsIE,
|
||||
)
|
||||
from .rutv import RUTVIE
|
||||
from .ruutu import RuutuIE
|
||||
from .ruv import (
|
||||
RuvIE,
|
||||
@@ -1849,7 +1840,6 @@ from .simplecast import (
|
||||
SimplecastPodcastIE,
|
||||
)
|
||||
from .sina import SinaIE
|
||||
from .sixplay import SixPlayIE
|
||||
from .skeb import SkebIE
|
||||
from .sky import (
|
||||
SkyNewsIE,
|
||||
@@ -1877,7 +1867,12 @@ from .skynewsau import SkyNewsAUIE
|
||||
from .slideshare import SlideshareIE
|
||||
from .slideslive import SlidesLiveIE
|
||||
from .slutload import SlutloadIE
|
||||
from .smotrim import SmotrimIE
|
||||
from .smotrim import (
|
||||
SmotrimAudioIE,
|
||||
SmotrimIE,
|
||||
SmotrimLiveIE,
|
||||
SmotrimPlaylistIE,
|
||||
)
|
||||
from .snapchat import SnapchatSpotlightIE
|
||||
from .snotr import SnotrIE
|
||||
from .softwhiteunderbelly import SoftWhiteUnderbellyIE
|
||||
@@ -1925,10 +1920,6 @@ from .spiegel import SpiegelIE
|
||||
from .sport5 import Sport5IE
|
||||
from .sportbox import SportBoxIE
|
||||
from .sportdeutschland import SportDeutschlandIE
|
||||
from .spotify import (
|
||||
SpotifyIE,
|
||||
SpotifyShowIE,
|
||||
)
|
||||
from .spreaker import (
|
||||
SpreakerIE,
|
||||
SpreakerShowIE,
|
||||
@@ -2149,6 +2140,7 @@ from .tubitv import (
|
||||
)
|
||||
from .tumblr import TumblrIE
|
||||
from .tunein import (
|
||||
TuneInEmbedIE,
|
||||
TuneInPodcastEpisodeIE,
|
||||
TuneInPodcastIE,
|
||||
TuneInShortenerIE,
|
||||
@@ -2283,7 +2275,6 @@ from .utreon import UtreonIE
|
||||
from .varzesh3 import Varzesh3IE
|
||||
from .vbox7 import Vbox7IE
|
||||
from .veo import VeoIE
|
||||
from .vesti import VestiIE
|
||||
from .vevo import (
|
||||
VevoIE,
|
||||
VevoPlaylistIE,
|
||||
@@ -2472,7 +2463,6 @@ from .wykop import (
|
||||
WykopPostCommentIE,
|
||||
WykopPostIE,
|
||||
)
|
||||
from .xanimu import XanimuIE
|
||||
from .xboxclips import XboxClipsIE
|
||||
from .xhamster import (
|
||||
XHamsterEmbedIE,
|
||||
|
@@ -5,8 +5,6 @@ import zlib
|
||||
|
||||
from .anvato import AnvatoIE
|
||||
from .common import InfoExtractor
|
||||
from .paramountplus import ParamountPlusIE
|
||||
from ..networking import HEADRequest
|
||||
from ..utils import (
|
||||
ExtractorError,
|
||||
UserNotLive,
|
||||
@@ -132,13 +130,7 @@ class CBSNewsEmbedIE(CBSNewsBaseIE):
|
||||
video_id = item['mpxRefId']
|
||||
video_url = self._get_video_url(item)
|
||||
if not video_url:
|
||||
# Old embeds redirect user to ParamountPlus but most links are 404
|
||||
pplus_url = f'https://www.paramountplus.com/shows/video/{video_id}'
|
||||
try:
|
||||
self._request_webpage(HEADRequest(pplus_url), video_id)
|
||||
return self.url_result(pplus_url, ParamountPlusIE)
|
||||
except ExtractorError:
|
||||
self.raise_no_formats('This video is no longer available', True, video_id)
|
||||
raise ExtractorError('This video is no longer available', expected=True)
|
||||
|
||||
return self._extract_video(item, video_url, video_id)
|
||||
|
||||
|
@@ -1,243 +0,0 @@
|
||||
import hashlib
|
||||
import hmac
|
||||
import re
|
||||
import time
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..networking.exceptions import HTTPError
|
||||
from ..utils import (
|
||||
ExtractorError,
|
||||
determine_ext,
|
||||
float_or_none,
|
||||
int_or_none,
|
||||
orderedSet,
|
||||
parse_age_limit,
|
||||
parse_duration,
|
||||
url_or_none,
|
||||
)
|
||||
|
||||
|
||||
class CrackleIE(InfoExtractor):
|
||||
_VALID_URL = r'(?:crackle:|https?://(?:(?:www|m)\.)?(?:sony)?crackle\.com/(?:playlist/\d+/|(?:[^/]+/)+))(?P<id>\d+)'
|
||||
_TESTS = [{
|
||||
# Crackle is available in the United States and territories
|
||||
'url': 'https://www.crackle.com/thanksgiving/2510064',
|
||||
'info_dict': {
|
||||
'id': '2510064',
|
||||
'ext': 'mp4',
|
||||
'title': 'Touch Football',
|
||||
'description': 'md5:cfbb513cf5de41e8b56d7ab756cff4df',
|
||||
'duration': 1398,
|
||||
'view_count': int,
|
||||
'average_rating': 0,
|
||||
'age_limit': 17,
|
||||
'genre': 'Comedy',
|
||||
'creator': 'Daniel Powell',
|
||||
'artist': 'Chris Elliott, Amy Sedaris',
|
||||
'release_year': 2016,
|
||||
'series': 'Thanksgiving',
|
||||
'episode': 'Touch Football',
|
||||
'season_number': 1,
|
||||
'episode_number': 1,
|
||||
},
|
||||
'params': {
|
||||
# m3u8 download
|
||||
'skip_download': True,
|
||||
},
|
||||
'expected_warnings': [
|
||||
'Trying with a list of known countries',
|
||||
],
|
||||
}, {
|
||||
'url': 'https://www.sonycrackle.com/thanksgiving/2510064',
|
||||
'only_matching': True,
|
||||
}]
|
||||
|
||||
_MEDIA_FILE_SLOTS = {
|
||||
'360p.mp4': {
|
||||
'width': 640,
|
||||
'height': 360,
|
||||
},
|
||||
'480p.mp4': {
|
||||
'width': 768,
|
||||
'height': 432,
|
||||
},
|
||||
'480p_1mbps.mp4': {
|
||||
'width': 852,
|
||||
'height': 480,
|
||||
},
|
||||
}
|
||||
|
||||
def _download_json(self, url, *args, **kwargs):
|
||||
# Authorization generation algorithm is reverse engineered from:
|
||||
# https://www.sonycrackle.com/static/js/main.ea93451f.chunk.js
|
||||
timestamp = time.strftime('%Y%m%d%H%M', time.gmtime())
|
||||
h = hmac.new(b'IGSLUQCBDFHEOIFM', '|'.join([url, timestamp]).encode(), hashlib.sha1).hexdigest().upper()
|
||||
headers = {
|
||||
'Accept': 'application/json',
|
||||
'Authorization': '|'.join([h, timestamp, '117', '1']),
|
||||
}
|
||||
return InfoExtractor._download_json(self, url, *args, headers=headers, **kwargs)
|
||||
|
||||
def _real_extract(self, url):
|
||||
video_id = self._match_id(url)
|
||||
|
||||
geo_bypass_country = self.get_param('geo_bypass_country', None)
|
||||
countries = orderedSet((geo_bypass_country, 'US', 'AU', 'CA', 'AS', 'FM', 'GU', 'MP', 'PR', 'PW', 'MH', 'VI', ''))
|
||||
num_countries, num = len(countries) - 1, 0
|
||||
|
||||
media = {}
|
||||
for num, country in enumerate(countries):
|
||||
if num == 1: # start hard-coded list
|
||||
self.report_warning('%s. Trying with a list of known countries' % (
|
||||
f'Unable to obtain video formats from {geo_bypass_country} API' if geo_bypass_country
|
||||
else 'No country code was given using --geo-bypass-country'))
|
||||
elif num == num_countries: # end of list
|
||||
geo_info = self._download_json(
|
||||
'https://web-api-us.crackle.com/Service.svc/geo/country',
|
||||
video_id, fatal=False, note='Downloading geo-location information from crackle API',
|
||||
errnote='Unable to fetch geo-location information from crackle') or {}
|
||||
country = geo_info.get('CountryCode')
|
||||
if country is None:
|
||||
continue
|
||||
self.to_screen(f'{self.IE_NAME} identified country as {country}')
|
||||
if country in countries:
|
||||
self.to_screen(f'Downloading from {country} API was already attempted. Skipping...')
|
||||
continue
|
||||
|
||||
if country is None:
|
||||
continue
|
||||
try:
|
||||
media = self._download_json(
|
||||
f'https://web-api-us.crackle.com/Service.svc/details/media/{video_id}/{country}?disableProtocols=true',
|
||||
video_id, note=f'Downloading media JSON from {country} API',
|
||||
errnote='Unable to download media JSON')
|
||||
except ExtractorError as e:
|
||||
# 401 means geo restriction, trying next country
|
||||
if isinstance(e.cause, HTTPError) and e.cause.status == 401:
|
||||
continue
|
||||
raise
|
||||
|
||||
status = media.get('status')
|
||||
if status.get('messageCode') != '0':
|
||||
raise ExtractorError(
|
||||
'{} said: {} {} - {}'.format(
|
||||
self.IE_NAME, status.get('messageCodeDescription'), status.get('messageCode'), status.get('message')),
|
||||
expected=True)
|
||||
|
||||
# Found video formats
|
||||
if isinstance(media.get('MediaURLs'), list):
|
||||
break
|
||||
|
||||
ignore_no_formats = self.get_param('ignore_no_formats_error')
|
||||
|
||||
if not media or (not media.get('MediaURLs') and not ignore_no_formats):
|
||||
raise ExtractorError(
|
||||
'Unable to access the crackle API. Try passing your country code '
|
||||
'to --geo-bypass-country. If it still does not work and the '
|
||||
'video is available in your country')
|
||||
title = media['Title']
|
||||
|
||||
formats, subtitles = [], {}
|
||||
has_drm = False
|
||||
for e in media.get('MediaURLs') or []:
|
||||
if e.get('UseDRM'):
|
||||
has_drm = True
|
||||
format_url = url_or_none(e.get('DRMPath'))
|
||||
else:
|
||||
format_url = url_or_none(e.get('Path'))
|
||||
if not format_url:
|
||||
continue
|
||||
ext = determine_ext(format_url)
|
||||
if ext == 'm3u8':
|
||||
fmts, subs = self._extract_m3u8_formats_and_subtitles(
|
||||
format_url, video_id, 'mp4', entry_protocol='m3u8_native',
|
||||
m3u8_id='hls', fatal=False)
|
||||
formats.extend(fmts)
|
||||
subtitles = self._merge_subtitles(subtitles, subs)
|
||||
elif ext == 'mpd':
|
||||
fmts, subs = self._extract_mpd_formats_and_subtitles(
|
||||
format_url, video_id, mpd_id='dash', fatal=False)
|
||||
formats.extend(fmts)
|
||||
subtitles = self._merge_subtitles(subtitles, subs)
|
||||
elif format_url.endswith('.ism/Manifest'):
|
||||
fmts, subs = self._extract_ism_formats_and_subtitles(
|
||||
format_url, video_id, ism_id='mss', fatal=False)
|
||||
formats.extend(fmts)
|
||||
subtitles = self._merge_subtitles(subtitles, subs)
|
||||
else:
|
||||
mfs_path = e.get('Type')
|
||||
mfs_info = self._MEDIA_FILE_SLOTS.get(mfs_path)
|
||||
if not mfs_info:
|
||||
continue
|
||||
formats.append({
|
||||
'url': format_url,
|
||||
'format_id': 'http-' + mfs_path.split('.')[0],
|
||||
'width': mfs_info['width'],
|
||||
'height': mfs_info['height'],
|
||||
})
|
||||
if not formats and has_drm:
|
||||
self.report_drm(video_id)
|
||||
|
||||
description = media.get('Description')
|
||||
duration = int_or_none(media.get(
|
||||
'DurationInSeconds')) or parse_duration(media.get('Duration'))
|
||||
view_count = int_or_none(media.get('CountViews'))
|
||||
average_rating = float_or_none(media.get('UserRating'))
|
||||
age_limit = parse_age_limit(media.get('Rating'))
|
||||
genre = media.get('Genre')
|
||||
release_year = int_or_none(media.get('ReleaseYear'))
|
||||
creator = media.get('Directors')
|
||||
artist = media.get('Cast')
|
||||
|
||||
if media.get('MediaTypeDisplayValue') == 'Full Episode':
|
||||
series = media.get('ShowName')
|
||||
episode = title
|
||||
season_number = int_or_none(media.get('Season'))
|
||||
episode_number = int_or_none(media.get('Episode'))
|
||||
else:
|
||||
series = episode = season_number = episode_number = None
|
||||
|
||||
cc_files = media.get('ClosedCaptionFiles')
|
||||
if isinstance(cc_files, list):
|
||||
for cc_file in cc_files:
|
||||
if not isinstance(cc_file, dict):
|
||||
continue
|
||||
cc_url = url_or_none(cc_file.get('Path'))
|
||||
if not cc_url:
|
||||
continue
|
||||
lang = cc_file.get('Locale') or 'en'
|
||||
subtitles.setdefault(lang, []).append({'url': cc_url})
|
||||
|
||||
thumbnails = []
|
||||
images = media.get('Images')
|
||||
if isinstance(images, list):
|
||||
for image_key, image_url in images.items():
|
||||
mobj = re.search(r'Img_(\d+)[xX](\d+)', image_key)
|
||||
if not mobj:
|
||||
continue
|
||||
thumbnails.append({
|
||||
'url': image_url,
|
||||
'width': int(mobj.group(1)),
|
||||
'height': int(mobj.group(2)),
|
||||
})
|
||||
|
||||
return {
|
||||
'id': video_id,
|
||||
'title': title,
|
||||
'description': description,
|
||||
'duration': duration,
|
||||
'view_count': view_count,
|
||||
'average_rating': average_rating,
|
||||
'age_limit': age_limit,
|
||||
'genre': genre,
|
||||
'creator': creator,
|
||||
'artist': artist,
|
||||
'release_year': release_year,
|
||||
'series': series,
|
||||
'episode': episode,
|
||||
'season_number': season_number,
|
||||
'episode_number': episode_number,
|
||||
'thumbnails': thumbnails,
|
||||
'subtitles': subtitles,
|
||||
'formats': formats,
|
||||
}
|
@@ -1,180 +0,0 @@
|
||||
import re
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..utils import (
|
||||
ExtractorError,
|
||||
int_or_none,
|
||||
parse_age_limit,
|
||||
parse_iso8601,
|
||||
parse_qs,
|
||||
smuggle_url,
|
||||
str_or_none,
|
||||
update_url_query,
|
||||
)
|
||||
from ..utils.traversal import traverse_obj
|
||||
|
||||
|
||||
class CWTVIE(InfoExtractor):
|
||||
IE_NAME = 'cwtv'
|
||||
_VALID_URL = r'https?://(?:www\.)?cw(?:tv(?:pr)?|seed)\.com/(?:shows/)?(?:[^/]+/)+[^?]*\?.*\b(?:play|watch|guid)=(?P<id>[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12})'
|
||||
_TESTS = [{
|
||||
'url': 'https://www.cwtv.com/shows/continuum/a-stitch-in-time/?play=9149a1e1-4cb2-46d7-81b2-47d35bbd332b',
|
||||
'info_dict': {
|
||||
'id': '9149a1e1-4cb2-46d7-81b2-47d35bbd332b',
|
||||
'ext': 'mp4',
|
||||
'title': 'A Stitch in Time',
|
||||
'description': r're:(?s)City Protective Services officer Kiera Cameron is transported from 2077.+',
|
||||
'thumbnail': r're:https?://.+\.jpe?g',
|
||||
'duration': 2632,
|
||||
'timestamp': 1736928000,
|
||||
'uploader': 'CWTV',
|
||||
'chapters': 'count:5',
|
||||
'series': 'Continuum',
|
||||
'season_number': 1,
|
||||
'episode_number': 1,
|
||||
'age_limit': 14,
|
||||
'upload_date': '20250115',
|
||||
'season': 'Season 1',
|
||||
'episode': 'Episode 1',
|
||||
},
|
||||
'params': {
|
||||
# m3u8 download
|
||||
'skip_download': True,
|
||||
},
|
||||
}, {
|
||||
'url': 'http://cwtv.com/shows/arrow/legends-of-yesterday/?play=6b15e985-9345-4f60-baf8-56e96be57c63',
|
||||
'info_dict': {
|
||||
'id': '6b15e985-9345-4f60-baf8-56e96be57c63',
|
||||
'ext': 'mp4',
|
||||
'title': 'Legends of Yesterday',
|
||||
'description': r're:(?s)Oliver and Barry Allen take Kendra Saunders and Carter Hall to a remote.+',
|
||||
'duration': 2665,
|
||||
'series': 'Arrow',
|
||||
'season_number': 4,
|
||||
'season': '4',
|
||||
'episode_number': 8,
|
||||
'upload_date': '20151203',
|
||||
'timestamp': 1449122100,
|
||||
},
|
||||
'params': {
|
||||
# m3u8 download
|
||||
'skip_download': True,
|
||||
},
|
||||
'skip': 'redirect to http://cwtv.com/shows/arrow/',
|
||||
}, {
|
||||
'url': 'http://www.cwseed.com/shows/whose-line-is-it-anyway/jeff-davis-4/?play=24282b12-ead2-42f2-95ad-26770c2c6088',
|
||||
'info_dict': {
|
||||
'id': '24282b12-ead2-42f2-95ad-26770c2c6088',
|
||||
'ext': 'mp4',
|
||||
'title': 'Jeff Davis 4',
|
||||
'description': 'Jeff Davis is back to make you laugh.',
|
||||
'duration': 1263,
|
||||
'series': 'Whose Line Is It Anyway?',
|
||||
'season_number': 11,
|
||||
'episode_number': 20,
|
||||
'upload_date': '20151006',
|
||||
'timestamp': 1444107300,
|
||||
'age_limit': 14,
|
||||
'uploader': 'CWTV',
|
||||
'thumbnail': r're:https?://.+\.jpe?g',
|
||||
'chapters': 'count:4',
|
||||
'episode': 'Episode 20',
|
||||
'season': 'Season 11',
|
||||
},
|
||||
'params': {
|
||||
# m3u8 download
|
||||
'skip_download': True,
|
||||
},
|
||||
}, {
|
||||
'url': 'http://cwtv.com/thecw/chroniclesofcisco/?play=8adebe35-f447-465f-ab52-e863506ff6d6',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'http://cwtvpr.com/the-cw/video?watch=9eee3f60-ef4e-440b-b3b2-49428ac9c54e',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'http://cwtv.com/shows/arrow/legends-of-yesterday/?watch=6b15e985-9345-4f60-baf8-56e96be57c63',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'http://www.cwtv.com/movies/play/?guid=0a8e8b5b-1356-41d5-9a6a-4eda1a6feb6c',
|
||||
'only_matching': True,
|
||||
}]
|
||||
|
||||
def _real_extract(self, url):
|
||||
video_id = self._match_id(url)
|
||||
data = self._download_json(
|
||||
f'https://images.cwtv.com/feed/app-2/video-meta/apiversion_22/device_android/guid_{video_id}', video_id)
|
||||
if traverse_obj(data, 'result') != 'ok':
|
||||
raise ExtractorError(traverse_obj(data, (('error_msg', 'msg'), {str}, any)), expected=True)
|
||||
video_data = data['video']
|
||||
title = video_data['title']
|
||||
mpx_url = update_url_query(
|
||||
video_data.get('mpx_url') or f'https://link.theplatform.com/s/cwtv/media/guid/2703454149/{video_id}',
|
||||
{'formats': 'M3U+none'})
|
||||
|
||||
season = str_or_none(video_data.get('season'))
|
||||
episode = str_or_none(video_data.get('episode'))
|
||||
if episode and season:
|
||||
episode = episode[len(season):]
|
||||
|
||||
return {
|
||||
'_type': 'url_transparent',
|
||||
'id': video_id,
|
||||
'title': title,
|
||||
'url': smuggle_url(mpx_url, {'force_smil_url': True}),
|
||||
'description': video_data.get('description_long'),
|
||||
'duration': int_or_none(video_data.get('duration_secs')),
|
||||
'series': video_data.get('series_name'),
|
||||
'season_number': int_or_none(season),
|
||||
'episode_number': int_or_none(episode),
|
||||
'timestamp': parse_iso8601(video_data.get('start_time')),
|
||||
'age_limit': parse_age_limit(video_data.get('rating')),
|
||||
'ie_key': 'ThePlatform',
|
||||
'thumbnail': video_data.get('large_thumbnail'),
|
||||
}
|
||||
|
||||
|
||||
class CWTVMovieIE(InfoExtractor):
|
||||
IE_NAME = 'cwtv:movie'
|
||||
_VALID_URL = r'https?://(?:www\.)?cwtv\.com/shows/(?P<id>[\w-]+)/?\?(?:[^#]+&)?viewContext=Movies'
|
||||
_TESTS = [{
|
||||
'url': 'https://www.cwtv.com/shows/the-crush/?viewContext=Movies+Swimlane',
|
||||
'info_dict': {
|
||||
'id': '0a8e8b5b-1356-41d5-9a6a-4eda1a6feb6c',
|
||||
'ext': 'mp4',
|
||||
'title': 'The Crush',
|
||||
'upload_date': '20241112',
|
||||
'description': 'md5:1549acd90dff4a8273acd7284458363e',
|
||||
'chapters': 'count:9',
|
||||
'timestamp': 1731398400,
|
||||
'age_limit': 16,
|
||||
'duration': 5337,
|
||||
'series': 'The Crush',
|
||||
'season': 'Season 1',
|
||||
'uploader': 'CWTV',
|
||||
'season_number': 1,
|
||||
'episode': 'Episode 1',
|
||||
'episode_number': 1,
|
||||
'thumbnail': r're:https?://.+\.jpe?g',
|
||||
},
|
||||
'params': {
|
||||
# m3u8 download
|
||||
'skip_download': True,
|
||||
},
|
||||
}]
|
||||
_UUID_RE = r'[\da-f]{8}-(?:[\da-f]{4}-){3}[\da-f]{12}'
|
||||
|
||||
def _real_extract(self, url):
|
||||
display_id = self._match_id(url)
|
||||
webpage = self._download_webpage(url, display_id)
|
||||
app_url = (
|
||||
self._html_search_meta('al:ios:url', webpage, default=None)
|
||||
or self._html_search_meta('al:android:url', webpage, default=None))
|
||||
video_id = (
|
||||
traverse_obj(parse_qs(app_url), ('video_id', 0, {lambda x: re.fullmatch(self._UUID_RE, x)}, 0))
|
||||
or self._search_regex([
|
||||
rf'CWTV\.Site\.curPlayingGUID\s*=\s*["\']({self._UUID_RE})',
|
||||
rf'CWTV\.Site\.viewInAppURL\s*=\s*["\']/shows/[\w-]+/watch-in-app/\?play=({self._UUID_RE})',
|
||||
], webpage, 'video ID'))
|
||||
|
||||
return self.url_result(
|
||||
f'https://www.cwtv.com/shows/{display_id}/{display_id}/?play={video_id}', CWTVIE, video_id)
|
@@ -37,7 +37,7 @@ class LocoIE(InfoExtractor):
|
||||
},
|
||||
}, {
|
||||
'url': 'https://loco.com/stream/c64916eb-10fb-46a9-9a19-8c4b7ed064e7',
|
||||
'md5': '45ebc8a47ee1c2240178757caf8881b5',
|
||||
'md5': '8b9bda03eba4d066928ae8d71f19befb',
|
||||
'info_dict': {
|
||||
'id': 'c64916eb-10fb-46a9-9a19-8c4b7ed064e7',
|
||||
'ext': 'mp4',
|
||||
@@ -55,9 +55,9 @@ class LocoIE(InfoExtractor):
|
||||
'tags': ['Gameplay'],
|
||||
'series': 'GTA 5',
|
||||
'timestamp': 1740612872,
|
||||
'modified_timestamp': 1740613037,
|
||||
'modified_timestamp': 1750948439,
|
||||
'upload_date': '20250226',
|
||||
'modified_date': '20250226',
|
||||
'modified_date': '20250626',
|
||||
},
|
||||
}, {
|
||||
# Requires video authorization
|
||||
@@ -123,8 +123,8 @@ class LocoIE(InfoExtractor):
|
||||
def _real_extract(self, url):
|
||||
video_type, video_id = self._match_valid_url(url).group('type', 'id')
|
||||
webpage = self._download_webpage(url, video_id)
|
||||
stream = traverse_obj(self._search_nextjs_data(webpage, video_id), (
|
||||
'props', 'pageProps', ('liveStreamData', 'stream', 'liveStream'), {dict}, any, {require('stream info')}))
|
||||
stream = traverse_obj(self._search_nextjs_v13_data(webpage, video_id), (
|
||||
..., (None, 'ssrData'), ('liveStreamData', 'stream', 'liveStream'), {dict}, any, {require('stream info')}))
|
||||
|
||||
if access_token := self._get_access_token(video_id):
|
||||
self._request_webpage(
|
||||
|
151
yt-dlp/yt_dlp/extractor/onsen.py
Normal file
151
yt-dlp/yt_dlp/extractor/onsen.py
Normal file
@@ -0,0 +1,151 @@
|
||||
import base64
|
||||
import json
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..networking.exceptions import HTTPError
|
||||
from ..utils import (
|
||||
ExtractorError,
|
||||
clean_html,
|
||||
int_or_none,
|
||||
parse_qs,
|
||||
str_or_none,
|
||||
strftime_or_none,
|
||||
update_url,
|
||||
update_url_query,
|
||||
url_or_none,
|
||||
)
|
||||
from ..utils.traversal import traverse_obj
|
||||
|
||||
|
||||
class OnsenIE(InfoExtractor):
|
||||
IE_NAME = 'onsen'
|
||||
IE_DESC = 'インターネットラジオステーション<音泉>'
|
||||
|
||||
_BASE_URL = 'https://www.onsen.ag'
|
||||
_HEADERS = {'Referer': f'{_BASE_URL}/'}
|
||||
_NETRC_MACHINE = 'onsen'
|
||||
_VALID_URL = r'https?://(?:(?:share|www)\.)onsen\.ag/program/(?P<id>[^/?#]+)'
|
||||
_TESTS = [{
|
||||
'url': 'https://share.onsen.ag/program/onsenking?p=90&c=MTA0NjI',
|
||||
'info_dict': {
|
||||
'id': '10462',
|
||||
'ext': 'm4a',
|
||||
'title': '第SP回',
|
||||
'cast': 'count:3',
|
||||
'description': 'md5:de62c80a41c4c8d84da53a1ee681ad18',
|
||||
'display_id': 'MTA0NjI=',
|
||||
'media_type': 'sound',
|
||||
'section_start': 0,
|
||||
'series': '音泉キング「下野紘」のラジオ きみはもちろん、<音泉>ファミリーだよね?',
|
||||
'series_id': 'onsenking',
|
||||
'tags': 'count:2',
|
||||
'thumbnail': r're:https?://d3bzklg4lms4gh\.cloudfront\.net/program_info/image/default/production/.+',
|
||||
'upload_date': '20220627',
|
||||
'webpage_url': 'https://www.onsen.ag/program/onsenking?c=MTA0NjI=',
|
||||
},
|
||||
}, {
|
||||
'url': 'https://share.onsen.ag/program/girls-band-cry-radio?p=370&c=MTgwMDE',
|
||||
'info_dict': {
|
||||
'id': '18001',
|
||||
'ext': 'mp4',
|
||||
'title': '第4回',
|
||||
'cast': 'count:5',
|
||||
'description': 'md5:bbca8a389d99c90cbbce8f383c85fedd',
|
||||
'display_id': 'MTgwMDE=',
|
||||
'media_type': 'movie',
|
||||
'section_start': 0,
|
||||
'series': 'TVアニメ『ガールズバンドクライ』WEBラジオ「ガールズバンドクライ~ラジオにも全部ぶち込め。~」',
|
||||
'series_id': 'girls-band-cry-radio',
|
||||
'tags': 'count:3',
|
||||
'thumbnail': r're:https?://d3bzklg4lms4gh\.cloudfront\.net/program_info/image/default/production/.+',
|
||||
'upload_date': '20240425',
|
||||
'webpage_url': 'https://www.onsen.ag/program/girls-band-cry-radio?c=MTgwMDE=',
|
||||
},
|
||||
'skip': 'Only available for premium supporters',
|
||||
}, {
|
||||
'url': 'https://www.onsen.ag/program/uma',
|
||||
'info_dict': {
|
||||
'id': 'uma',
|
||||
'title': 'UMA YELL RADIO',
|
||||
},
|
||||
'playlist_mincount': 35,
|
||||
}]
|
||||
|
||||
@staticmethod
|
||||
def _get_encoded_id(program):
|
||||
return base64.urlsafe_b64encode(str(program['id']).encode()).decode()
|
||||
|
||||
def _perform_login(self, username, password):
|
||||
sign_in = self._download_json(
|
||||
f'{self._BASE_URL}/web_api/signin', None, 'Logging in', headers={
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
}, data=json.dumps({
|
||||
'session': {
|
||||
'email': username,
|
||||
'password': password,
|
||||
},
|
||||
}).encode(), expected_status=401)
|
||||
|
||||
if sign_in.get('error'):
|
||||
raise ExtractorError('Invalid username or password', expected=True)
|
||||
|
||||
def _real_extract(self, url):
|
||||
program_id = self._match_id(url)
|
||||
try:
|
||||
programs = self._download_json(
|
||||
f'{self._BASE_URL}/web_api/programs/{program_id}', program_id)
|
||||
except ExtractorError as e:
|
||||
if isinstance(e.cause, HTTPError) and e.cause.status == 404:
|
||||
raise ExtractorError('Invalid URL', expected=True)
|
||||
raise
|
||||
|
||||
query = {k: v[-1] for k, v in parse_qs(url).items() if v}
|
||||
if 'c' not in query:
|
||||
entries = [
|
||||
self.url_result(update_url_query(url, {'c': self._get_encoded_id(program)}), OnsenIE)
|
||||
for program in traverse_obj(programs, ('contents', lambda _, v: v['id']))
|
||||
]
|
||||
|
||||
return self.playlist_result(
|
||||
entries, program_id, traverse_obj(programs, ('program_info', 'title', {clean_html})))
|
||||
|
||||
raw_id = base64.urlsafe_b64decode(f'{query["c"]}===').decode()
|
||||
p_keys = ('contents', lambda _, v: v['id'] == int(raw_id))
|
||||
|
||||
program = traverse_obj(programs, (*p_keys, any))
|
||||
if not program:
|
||||
raise ExtractorError(
|
||||
'This program is no longer available', expected=True)
|
||||
m3u8_url = traverse_obj(program, ('streaming_url', {url_or_none}))
|
||||
if not m3u8_url:
|
||||
self.raise_login_required(
|
||||
'This program is only available for premium supporters')
|
||||
|
||||
display_id = self._get_encoded_id(program)
|
||||
date_str = self._search_regex(
|
||||
rf'{program_id}0?(\d{{6}})', m3u8_url, 'date string', default=None)
|
||||
|
||||
return {
|
||||
'display_id': display_id,
|
||||
'formats': self._extract_m3u8_formats(m3u8_url, raw_id, headers=self._HEADERS),
|
||||
'http_headers': self._HEADERS,
|
||||
'section_start': int_or_none(query.get('t', 0)),
|
||||
'upload_date': strftime_or_none(f'20{date_str}'),
|
||||
'webpage_url': f'{self._BASE_URL}/program/{program_id}?c={display_id}',
|
||||
**traverse_obj(program, {
|
||||
'id': ('id', {int}, {str_or_none}),
|
||||
'title': ('title', {clean_html}),
|
||||
'media_type': ('media_type', {str}),
|
||||
'thumbnail': ('poster_image_url', {url_or_none}, {update_url(query=None)}),
|
||||
}),
|
||||
**traverse_obj(programs, {
|
||||
'cast': (('performers', (*p_keys, 'guests')), ..., 'name', {str}, filter),
|
||||
'series_id': ('directory_name', {str}),
|
||||
}),
|
||||
**traverse_obj(programs, ('program_info', {
|
||||
'description': ('description', {clean_html}, filter),
|
||||
'series': ('title', {clean_html}),
|
||||
'tags': ('hashtag_list', ..., {str}, filter),
|
||||
})),
|
||||
}
|
@@ -1,201 +0,0 @@
|
||||
import itertools
|
||||
|
||||
from .cbs import CBSBaseIE
|
||||
from .common import InfoExtractor
|
||||
from ..utils import (
|
||||
ExtractorError,
|
||||
int_or_none,
|
||||
url_or_none,
|
||||
)
|
||||
|
||||
|
||||
class ParamountPlusIE(CBSBaseIE):
|
||||
_VALID_URL = r'''(?x)
|
||||
(?:
|
||||
paramountplus:|
|
||||
https?://(?:www\.)?(?:
|
||||
paramountplus\.com/(?:shows|movies)/(?:video|[^/]+/video|[^/]+)/
|
||||
)(?P<id>[\w-]+))'''
|
||||
|
||||
# All tests are blocked outside US
|
||||
_TESTS = [{
|
||||
'url': 'https://www.paramountplus.com/shows/video/Oe44g5_NrlgiZE3aQVONleD6vXc8kP0k/',
|
||||
'info_dict': {
|
||||
'id': 'Oe44g5_NrlgiZE3aQVONleD6vXc8kP0k',
|
||||
'ext': 'mp4',
|
||||
'title': 'CatDog - Climb Every CatDog/The Canine Mutiny',
|
||||
'description': 'md5:7ac835000645a69933df226940e3c859',
|
||||
'duration': 1426,
|
||||
'timestamp': 920264400,
|
||||
'upload_date': '19990301',
|
||||
'uploader': 'CBSI-NEW',
|
||||
'episode_number': 5,
|
||||
'thumbnail': r're:https?://.+\.jpg$',
|
||||
'season': 'Season 2',
|
||||
'chapters': 'count:3',
|
||||
'episode': 'Episode 5',
|
||||
'season_number': 2,
|
||||
'series': 'CatDog',
|
||||
},
|
||||
'params': {
|
||||
'skip_download': 'm3u8',
|
||||
},
|
||||
}, {
|
||||
'url': 'https://www.paramountplus.com/shows/video/6hSWYWRrR9EUTz7IEe5fJKBhYvSUfexd/',
|
||||
'info_dict': {
|
||||
'id': '6hSWYWRrR9EUTz7IEe5fJKBhYvSUfexd',
|
||||
'ext': 'mp4',
|
||||
'title': '7/23/21 WEEK IN REVIEW (Rep. Jahana Hayes/Howard Fineman/Sen. Michael Bennet/Sheera Frenkel & Cecilia Kang)',
|
||||
'description': 'md5:f4adcea3e8b106192022e121f1565bae',
|
||||
'duration': 2506,
|
||||
'timestamp': 1627063200,
|
||||
'upload_date': '20210723',
|
||||
'uploader': 'CBSI-NEW',
|
||||
'episode_number': 81,
|
||||
'thumbnail': r're:https?://.+\.jpg$',
|
||||
'season': 'Season 2',
|
||||
'chapters': 'count:4',
|
||||
'episode': 'Episode 81',
|
||||
'season_number': 2,
|
||||
'series': 'Tooning Out The News',
|
||||
},
|
||||
'params': {
|
||||
'skip_download': 'm3u8',
|
||||
},
|
||||
}, {
|
||||
'url': 'https://www.paramountplus.com/movies/video/vM2vm0kE6vsS2U41VhMRKTOVHyQAr6pC/',
|
||||
'info_dict': {
|
||||
'id': 'vM2vm0kE6vsS2U41VhMRKTOVHyQAr6pC',
|
||||
'ext': 'mp4',
|
||||
'title': 'Daddy\'s Home',
|
||||
'upload_date': '20151225',
|
||||
'description': 'md5:9a6300c504d5e12000e8707f20c54745',
|
||||
'uploader': 'CBSI-NEW',
|
||||
'timestamp': 1451030400,
|
||||
'thumbnail': r're:https?://.+\.jpg$',
|
||||
'chapters': 'count:0',
|
||||
'duration': 5761,
|
||||
'series': 'Paramount+ Movies',
|
||||
},
|
||||
'params': {
|
||||
'skip_download': 'm3u8',
|
||||
},
|
||||
'skip': 'DRM',
|
||||
}, {
|
||||
'url': 'https://www.paramountplus.com/movies/video/5EKDXPOzdVf9voUqW6oRuocyAEeJGbEc/',
|
||||
'info_dict': {
|
||||
'id': '5EKDXPOzdVf9voUqW6oRuocyAEeJGbEc',
|
||||
'ext': 'mp4',
|
||||
'uploader': 'CBSI-NEW',
|
||||
'description': 'md5:bc7b6fea84ba631ef77a9bda9f2ff911',
|
||||
'timestamp': 1577865600,
|
||||
'title': 'Sonic the Hedgehog',
|
||||
'upload_date': '20200101',
|
||||
'thumbnail': r're:https?://.+\.jpg$',
|
||||
'chapters': 'count:0',
|
||||
'duration': 5932,
|
||||
'series': 'Paramount+ Movies',
|
||||
},
|
||||
'params': {
|
||||
'skip_download': 'm3u8',
|
||||
},
|
||||
'skip': 'DRM',
|
||||
}, {
|
||||
'url': 'https://www.paramountplus.com/shows/the-real-world/video/mOVeHeL9ub9yWdyzSZFYz8Uj4ZBkVzQg/the-real-world-reunion/',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://www.paramountplus.com/shows/video/mOVeHeL9ub9yWdyzSZFYz8Uj4ZBkVzQg/',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://www.paramountplus.com/movies/video/W0VyStQqUnqKzJkrpSAIARuCc9YuYGNy/',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://www.paramountplus.com/movies/paw-patrol-the-movie/W0VyStQqUnqKzJkrpSAIARuCc9YuYGNy/',
|
||||
'only_matching': True,
|
||||
}]
|
||||
|
||||
def _extract_video_info(self, content_id, mpx_acc=2198311517):
|
||||
items_data = self._download_json(
|
||||
f'https://www.paramountplus.com/apps-api/v2.0/androidtv/video/cid/{content_id}.json',
|
||||
content_id, query={
|
||||
'locale': 'en-us',
|
||||
'at': 'ABCXgPuoStiPipsK0OHVXIVh68zNys+G4f7nW9R6qH68GDOcneW6Kg89cJXGfiQCsj0=',
|
||||
}, headers=self.geo_verification_headers())
|
||||
|
||||
asset_types = {
|
||||
item.get('assetType'): {
|
||||
'format': 'SMIL',
|
||||
'formats': 'M3U+none,MPEG4', # '+none' specifies ProtectionScheme (no DRM)
|
||||
} for item in items_data['itemList']
|
||||
}
|
||||
item = items_data['itemList'][-1]
|
||||
|
||||
info, error = {}, None
|
||||
metadata = {
|
||||
'title': item.get('title'),
|
||||
'series': item.get('seriesTitle'),
|
||||
'season_number': int_or_none(item.get('seasonNum')),
|
||||
'episode_number': int_or_none(item.get('episodeNum')),
|
||||
'duration': int_or_none(item.get('duration')),
|
||||
'thumbnail': url_or_none(item.get('thumbnail')),
|
||||
}
|
||||
try:
|
||||
info = self._extract_common_video_info(content_id, asset_types, mpx_acc, extra_info=metadata)
|
||||
except ExtractorError as e:
|
||||
error = e
|
||||
|
||||
# Check for DRM formats to give appropriate error
|
||||
if not info.get('formats'):
|
||||
for query in asset_types.values():
|
||||
query['formats'] = 'MPEG-DASH,M3U,MPEG4' # allows DRM formats
|
||||
|
||||
try:
|
||||
drm_info = self._extract_common_video_info(content_id, asset_types, mpx_acc, extra_info=metadata)
|
||||
except ExtractorError:
|
||||
if error:
|
||||
raise error from None
|
||||
raise
|
||||
if drm_info['formats']:
|
||||
self.report_drm(content_id)
|
||||
elif error:
|
||||
raise error
|
||||
|
||||
return info
|
||||
|
||||
|
||||
class ParamountPlusSeriesIE(InfoExtractor):
|
||||
_VALID_URL = r'https?://(?:www\.)?paramountplus\.com/shows/(?P<id>[a-zA-Z0-9-_]+)/?(?:[#?]|$)'
|
||||
_TESTS = [{
|
||||
'url': 'https://www.paramountplus.com/shows/drake-josh',
|
||||
'playlist_mincount': 50,
|
||||
'info_dict': {
|
||||
'id': 'drake-josh',
|
||||
},
|
||||
}, {
|
||||
'url': 'https://www.paramountplus.com/shows/hawaii_five_0/',
|
||||
'playlist_mincount': 240,
|
||||
'info_dict': {
|
||||
'id': 'hawaii_five_0',
|
||||
},
|
||||
}, {
|
||||
'url': 'https://www.paramountplus.com/shows/spongebob-squarepants/',
|
||||
'playlist_mincount': 248,
|
||||
'info_dict': {
|
||||
'id': 'spongebob-squarepants',
|
||||
},
|
||||
}]
|
||||
|
||||
def _entries(self, show_name):
|
||||
for page in itertools.count():
|
||||
show_json = self._download_json(
|
||||
f'https://www.paramountplus.com/shows/{show_name}/xhr/episodes/page/{page}/size/50/xs/0/season/0', show_name)
|
||||
if not show_json.get('success'):
|
||||
return
|
||||
for episode in show_json['result']['data']:
|
||||
yield self.url_result(
|
||||
'https://www.paramountplus.com{}'.format(episode['url']),
|
||||
ie=ParamountPlusIE.ie_key(), video_id=episode['content_id'])
|
||||
|
||||
def _real_extract(self, url):
|
||||
show_name = self._match_id(url)
|
||||
return self.playlist_result(self._entries(show_name), playlist_id=show_name)
|
@@ -1,191 +0,0 @@
|
||||
import re
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..utils import ExtractorError, int_or_none, str_to_int
|
||||
|
||||
|
||||
class RUTVIE(InfoExtractor):
|
||||
IE_DESC = 'RUTV.RU'
|
||||
_VALID_URL = r'''(?x)
|
||||
https?://
|
||||
(?:test)?player\.(?:rutv\.ru|vgtrk\.com)/
|
||||
(?P<path>
|
||||
flash\d+v/container\.swf\?id=|
|
||||
iframe/(?P<type>swf|video|live)/id/|
|
||||
index/iframe/cast_id/
|
||||
)
|
||||
(?P<id>\d+)
|
||||
'''
|
||||
_EMBED_REGEX = [
|
||||
r'<iframe[^>]+?src=(["\'])(?P<url>https?://(?:test)?player\.(?:rutv\.ru|vgtrk\.com)/(?:iframe/(?:swf|video|live)/id|index/iframe/cast_id)/.+?)\1',
|
||||
r'<meta[^>]+?property=(["\'])og:video\1[^>]+?content=(["\'])(?P<url>https?://(?:test)?player\.(?:rutv\.ru|vgtrk\.com)/flash\d+v/container\.swf\?id=.+?\2)',
|
||||
]
|
||||
|
||||
_TESTS = [{
|
||||
'url': 'http://player.rutv.ru/flash2v/container.swf?id=774471&sid=kultura&fbv=true&isPlay=true&ssl=false&i=560&acc_video_id=episode_id/972347/video_id/978186/brand_id/31724',
|
||||
'info_dict': {
|
||||
'id': '774471',
|
||||
'ext': 'mp4',
|
||||
'title': 'Монологи на все времена. Концерт',
|
||||
'description': 'md5:18d8b5e6a41fb1faa53819471852d5d5',
|
||||
'duration': 2906,
|
||||
'thumbnail': r're:https?://cdn-st2\.smotrim\.ru/.+\.jpg',
|
||||
},
|
||||
'params': {'skip_download': 'm3u8'},
|
||||
}, {
|
||||
'url': 'https://player.vgtrk.com/flash2v/container.swf?id=774016&sid=russiatv&fbv=true&isPlay=true&ssl=false&i=560&acc_video_id=episode_id/972098/video_id/977760/brand_id/57638',
|
||||
'info_dict': {
|
||||
'id': '774016',
|
||||
'ext': 'mp4',
|
||||
'title': 'Чужой в семье Сталина',
|
||||
'description': '',
|
||||
'duration': 2539,
|
||||
},
|
||||
'skip': 'Invalid URL',
|
||||
}, {
|
||||
'url': 'http://player.rutv.ru/iframe/swf/id/766888/sid/hitech/?acc_video_id=4000',
|
||||
'info_dict': {
|
||||
'id': '766888',
|
||||
'ext': 'mp4',
|
||||
'title': 'Вести.net: интернет-гиганты начали перетягивание программных "одеял"',
|
||||
'description': 'md5:65ddd47f9830c4f42ed6475f8730c995',
|
||||
'duration': 279,
|
||||
'thumbnail': r're:https?://cdn-st2\.smotrim\.ru/.+\.jpg',
|
||||
},
|
||||
'params': {'skip_download': 'm3u8'},
|
||||
}, {
|
||||
'url': 'http://player.rutv.ru/iframe/video/id/771852/start_zoom/true/showZoomBtn/false/sid/russiatv/?acc_video_id=episode_id/970443/video_id/975648/brand_id/5169',
|
||||
'info_dict': {
|
||||
'id': '771852',
|
||||
'ext': 'mp4',
|
||||
'title': 'Прямой эфир. Жертвы загадочной болезни: смерть от старости в 17 лет',
|
||||
'description': 'md5:b81c8c55247a4bd996b43ce17395b2d8',
|
||||
'duration': 3096,
|
||||
'thumbnail': r're:https?://cdn-st2\.smotrim\.ru/.+\.jpg',
|
||||
},
|
||||
'params': {'skip_download': 'm3u8'},
|
||||
}, {
|
||||
'url': 'http://player.rutv.ru/iframe/live/id/51499/showZoomBtn/false/isPlay/true/sid/sochi2014',
|
||||
'info_dict': {
|
||||
'id': '51499',
|
||||
'ext': 'flv',
|
||||
'title': 'Сочи-2014. Биатлон. Индивидуальная гонка. Мужчины ',
|
||||
'description': 'md5:9e0ed5c9d2fa1efbfdfed90c9a6d179c',
|
||||
},
|
||||
'skip': 'Invalid URL',
|
||||
}, {
|
||||
'url': 'http://player.rutv.ru/iframe/live/id/21/showZoomBtn/false/isPlay/true/',
|
||||
'info_dict': {
|
||||
'id': '21',
|
||||
'ext': 'mp4',
|
||||
'title': str,
|
||||
'is_live': True,
|
||||
},
|
||||
'skip': 'Invalid URL',
|
||||
}, {
|
||||
'url': 'https://testplayer.vgtrk.com/iframe/live/id/19201/showZoomBtn/false/isPlay/true/',
|
||||
'only_matching': True,
|
||||
}]
|
||||
_WEBPAGE_TESTS = [{
|
||||
'url': 'http://istoriya-teatra.ru/news/item/f00/s05/n0000545/index.shtml',
|
||||
'info_dict': {
|
||||
'id': '1952012',
|
||||
'ext': 'mp4',
|
||||
'title': 'Новости культуры. Эфир от 10.10.2019 (23:30). Театр Сатиры отмечает день рождения премьерой',
|
||||
'description': 'md5:fced27112ff01ff8fc4a452fc088bad6',
|
||||
'duration': 191,
|
||||
'thumbnail': r're:https?://cdn-st2\.smotrim\.ru/.+\.jpg',
|
||||
},
|
||||
'params': {'skip_download': 'm3u8'},
|
||||
}]
|
||||
|
||||
def _real_extract(self, url):
|
||||
mobj = self._match_valid_url(url)
|
||||
video_id = mobj.group('id')
|
||||
video_path = mobj.group('path')
|
||||
|
||||
if re.match(r'flash\d+v', video_path):
|
||||
video_type = 'video'
|
||||
elif video_path.startswith('iframe'):
|
||||
video_type = mobj.group('type')
|
||||
if video_type == 'swf':
|
||||
video_type = 'video'
|
||||
elif video_path.startswith('index/iframe/cast_id'):
|
||||
video_type = 'live'
|
||||
|
||||
is_live = video_type == 'live'
|
||||
|
||||
json_data = self._download_json(
|
||||
'http://player.vgtrk.com/iframe/data{}/id/{}'.format('live' if is_live else 'video', video_id),
|
||||
video_id, 'Downloading JSON')
|
||||
|
||||
if json_data['errors']:
|
||||
raise ExtractorError('{} said: {}'.format(self.IE_NAME, json_data['errors']), expected=True)
|
||||
|
||||
playlist = json_data['data']['playlist']
|
||||
medialist = playlist['medialist']
|
||||
media = medialist[0]
|
||||
|
||||
if media['errors']:
|
||||
raise ExtractorError('{} said: {}'.format(self.IE_NAME, media['errors']), expected=True)
|
||||
|
||||
view_count = int_or_none(playlist.get('count_views'))
|
||||
priority_transport = playlist['priority_transport']
|
||||
|
||||
thumbnail = media['picture']
|
||||
width = int_or_none(media['width'])
|
||||
height = int_or_none(media['height'])
|
||||
description = media['anons']
|
||||
title = media['title']
|
||||
duration = int_or_none(media.get('duration'))
|
||||
|
||||
formats = []
|
||||
subtitles = {}
|
||||
|
||||
for transport, links in media['sources'].items():
|
||||
for quality, url in links.items():
|
||||
preference = -1 if priority_transport == transport else -2
|
||||
if transport == 'rtmp':
|
||||
mobj = re.search(r'^(?P<url>rtmp://[^/]+/(?P<app>.+))/(?P<playpath>.+)$', url)
|
||||
if not mobj:
|
||||
continue
|
||||
fmt = {
|
||||
'url': mobj.group('url'),
|
||||
'play_path': mobj.group('playpath'),
|
||||
'app': mobj.group('app'),
|
||||
'page_url': 'http://player.rutv.ru',
|
||||
'player_url': 'http://player.rutv.ru/flash3v/osmf.swf?i=22',
|
||||
'rtmp_live': True,
|
||||
'ext': 'flv',
|
||||
'vbr': str_to_int(quality),
|
||||
}
|
||||
elif transport == 'm3u8':
|
||||
fmt, subs = self._extract_m3u8_formats_and_subtitles(
|
||||
url, video_id, 'mp4', quality=preference, m3u8_id='hls')
|
||||
formats.extend(fmt)
|
||||
self._merge_subtitles(subs, target=subtitles)
|
||||
continue
|
||||
else:
|
||||
fmt = {
|
||||
'url': url,
|
||||
}
|
||||
fmt.update({
|
||||
'width': int_or_none(quality, default=height, invscale=width, scale=height),
|
||||
'height': int_or_none(quality, default=height),
|
||||
'format_id': f'{transport}-{quality}',
|
||||
'source_preference': preference,
|
||||
})
|
||||
formats.append(fmt)
|
||||
|
||||
return {
|
||||
'id': video_id,
|
||||
'title': title,
|
||||
'description': description,
|
||||
'thumbnail': thumbnail,
|
||||
'view_count': view_count,
|
||||
'duration': duration,
|
||||
'formats': formats,
|
||||
'subtitles': subtitles,
|
||||
'is_live': is_live,
|
||||
'_format_sort_fields': ('source', ),
|
||||
}
|
@@ -1,119 +0,0 @@
|
||||
from .common import InfoExtractor
|
||||
from ..utils import (
|
||||
determine_ext,
|
||||
int_or_none,
|
||||
parse_qs,
|
||||
qualities,
|
||||
try_get,
|
||||
)
|
||||
|
||||
|
||||
class SixPlayIE(InfoExtractor):
|
||||
IE_NAME = '6play'
|
||||
_VALID_URL = r'(?:6play:|https?://(?:www\.)?(?P<domain>6play\.fr|rtlplay\.be|play\.rtl\.hr|rtlmost\.hu)/.+?-c_)(?P<id>[0-9]+)'
|
||||
_TESTS = [{
|
||||
'url': 'https://www.6play.fr/minute-par-minute-p_9533/le-but-qui-a-marque-lhistoire-du-football-francais-c_12041051',
|
||||
'md5': '31fcd112637baa0c2ab92c4fcd8baf27',
|
||||
'info_dict': {
|
||||
'id': '12041051',
|
||||
'ext': 'mp4',
|
||||
'title': 'Le but qui a marqué l\'histoire du football français !',
|
||||
'description': 'md5:b59e7e841d646ef1eb42a7868eb6a851',
|
||||
},
|
||||
}, {
|
||||
'url': 'https://www.rtlplay.be/rtl-info-13h-p_8551/les-titres-du-rtlinfo-13h-c_12045869',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://play.rtl.hr/pj-masks-p_9455/epizoda-34-sezona-1-catboyevo-cudo-na-dva-kotaca-c_11984989',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://www.rtlmost.hu/megtorve-p_14167/megtorve-6-resz-c_12397787',
|
||||
'only_matching': True,
|
||||
}]
|
||||
|
||||
def _real_extract(self, url):
|
||||
domain, video_id = self._match_valid_url(url).groups()
|
||||
service, consumer_name = {
|
||||
'6play.fr': ('6play', 'm6web'),
|
||||
'rtlplay.be': ('rtlbe_rtl_play', 'rtlbe'),
|
||||
'play.rtl.hr': ('rtlhr_rtl_play', 'rtlhr'),
|
||||
'rtlmost.hu': ('rtlhu_rtl_most', 'rtlhu'),
|
||||
}.get(domain, ('6play', 'm6web'))
|
||||
|
||||
data = self._download_json(
|
||||
f'https://pc.middleware.6play.fr/6play/v2/platforms/m6group_web/services/{service}/videos/clip_{video_id}',
|
||||
video_id, headers={
|
||||
'x-customer-name': consumer_name,
|
||||
}, query={
|
||||
'csa': 5,
|
||||
'with': 'clips',
|
||||
})
|
||||
|
||||
clip_data = data['clips'][0]
|
||||
title = clip_data['title']
|
||||
|
||||
urls = []
|
||||
quality_key = qualities(['lq', 'sd', 'hq', 'hd'])
|
||||
formats = []
|
||||
subtitles = {}
|
||||
assets = clip_data.get('assets') or []
|
||||
for asset in assets:
|
||||
asset_url = asset.get('full_physical_path')
|
||||
protocol = asset.get('protocol')
|
||||
if not asset_url or ((protocol == 'primetime' or asset.get('type') == 'usp_hlsfp_h264') and not ('_drmnp.ism/' in asset_url or '_unpnp.ism/' in asset_url)) or asset_url in urls:
|
||||
continue
|
||||
urls.append(asset_url)
|
||||
container = asset.get('video_container')
|
||||
ext = determine_ext(asset_url)
|
||||
if protocol == 'http_subtitle' or ext == 'vtt':
|
||||
subtitles.setdefault('fr', []).append({'url': asset_url})
|
||||
continue
|
||||
if container == 'm3u8' or ext == 'm3u8':
|
||||
if protocol == 'usp':
|
||||
if parse_qs(asset_url).get('token', [None])[0]:
|
||||
urlh = self._request_webpage(
|
||||
asset_url, video_id, fatal=False,
|
||||
headers=self.geo_verification_headers())
|
||||
if not urlh:
|
||||
continue
|
||||
asset_url = urlh.url
|
||||
asset_url = asset_url.replace('_drmnp.ism/', '_unpnp.ism/')
|
||||
for i in range(3, 0, -1):
|
||||
asset_url = asset_url.replace('_sd1/', f'_sd{i}/')
|
||||
m3u8_formats = self._extract_m3u8_formats(
|
||||
asset_url, video_id, 'mp4', 'm3u8_native',
|
||||
m3u8_id='hls', fatal=False)
|
||||
formats.extend(m3u8_formats)
|
||||
formats.extend(self._extract_mpd_formats(
|
||||
asset_url.replace('.m3u8', '.mpd'),
|
||||
video_id, mpd_id='dash', fatal=False))
|
||||
if m3u8_formats:
|
||||
break
|
||||
else:
|
||||
formats.extend(self._extract_m3u8_formats(
|
||||
asset_url, video_id, 'mp4', 'm3u8_native',
|
||||
m3u8_id='hls', fatal=False))
|
||||
elif container == 'mp4' or ext == 'mp4':
|
||||
quality = asset.get('video_quality')
|
||||
formats.append({
|
||||
'url': asset_url,
|
||||
'format_id': quality,
|
||||
'quality': quality_key(quality),
|
||||
'ext': ext,
|
||||
})
|
||||
|
||||
def get(getter):
|
||||
for src in (data, clip_data):
|
||||
v = try_get(src, getter, str)
|
||||
if v:
|
||||
return v
|
||||
|
||||
return {
|
||||
'id': video_id,
|
||||
'title': title,
|
||||
'description': get(lambda x: x['description']),
|
||||
'duration': int_or_none(clip_data.get('duration')),
|
||||
'series': get(lambda x: x['program']['title']),
|
||||
'formats': formats,
|
||||
'subtitles': subtitles,
|
||||
}
|
@@ -1,65 +1,403 @@
|
||||
import functools
|
||||
import json
|
||||
import re
|
||||
import urllib.parse
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..utils import ExtractorError
|
||||
from ..utils import (
|
||||
OnDemandPagedList,
|
||||
clean_html,
|
||||
determine_ext,
|
||||
extract_attributes,
|
||||
int_or_none,
|
||||
parse_iso8601,
|
||||
str_or_none,
|
||||
unescapeHTML,
|
||||
url_or_none,
|
||||
urljoin,
|
||||
)
|
||||
from ..utils.traversal import (
|
||||
find_element,
|
||||
find_elements,
|
||||
require,
|
||||
traverse_obj,
|
||||
)
|
||||
|
||||
|
||||
class SmotrimIE(InfoExtractor):
|
||||
_VALID_URL = r'https?://smotrim\.ru/(?P<type>brand|video|article|live)/(?P<id>[0-9]+)'
|
||||
_TESTS = [{ # video
|
||||
class SmotrimBaseIE(InfoExtractor):
|
||||
_BASE_URL = 'https://smotrim.ru'
|
||||
_GEO_BYPASS = False
|
||||
_GEO_COUNTRIES = ['RU']
|
||||
|
||||
def _extract_from_smotrim_api(self, typ, item_id):
|
||||
path = f'data{typ.replace("-", "")}/{"uid" if typ == "live" else "id"}'
|
||||
data = self._download_json(
|
||||
f'https://player.smotrim.ru/iframe/{path}/{item_id}/sid/smotrim', item_id)
|
||||
media = traverse_obj(data, ('data', 'playlist', 'medialist', -1, {dict}))
|
||||
if traverse_obj(media, ('locked', {bool})):
|
||||
self.raise_login_required()
|
||||
if error_msg := traverse_obj(media, ('errors', {clean_html})):
|
||||
self.raise_geo_restricted(error_msg, countries=self._GEO_COUNTRIES)
|
||||
|
||||
webpage_url = traverse_obj(data, ('data', 'template', 'share_url', {url_or_none}))
|
||||
webpage = self._download_webpage(webpage_url, item_id)
|
||||
common = {
|
||||
'thumbnail': self._html_search_meta(['og:image', 'twitter:image'], webpage, default=None),
|
||||
**traverse_obj(media, {
|
||||
'id': ('id', {str_or_none}),
|
||||
'title': (('episodeTitle', 'title'), {clean_html}, filter, any),
|
||||
'channel_id': ('channelId', {str_or_none}),
|
||||
'description': ('anons', {clean_html}, filter),
|
||||
'season': ('season', {clean_html}, filter),
|
||||
'series': (('brand_title', 'brandTitle'), {clean_html}, filter, any),
|
||||
'series_id': ('brand_id', {str_or_none}),
|
||||
}),
|
||||
}
|
||||
|
||||
if typ == 'audio':
|
||||
bookmark = self._search_json(
|
||||
r'class="bookmark"[^>]+value\s*=\s*"', webpage,
|
||||
'bookmark', item_id, default={}, transform_source=unescapeHTML)
|
||||
|
||||
metadata = {
|
||||
'vcodec': 'none',
|
||||
**common,
|
||||
**traverse_obj(media, {
|
||||
'ext': ('audio_url', {determine_ext(default_ext='mp3')}),
|
||||
'duration': ('duration', {int_or_none}),
|
||||
'url': ('audio_url', {url_or_none}),
|
||||
}),
|
||||
**traverse_obj(bookmark, {
|
||||
'title': ('subtitle', {clean_html}),
|
||||
'timestamp': ('published', {parse_iso8601}),
|
||||
}),
|
||||
}
|
||||
elif typ == 'audio-live':
|
||||
metadata = {
|
||||
'ext': 'mp3',
|
||||
'url': traverse_obj(media, ('source', 'auto', {url_or_none})),
|
||||
'vcodec': 'none',
|
||||
**common,
|
||||
}
|
||||
else:
|
||||
formats, subtitles = [], {}
|
||||
for m3u8_url in traverse_obj(media, (
|
||||
'sources', 'm3u8', {dict.values}, ..., {url_or_none},
|
||||
)):
|
||||
fmts, subs = self._extract_m3u8_formats_and_subtitles(
|
||||
m3u8_url, item_id, 'mp4', m3u8_id='hls', fatal=False)
|
||||
formats.extend(fmts)
|
||||
self._merge_subtitles(subs, target=subtitles)
|
||||
|
||||
metadata = {
|
||||
'formats': formats,
|
||||
'subtitles': subtitles,
|
||||
**self._search_json_ld(webpage, item_id),
|
||||
**common,
|
||||
}
|
||||
|
||||
return {
|
||||
'age_limit': traverse_obj(data, ('data', 'age_restrictions', {int_or_none})),
|
||||
'is_live': typ in ('audio-live', 'live'),
|
||||
'tags': traverse_obj(webpage, (
|
||||
{find_elements(cls='tags-list__link')}, ..., {clean_html}, filter, all, filter)),
|
||||
'webpage_url': webpage_url,
|
||||
**metadata,
|
||||
}
|
||||
|
||||
|
||||
class SmotrimIE(SmotrimBaseIE):
|
||||
IE_NAME = 'smotrim'
|
||||
_VALID_URL = r'(?:https?:)?//(?:(?:player|www)\.)?smotrim\.ru(?:/iframe)?/video(?:/id)?/(?P<id>\d+)'
|
||||
_EMBED_REGEX = [fr'<iframe\b[^>]+\bsrc=["\'](?P<url>{_VALID_URL})']
|
||||
_TESTS = [{
|
||||
'url': 'https://smotrim.ru/video/1539617',
|
||||
'md5': 'b1923a533c8cab09679789d720d0b1c5',
|
||||
'info_dict': {
|
||||
'id': '1539617',
|
||||
'ext': 'mp4',
|
||||
'title': 'Полиглот. Китайский с нуля за 16 часов! Урок №16',
|
||||
'description': '',
|
||||
'title': 'Урок №16',
|
||||
'duration': 2631,
|
||||
'series': 'Полиглот. Китайский с нуля за 16 часов!',
|
||||
'series_id': '60562',
|
||||
'tags': 'mincount:6',
|
||||
'thumbnail': r're:https?://cdn-st\d+\.smotrim\.ru/.+\.(?:jpg|png)',
|
||||
'timestamp': 1466771100,
|
||||
'upload_date': '20160624',
|
||||
'view_count': int,
|
||||
},
|
||||
'add_ie': ['RUTV'],
|
||||
}, { # article (geo-restricted? plays fine from the US and JP)
|
||||
}, {
|
||||
'url': 'https://player.smotrim.ru/iframe/video/id/2988590',
|
||||
'info_dict': {
|
||||
'id': '2988590',
|
||||
'ext': 'mp4',
|
||||
'title': 'Трейлер',
|
||||
'age_limit': 16,
|
||||
'description': 'md5:6af7e68ecf4ed7b8ff6720d20c4da47b',
|
||||
'duration': 30,
|
||||
'series': 'Мы в разводе',
|
||||
'series_id': '71624',
|
||||
'tags': 'mincount:5',
|
||||
'thumbnail': r're:https?://cdn-st\d+\.smotrim\.ru/.+\.(?:jpg|png)',
|
||||
'timestamp': 1750670040,
|
||||
'upload_date': '20250623',
|
||||
'view_count': int,
|
||||
'webpage_url': 'https://smotrim.ru/video/2988590',
|
||||
},
|
||||
}]
|
||||
_WEBPAGE_TESTS = [{
|
||||
'url': 'https://smotrim.ru/article/2813445',
|
||||
'md5': 'e0ac453952afbc6a2742e850b4dc8e77',
|
||||
'info_dict': {
|
||||
'id': '2431846',
|
||||
'ext': 'mp4',
|
||||
'title': 'Новости культуры. Съёмки первой программы "Большие и маленькие"',
|
||||
'description': 'md5:94a4a22472da4252bf5587a4ee441b99',
|
||||
'title': 'Съёмки первой программы "Большие и маленькие"',
|
||||
'description': 'md5:446c9a5d334b995152a813946353f447',
|
||||
'duration': 240,
|
||||
'series': 'Новости культуры',
|
||||
'series_id': '19725',
|
||||
'tags': 'mincount:6',
|
||||
'thumbnail': r're:https?://cdn-st\d+\.smotrim\.ru/.+\.(?:jpg|png)',
|
||||
'timestamp': 1656054443,
|
||||
'upload_date': '20220624',
|
||||
'view_count': int,
|
||||
'webpage_url': 'https://smotrim.ru/video/2431846',
|
||||
},
|
||||
'add_ie': ['RUTV'],
|
||||
}, { # brand, redirect
|
||||
'url': 'https://smotrim.ru/brand/64356',
|
||||
'md5': '740472999ccff81d7f6df79cecd91c18',
|
||||
}, {
|
||||
'url': 'https://www.vesti.ru/article/4642878',
|
||||
'info_dict': {
|
||||
'id': '2354523',
|
||||
'id': '3007209',
|
||||
'ext': 'mp4',
|
||||
'title': 'Большие и маленькие. Лучшее. 4-й выпуск',
|
||||
'description': 'md5:84089e834429008371ea41ea3507b989',
|
||||
'title': 'Иностранные мессенджеры используют не только мошенники, но и вербовщики',
|
||||
'description': 'md5:74ab625a0a89b87b2e0ed98d6391b182',
|
||||
'duration': 265,
|
||||
'series': 'Вести. Дежурная часть',
|
||||
'series_id': '5204',
|
||||
'tags': 'mincount:6',
|
||||
'thumbnail': r're:https?://cdn-st\d+\.smotrim\.ru/.+\.(?:jpg|png)',
|
||||
'timestamp': 1754756280,
|
||||
'upload_date': '20250809',
|
||||
'view_count': int,
|
||||
'webpage_url': 'https://smotrim.ru/video/3007209',
|
||||
},
|
||||
'add_ie': ['RUTV'],
|
||||
}, { # live
|
||||
'url': 'https://smotrim.ru/live/19201',
|
||||
'info_dict': {
|
||||
'id': '19201',
|
||||
'ext': 'mp4',
|
||||
# this looks like a TV channel name
|
||||
'title': 'Россия Культура. Прямой эфир',
|
||||
'description': '',
|
||||
},
|
||||
'add_ie': ['RUTV'],
|
||||
}]
|
||||
|
||||
def _real_extract(self, url):
|
||||
video_id, typ = self._match_valid_url(url).group('id', 'type')
|
||||
rutv_type = 'video'
|
||||
if typ not in ('video', 'live'):
|
||||
webpage = self._download_webpage(url, video_id, f'Resolving {typ} link')
|
||||
# there are two cases matching regex:
|
||||
# 1. "embedUrl" in JSON LD (/brand/)
|
||||
# 2. "src" attribute from iframe (/article/)
|
||||
video_id = self._search_regex(
|
||||
r'"https://player.smotrim.ru/iframe/video/id/(?P<video_id>\d+)/',
|
||||
webpage, 'video_id', default=None)
|
||||
if not video_id:
|
||||
raise ExtractorError('There are no video in this page.', expected=True)
|
||||
elif typ == 'live':
|
||||
rutv_type = 'live'
|
||||
video_id = self._match_id(url)
|
||||
|
||||
return self.url_result(f'https://player.vgtrk.com/iframe/{rutv_type}/id/{video_id}')
|
||||
return self._extract_from_smotrim_api('video', video_id)
|
||||
|
||||
|
||||
class SmotrimAudioIE(SmotrimBaseIE):
|
||||
IE_NAME = 'smotrim:audio'
|
||||
_VALID_URL = r'https?://(?:(?:player|www)\.)?smotrim\.ru(?:/iframe)?/audio(?:/id)?/(?P<id>\d+)'
|
||||
_TESTS = [{
|
||||
'url': 'https://smotrim.ru/audio/2573986',
|
||||
'md5': 'e28d94c20da524e242b2d00caef41a8e',
|
||||
'info_dict': {
|
||||
'id': '2573986',
|
||||
'ext': 'mp3',
|
||||
'title': 'Радиоспектакль',
|
||||
'description': 'md5:4bcaaf7d532bc78f76e478fad944e388',
|
||||
'duration': 3072,
|
||||
'series': 'Морис Леблан. Арсен Люпен, джентльмен-грабитель',
|
||||
'series_id': '66461',
|
||||
'tags': 'mincount:7',
|
||||
'thumbnail': r're:https?://cdn-st\d+\.smotrim\.ru/.+\.(?:jpg|png)',
|
||||
'timestamp': 1624884358,
|
||||
'upload_date': '20210628',
|
||||
},
|
||||
}, {
|
||||
'url': 'https://player.smotrim.ru/iframe/audio/id/2860468',
|
||||
'md5': '5a6bc1fa24c7142958be1ad9cfae58a8',
|
||||
'info_dict': {
|
||||
'id': '2860468',
|
||||
'ext': 'mp3',
|
||||
'title': 'Колобок и музыкальная игра "Терем-теремок"',
|
||||
'duration': 1501,
|
||||
'series': 'Веселый колобок',
|
||||
'series_id': '68880',
|
||||
'tags': 'mincount:4',
|
||||
'thumbnail': r're:https?://cdn-st\d+\.smotrim\.ru/.+\.(?:jpg|png)',
|
||||
'timestamp': 1755925800,
|
||||
'upload_date': '20250823',
|
||||
'webpage_url': 'https://smotrim.ru/audio/2860468',
|
||||
},
|
||||
}]
|
||||
|
||||
def _real_extract(self, url):
|
||||
audio_id = self._match_id(url)
|
||||
|
||||
return self._extract_from_smotrim_api('audio', audio_id)
|
||||
|
||||
|
||||
class SmotrimLiveIE(SmotrimBaseIE):
|
||||
IE_NAME = 'smotrim:live'
|
||||
_VALID_URL = r'''(?x:
|
||||
(?:https?:)?//
|
||||
(?:(?:(?:test)?player|www)\.)?
|
||||
(?:
|
||||
smotrim\.ru|
|
||||
vgtrk\.com
|
||||
)
|
||||
(?:/iframe)?/
|
||||
(?P<type>
|
||||
channel|
|
||||
(?:audio-)?live
|
||||
)
|
||||
(?:/u?id)?/(?P<id>[\da-f-]+)
|
||||
)'''
|
||||
_EMBED_REGEX = [fr'<iframe\b[^>]+\bsrc=["\'](?P<url>{_VALID_URL})']
|
||||
_TESTS = [{
|
||||
'url': 'https://smotrim.ru/channel/76',
|
||||
'info_dict': {
|
||||
'id': '1661',
|
||||
'ext': 'mp4',
|
||||
'title': str,
|
||||
'channel_id': '76',
|
||||
'description': 'Смотрим прямой эфир «Москва 24»',
|
||||
'display_id': '76',
|
||||
'live_status': 'is_live',
|
||||
'thumbnail': r're:https?://cdn-st\d+\.smotrim\.ru/.+\.(?:jpg|png)',
|
||||
'timestamp': int,
|
||||
'upload_date': str,
|
||||
},
|
||||
'params': {'skip_download': 'Livestream'},
|
||||
}, {
|
||||
# Radio
|
||||
'url': 'https://smotrim.ru/channel/81',
|
||||
'info_dict': {
|
||||
'id': '81',
|
||||
'ext': 'mp3',
|
||||
'title': str,
|
||||
'channel_id': '81',
|
||||
'live_status': 'is_live',
|
||||
'thumbnail': r're:https?://cdn-st\d+\.smotrim\.ru/.+\.(?:jpg|png)',
|
||||
},
|
||||
'params': {'skip_download': 'Livestream'},
|
||||
}, {
|
||||
# Sometimes geo-restricted to Russia
|
||||
'url': 'https://player.smotrim.ru/iframe/live/uid/381308c7-a066-4c4f-9656-83e2e792a7b4',
|
||||
'info_dict': {
|
||||
'id': '19201',
|
||||
'ext': 'mp4',
|
||||
'title': str,
|
||||
'channel_id': '4',
|
||||
'description': 'Смотрим прямой эфир «Россия К»',
|
||||
'display_id': '381308c7-a066-4c4f-9656-83e2e792a7b4',
|
||||
'live_status': 'is_live',
|
||||
'thumbnail': r're:https?://cdn-st\d+\.smotrim\.ru/.+\.(?:jpg|png)',
|
||||
'timestamp': int,
|
||||
'upload_date': str,
|
||||
'webpage_url': 'https://smotrim.ru/channel/4',
|
||||
},
|
||||
'params': {'skip_download': 'Livestream'},
|
||||
}, {
|
||||
'url': 'https://smotrim.ru/live/19201',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://player.smotrim.ru/iframe/audio-live/id/81',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://testplayer.vgtrk.com/iframe/live/id/19201',
|
||||
'only_matching': True,
|
||||
}]
|
||||
|
||||
def _real_extract(self, url):
|
||||
typ, display_id = self._match_valid_url(url).group('type', 'id')
|
||||
|
||||
if typ == 'live' and re.fullmatch(r'[0-9]+', display_id):
|
||||
url = self._request_webpage(url, display_id).url
|
||||
typ = self._match_valid_url(url).group('type')
|
||||
|
||||
if typ == 'channel':
|
||||
webpage = self._download_webpage(url, display_id)
|
||||
src_url = traverse_obj(webpage, ((
|
||||
({find_element(cls='main-player__frame', html=True)}, {extract_attributes}, 'src'),
|
||||
({find_element(cls='audio-play-button', html=True)},
|
||||
{extract_attributes}, 'value', {urllib.parse.unquote}, {json.loads}, 'source'),
|
||||
), any, {self._proto_relative_url}, {url_or_none}, {require('src URL')}))
|
||||
typ, video_id = self._match_valid_url(src_url).group('type', 'id')
|
||||
else:
|
||||
video_id = display_id
|
||||
|
||||
return {
|
||||
'display_id': display_id,
|
||||
**self._extract_from_smotrim_api(typ, video_id),
|
||||
}
|
||||
|
||||
|
||||
class SmotrimPlaylistIE(SmotrimBaseIE):
|
||||
IE_NAME = 'smotrim:playlist'
|
||||
_PAGE_SIZE = 15
|
||||
_VALID_URL = r'https?://smotrim\.ru/(?P<type>brand|podcast)/(?P<id>\d+)/?(?P<season>[\w-]+)?'
|
||||
_TESTS = [{
|
||||
# Video
|
||||
'url': 'https://smotrim.ru/brand/64356',
|
||||
'info_dict': {
|
||||
'id': '64356',
|
||||
'title': 'Большие и маленькие',
|
||||
},
|
||||
'playlist_mincount': 55,
|
||||
}, {
|
||||
# Video, season
|
||||
'url': 'https://smotrim.ru/brand/65293/3-sezon',
|
||||
'info_dict': {
|
||||
'id': '65293',
|
||||
'title': 'Спасская',
|
||||
'season': '3 сезон',
|
||||
},
|
||||
'playlist_count': 16,
|
||||
}, {
|
||||
# Audio
|
||||
'url': 'https://smotrim.ru/brand/68880',
|
||||
'info_dict': {
|
||||
'id': '68880',
|
||||
'title': 'Веселый колобок',
|
||||
},
|
||||
'playlist_mincount': 156,
|
||||
}, {
|
||||
# Podcast
|
||||
'url': 'https://smotrim.ru/podcast/8021',
|
||||
'info_dict': {
|
||||
'id': '8021',
|
||||
'title': 'Сила звука',
|
||||
},
|
||||
'playlist_mincount': 27,
|
||||
}]
|
||||
|
||||
def _fetch_page(self, endpoint, key, playlist_id, page):
|
||||
page += 1
|
||||
items = self._download_json(
|
||||
f'{self._BASE_URL}/api/{endpoint}', playlist_id,
|
||||
f'Downloading page {page}', query={
|
||||
key: playlist_id,
|
||||
'limit': self._PAGE_SIZE,
|
||||
'page': page,
|
||||
},
|
||||
)
|
||||
|
||||
for link in traverse_obj(items, ('contents', -1, 'list', ..., 'link', {str})):
|
||||
yield self.url_result(urljoin(self._BASE_URL, link))
|
||||
|
||||
def _real_extract(self, url):
|
||||
playlist_type, playlist_id, season = self._match_valid_url(url).group('type', 'id', 'season')
|
||||
key = 'rubricId' if playlist_type == 'podcast' else 'brandId'
|
||||
webpage = self._download_webpage(url, playlist_id)
|
||||
playlist_title = self._html_search_meta(['og:title', 'twitter:title'], webpage, default=None)
|
||||
|
||||
if season:
|
||||
return self.playlist_from_matches(traverse_obj(webpage, (
|
||||
{find_elements(tag='a', attr='href', value=r'/video/\d+', html=True, regex=True)},
|
||||
..., {extract_attributes}, 'href', {str},
|
||||
)), playlist_id, playlist_title, season=traverse_obj(webpage, (
|
||||
{find_element(cls='seasons__item seasons__item--selected')}, {clean_html},
|
||||
)), ie=SmotrimIE, getter=urljoin(self._BASE_URL))
|
||||
|
||||
if traverse_obj(webpage, (
|
||||
{find_element(cls='brand-main-item__videos')}, {clean_html}, filter,
|
||||
)):
|
||||
endpoint = 'videos'
|
||||
else:
|
||||
endpoint = 'audios'
|
||||
|
||||
return self.playlist_result(OnDemandPagedList(
|
||||
functools.partial(self._fetch_page, endpoint, key, playlist_id), self._PAGE_SIZE), playlist_id, playlist_title)
|
||||
|
@@ -1,167 +0,0 @@
|
||||
import functools
|
||||
import json
|
||||
import re
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..utils import (
|
||||
OnDemandPagedList,
|
||||
clean_podcast_url,
|
||||
float_or_none,
|
||||
int_or_none,
|
||||
strip_or_none,
|
||||
traverse_obj,
|
||||
try_get,
|
||||
unified_strdate,
|
||||
)
|
||||
|
||||
|
||||
class SpotifyBaseIE(InfoExtractor):
|
||||
_WORKING = False
|
||||
_ACCESS_TOKEN = None
|
||||
_OPERATION_HASHES = {
|
||||
'Episode': '8276d4423d709ae9b68ec1b74cc047ba0f7479059a37820be730f125189ac2bf',
|
||||
'MinimalShow': '13ee079672fad3f858ea45a55eb109553b4fb0969ed793185b2e34cbb6ee7cc0',
|
||||
'ShowEpisodes': 'e0e5ce27bd7748d2c59b4d44ba245a8992a05be75d6fabc3b20753fc8857444d',
|
||||
}
|
||||
_VALID_URL_TEMPL = r'https?://open\.spotify\.com/(?:embed-podcast/|embed/|)%s/(?P<id>[^/?&#]+)'
|
||||
_EMBED_REGEX = [r'<iframe[^>]+src="(?P<url>https?://open\.spotify.com/embed/[^"]+)"']
|
||||
|
||||
def _real_initialize(self):
|
||||
self._ACCESS_TOKEN = self._download_json(
|
||||
'https://open.spotify.com/get_access_token', None)['accessToken']
|
||||
|
||||
def _call_api(self, operation, video_id, variables, **kwargs):
|
||||
return self._download_json(
|
||||
'https://api-partner.spotify.com/pathfinder/v1/query', video_id, query={
|
||||
'operationName': 'query' + operation,
|
||||
'variables': json.dumps(variables),
|
||||
'extensions': json.dumps({
|
||||
'persistedQuery': {
|
||||
'sha256Hash': self._OPERATION_HASHES[operation],
|
||||
},
|
||||
}),
|
||||
}, headers={'authorization': 'Bearer ' + self._ACCESS_TOKEN},
|
||||
**kwargs)['data']
|
||||
|
||||
def _extract_episode(self, episode, series):
|
||||
episode_id = episode['id']
|
||||
title = episode['name'].strip()
|
||||
|
||||
formats = []
|
||||
audio_preview = episode.get('audioPreview') or {}
|
||||
audio_preview_url = audio_preview.get('url')
|
||||
if audio_preview_url:
|
||||
f = {
|
||||
'url': audio_preview_url.replace('://p.scdn.co/mp3-preview/', '://anon-podcast.scdn.co/'),
|
||||
'vcodec': 'none',
|
||||
}
|
||||
audio_preview_format = audio_preview.get('format')
|
||||
if audio_preview_format:
|
||||
f['format_id'] = audio_preview_format
|
||||
mobj = re.match(r'([0-9A-Z]{3})_(?:[A-Z]+_)?(\d+)', audio_preview_format)
|
||||
if mobj:
|
||||
f.update({
|
||||
'abr': int(mobj.group(2)),
|
||||
'ext': mobj.group(1).lower(),
|
||||
})
|
||||
formats.append(f)
|
||||
|
||||
for item in (try_get(episode, lambda x: x['audio']['items']) or []):
|
||||
item_url = item.get('url')
|
||||
if not (item_url and item.get('externallyHosted')):
|
||||
continue
|
||||
formats.append({
|
||||
'url': clean_podcast_url(item_url),
|
||||
'vcodec': 'none',
|
||||
})
|
||||
|
||||
thumbnails = []
|
||||
for source in (try_get(episode, lambda x: x['coverArt']['sources']) or []):
|
||||
source_url = source.get('url')
|
||||
if not source_url:
|
||||
continue
|
||||
thumbnails.append({
|
||||
'url': source_url,
|
||||
'width': int_or_none(source.get('width')),
|
||||
'height': int_or_none(source.get('height')),
|
||||
})
|
||||
|
||||
return {
|
||||
'id': episode_id,
|
||||
'title': title,
|
||||
'formats': formats,
|
||||
'thumbnails': thumbnails,
|
||||
'description': strip_or_none(episode.get('description')),
|
||||
'duration': float_or_none(try_get(
|
||||
episode, lambda x: x['duration']['totalMilliseconds']), 1000),
|
||||
'release_date': unified_strdate(try_get(
|
||||
episode, lambda x: x['releaseDate']['isoString'])),
|
||||
'series': series,
|
||||
}
|
||||
|
||||
|
||||
class SpotifyIE(SpotifyBaseIE):
|
||||
IE_NAME = 'spotify'
|
||||
IE_DESC = 'Spotify episodes'
|
||||
_VALID_URL = SpotifyBaseIE._VALID_URL_TEMPL % 'episode'
|
||||
_TESTS = [{
|
||||
'url': 'https://open.spotify.com/episode/4Z7GAJ50bgctf6uclHlWKo',
|
||||
'md5': '74010a1e3fa4d9e1ab3aa7ad14e42d3b',
|
||||
'info_dict': {
|
||||
'id': '4Z7GAJ50bgctf6uclHlWKo',
|
||||
'ext': 'mp3',
|
||||
'title': 'From the archive: Why time management is ruining our lives',
|
||||
'description': 'md5:b120d9c4ff4135b42aa9b6d9cde86935',
|
||||
'duration': 2083.605,
|
||||
'release_date': '20201217',
|
||||
'series': "The Guardian's Audio Long Reads",
|
||||
},
|
||||
}, {
|
||||
'url': 'https://open.spotify.com/embed/episode/4TvCsKKs2thXmarHigWvXE?si=7eatS8AbQb6RxqO2raIuWA',
|
||||
'only_matching': True,
|
||||
}]
|
||||
|
||||
def _real_extract(self, url):
|
||||
episode_id = self._match_id(url)
|
||||
episode = self._call_api('Episode', episode_id, {
|
||||
'uri': 'spotify:episode:' + episode_id,
|
||||
})['episode']
|
||||
return self._extract_episode(
|
||||
episode, try_get(episode, lambda x: x['podcast']['name']))
|
||||
|
||||
|
||||
class SpotifyShowIE(SpotifyBaseIE):
|
||||
IE_NAME = 'spotify:show'
|
||||
IE_DESC = 'Spotify shows'
|
||||
_VALID_URL = SpotifyBaseIE._VALID_URL_TEMPL % 'show'
|
||||
_TEST = {
|
||||
'url': 'https://open.spotify.com/show/4PM9Ke6l66IRNpottHKV9M',
|
||||
'info_dict': {
|
||||
'id': '4PM9Ke6l66IRNpottHKV9M',
|
||||
'title': 'The Story from the Guardian',
|
||||
'description': 'The Story podcast is dedicated to our finest audio documentaries, investigations and long form stories',
|
||||
},
|
||||
'playlist_mincount': 36,
|
||||
}
|
||||
_PER_PAGE = 100
|
||||
|
||||
def _fetch_page(self, show_id, page=0):
|
||||
return self._call_api('ShowEpisodes', show_id, {
|
||||
'limit': 100,
|
||||
'offset': page * self._PER_PAGE,
|
||||
'uri': f'spotify:show:{show_id}',
|
||||
}, note=f'Downloading page {page + 1} JSON metadata')['podcast']
|
||||
|
||||
def _real_extract(self, url):
|
||||
show_id = self._match_id(url)
|
||||
first_page = self._fetch_page(show_id)
|
||||
|
||||
def _entries(page):
|
||||
podcast = self._fetch_page(show_id, page) if page else first_page
|
||||
yield from map(
|
||||
functools.partial(self._extract_episode, series=podcast.get('name')),
|
||||
traverse_obj(podcast, ('episodes', 'items', ..., 'episode')))
|
||||
|
||||
return self.playlist_result(
|
||||
OnDemandPagedList(_entries, self._PER_PAGE),
|
||||
show_id, first_page.get('name'), first_page.get('description'))
|
@@ -1,244 +1,335 @@
|
||||
import functools
|
||||
import urllib.parse
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..utils import (
|
||||
OnDemandPagedList,
|
||||
determine_ext,
|
||||
UnsupportedError,
|
||||
clean_html,
|
||||
int_or_none,
|
||||
join_nonempty,
|
||||
parse_iso8601,
|
||||
traverse_obj,
|
||||
update_url_query,
|
||||
url_or_none,
|
||||
)
|
||||
from ..utils.traversal import traverse_obj
|
||||
|
||||
|
||||
class TuneInBaseIE(InfoExtractor):
|
||||
_VALID_URL_BASE = r'https?://(?:www\.)?tunein\.com'
|
||||
|
||||
def _extract_metadata(self, webpage, content_id):
|
||||
return self._search_json(r'window.INITIAL_STATE=', webpage, 'hydration', content_id, fatal=False)
|
||||
def _call_api(self, item_id, endpoint=None, note='Downloading JSON metadata', fatal=False, query=None):
|
||||
return self._download_json(
|
||||
join_nonempty('https://api.tunein.com/profiles', item_id, endpoint, delim='/'),
|
||||
item_id, note=note, fatal=fatal, query=query) or {}
|
||||
|
||||
def _extract_formats_and_subtitles(self, content_id):
|
||||
streams = self._download_json(
|
||||
f'https://opml.radiotime.com/Tune.ashx?render=json&formats=mp3,aac,ogg,flash,hls&id={content_id}',
|
||||
content_id)['body']
|
||||
'https://opml.radiotime.com/Tune.ashx', content_id, query={
|
||||
'formats': 'mp3,aac,ogg,flash,hls',
|
||||
'id': content_id,
|
||||
'render': 'json',
|
||||
})
|
||||
|
||||
formats, subtitles = [], {}
|
||||
for stream in streams:
|
||||
for stream in traverse_obj(streams, ('body', lambda _, v: url_or_none(v['url']))):
|
||||
if stream.get('media_type') == 'hls':
|
||||
fmts, subs = self._extract_m3u8_formats_and_subtitles(stream['url'], content_id, fatal=False)
|
||||
formats.extend(fmts)
|
||||
self._merge_subtitles(subs, target=subtitles)
|
||||
elif determine_ext(stream['url']) == 'pls':
|
||||
playlist_content = self._download_webpage(stream['url'], content_id)
|
||||
formats.append({
|
||||
'url': self._search_regex(r'File1=(.*)', playlist_content, 'url', fatal=False),
|
||||
'abr': stream.get('bitrate'),
|
||||
'ext': stream.get('media_type'),
|
||||
})
|
||||
else:
|
||||
formats.append({
|
||||
'url': stream['url'],
|
||||
'abr': stream.get('bitrate'),
|
||||
'ext': stream.get('media_type'),
|
||||
})
|
||||
formats.append(traverse_obj(stream, {
|
||||
'abr': ('bitrate', {int_or_none}),
|
||||
'ext': ('media_type', {str}),
|
||||
'url': ('url', {self._proto_relative_url}),
|
||||
}))
|
||||
|
||||
return formats, subtitles
|
||||
|
||||
|
||||
class TuneInStationIE(TuneInBaseIE):
|
||||
_VALID_URL = TuneInBaseIE._VALID_URL_BASE + r'(?:/radio/[^?#]+-|/embed/player/)(?P<id>s\d+)'
|
||||
_EMBED_REGEX = [r'<iframe[^>]+src=["\'](?P<url>(?:https?://)?tunein\.com/embed/player/s\d+)']
|
||||
|
||||
IE_NAME = 'tunein:station'
|
||||
_VALID_URL = r'https?://tunein\.com/radio/[^/?#]+(?P<id>s\d+)'
|
||||
_TESTS = [{
|
||||
'url': 'https://tunein.com/radio/Jazz24-885-s34682/',
|
||||
'info_dict': {
|
||||
'id': 's34682',
|
||||
'title': str,
|
||||
'description': 'md5:d6d0b89063fd68d529fa7058ee98619b',
|
||||
'thumbnail': r're:https?://cdn-profiles\.tunein\.com/.+',
|
||||
'location': 'Seattle-Tacoma, US',
|
||||
'ext': 'mp3',
|
||||
'title': str,
|
||||
'alt_title': 'World Class Jazz',
|
||||
'channel_follower_count': int,
|
||||
'description': 'md5:d6d0b89063fd68d529fa7058ee98619b',
|
||||
'location': r're:Seattle-Tacoma, (?:US|WA)',
|
||||
'live_status': 'is_live',
|
||||
'thumbnail': r're:https?://.+',
|
||||
},
|
||||
'params': {
|
||||
'skip_download': True,
|
||||
},
|
||||
}, {
|
||||
'url': 'https://tunein.com/embed/player/s6404/',
|
||||
'only_matching': True,
|
||||
'params': {'skip_download': 'Livestream'},
|
||||
}, {
|
||||
'url': 'https://tunein.com/radio/BBC-Radio-1-988-s24939/',
|
||||
'info_dict': {
|
||||
'id': 's24939',
|
||||
'title': str,
|
||||
'description': 'md5:ee2c56794844610d045f8caf5ff34d0c',
|
||||
'thumbnail': r're:https?://cdn-profiles\.tunein\.com/.+',
|
||||
'location': 'London, UK',
|
||||
'ext': 'm4a',
|
||||
'title': str,
|
||||
'alt_title': 'The biggest new pop and all-day vibes',
|
||||
'channel_follower_count': int,
|
||||
'description': 'md5:ee2c56794844610d045f8caf5ff34d0c',
|
||||
'location': 'London, UK',
|
||||
'live_status': 'is_live',
|
||||
'thumbnail': r're:https?://.+',
|
||||
},
|
||||
'params': {
|
||||
'skip_download': True,
|
||||
'params': {'skip_download': 'Livestream'},
|
||||
}]
|
||||
|
||||
def _real_extract(self, url):
|
||||
station_id = self._match_id(url)
|
||||
formats, subtitles = self._extract_formats_and_subtitles(station_id)
|
||||
|
||||
return {
|
||||
'id': station_id,
|
||||
'formats': formats,
|
||||
'subtitles': subtitles,
|
||||
**traverse_obj(self._call_api(station_id), ('Item', {
|
||||
'title': ('Title', {clean_html}),
|
||||
'alt_title': ('Subtitle', {clean_html}, filter),
|
||||
'channel_follower_count': ('Actions', 'Follow', 'FollowerCount', {int_or_none}),
|
||||
'description': ('Description', {clean_html}, filter),
|
||||
'is_live': ('Actions', 'Play', 'IsLive', {bool}),
|
||||
'location': ('Properties', 'Location', 'DisplayName', {str}),
|
||||
'thumbnail': ('Image', {url_or_none}),
|
||||
})),
|
||||
}
|
||||
|
||||
|
||||
class TuneInPodcastIE(TuneInBaseIE):
|
||||
IE_NAME = 'tunein:podcast:program'
|
||||
_PAGE_SIZE = 20
|
||||
_VALID_URL = r'https?://tunein\.com/podcasts(?:/[^/?#]+){1,2}(?P<id>p\d+)'
|
||||
_TESTS = [{
|
||||
'url': 'https://tunein.com/podcasts/Technology-Podcasts/Artificial-Intelligence-p1153019/',
|
||||
'info_dict': {
|
||||
'id': 'p1153019',
|
||||
'title': 'Lex Fridman Podcast',
|
||||
},
|
||||
'playlist_mincount': 200,
|
||||
}, {
|
||||
'url': 'https://tunein.com/podcasts/World-News/BBC-News-p14/',
|
||||
'info_dict': {
|
||||
'id': 'p14',
|
||||
'title': 'BBC News',
|
||||
},
|
||||
'playlist_mincount': 35,
|
||||
}]
|
||||
|
||||
@classmethod
|
||||
def suitable(cls, url):
|
||||
return False if TuneInPodcastEpisodeIE.suitable(url) else super().suitable(url)
|
||||
|
||||
def _fetch_page(self, url, podcast_id, page=0):
|
||||
items = self._call_api(
|
||||
podcast_id, 'contents', f'Downloading page {page + 1}', query={
|
||||
'filter': 't:free',
|
||||
'limit': self._PAGE_SIZE,
|
||||
'offset': page * self._PAGE_SIZE,
|
||||
},
|
||||
)['Items']
|
||||
|
||||
for item in traverse_obj(items, (..., 'GuideId', {str}, filter)):
|
||||
yield self.url_result(update_url_query(url, {'topicId': item[1:]}))
|
||||
|
||||
def _real_extract(self, url):
|
||||
podcast_id = self._match_id(url)
|
||||
|
||||
return self.playlist_result(OnDemandPagedList(
|
||||
functools.partial(self._fetch_page, url, podcast_id), self._PAGE_SIZE),
|
||||
podcast_id, traverse_obj(self._call_api(podcast_id), ('Item', 'Title', {str})))
|
||||
|
||||
|
||||
class TuneInPodcastEpisodeIE(TuneInBaseIE):
|
||||
IE_NAME = 'tunein:podcast'
|
||||
_VALID_URL = r'https?://tunein\.com/podcasts(?:/[^/?#]+){1,2}(?P<series_id>p\d+)/?\?(?:[^#]+&)?(?i:topicid)=(?P<id>\d+)'
|
||||
_TESTS = [{
|
||||
'url': 'https://tunein.com/podcasts/Technology-Podcasts/Artificial-Intelligence-p1153019/?topicId=236404354',
|
||||
'info_dict': {
|
||||
'id': 't236404354',
|
||||
'ext': 'mp3',
|
||||
'title': '#351 – MrBeast: Future of YouTube, Twitter, TikTok, and Instagram',
|
||||
'alt_title': 'Technology Podcasts >',
|
||||
'cast': 'count:1',
|
||||
'description': 'md5:1029895354ef073ff00f20b82eb6eb71',
|
||||
'display_id': '236404354',
|
||||
'duration': 8330,
|
||||
'thumbnail': r're:https?://.+',
|
||||
'timestamp': 1673458571,
|
||||
'upload_date': '20230111',
|
||||
'series': 'Lex Fridman Podcast',
|
||||
'series_id': 'p1153019',
|
||||
},
|
||||
}, {
|
||||
'url': 'https://tunein.com/podcasts/The-BOB--TOM-Show-Free-Podcast-p20069/?topicId=174556405',
|
||||
'info_dict': {
|
||||
'id': 't174556405',
|
||||
'ext': 'mp3',
|
||||
'title': 'B&T Extra: Ohhh Yeah, It\'s Sexy Time',
|
||||
'alt_title': 'Westwood One >',
|
||||
'cast': 'count:2',
|
||||
'description': 'md5:6828234f410ab88c85655495c5fcfa88',
|
||||
'display_id': '174556405',
|
||||
'duration': 1203,
|
||||
'series': 'The BOB & TOM Show Free Podcast',
|
||||
'series_id': 'p20069',
|
||||
'thumbnail': r're:https?://.+',
|
||||
'timestamp': 1661799600,
|
||||
'upload_date': '20220829',
|
||||
},
|
||||
}]
|
||||
|
||||
def _real_extract(self, url):
|
||||
series_id, display_id = self._match_valid_url(url).group('series_id', 'id')
|
||||
episode_id = f't{display_id}'
|
||||
formats, subtitles = self._extract_formats_and_subtitles(episode_id)
|
||||
|
||||
return {
|
||||
'id': episode_id,
|
||||
'display_id': display_id,
|
||||
'formats': formats,
|
||||
'series': traverse_obj(self._call_api(series_id), ('Item', 'Title', {clean_html})),
|
||||
'series_id': series_id,
|
||||
'subtitles': subtitles,
|
||||
**traverse_obj(self._call_api(episode_id), ('Item', {
|
||||
'title': ('Title', {clean_html}),
|
||||
'alt_title': ('Subtitle', {clean_html}, filter),
|
||||
'cast': (
|
||||
'Properties', 'ParentProgram', 'Hosts', {clean_html},
|
||||
{lambda x: x.split(';')}, ..., {str.strip}, filter, all, filter),
|
||||
'description': ('Description', {clean_html}, filter),
|
||||
'duration': ('Actions', 'Play', 'Duration', {int_or_none}),
|
||||
'thumbnail': ('Image', {url_or_none}),
|
||||
'timestamp': ('Actions', 'Play', 'PublishTime', {parse_iso8601}),
|
||||
})),
|
||||
}
|
||||
|
||||
|
||||
class TuneInEmbedIE(TuneInBaseIE):
|
||||
IE_NAME = 'tunein:embed'
|
||||
_VALID_URL = r'https?://tunein\.com/embed/player/(?P<id>[^/?#]+)'
|
||||
_EMBED_REGEX = [r'<iframe\b[^>]+\bsrc=["\'](?P<url>(?:https?:)?//tunein\.com/embed/player/[^/?#"\']+)']
|
||||
_TESTS = [{
|
||||
'url': 'https://tunein.com/embed/player/s6404/',
|
||||
'info_dict': {
|
||||
'id': 's6404',
|
||||
'ext': 'mp3',
|
||||
'title': str,
|
||||
'alt_title': 'South Africa\'s News and Information Leader',
|
||||
'channel_follower_count': int,
|
||||
'live_status': 'is_live',
|
||||
'location': 'Johannesburg, South Africa',
|
||||
'thumbnail': r're:https?://.+',
|
||||
},
|
||||
'params': {'skip_download': 'Livestream'},
|
||||
}, {
|
||||
'url': 'https://tunein.com/embed/player/t236404354/',
|
||||
'info_dict': {
|
||||
'id': 't236404354',
|
||||
'ext': 'mp3',
|
||||
'title': '#351 – MrBeast: Future of YouTube, Twitter, TikTok, and Instagram',
|
||||
'alt_title': 'Technology Podcasts >',
|
||||
'cast': 'count:1',
|
||||
'description': 'md5:1029895354ef073ff00f20b82eb6eb71',
|
||||
'display_id': '236404354',
|
||||
'duration': 8330,
|
||||
'series': 'Lex Fridman Podcast',
|
||||
'series_id': 'p1153019',
|
||||
'thumbnail': r're:https?://.+',
|
||||
'timestamp': 1673458571,
|
||||
'upload_date': '20230111',
|
||||
},
|
||||
}, {
|
||||
'url': 'https://tunein.com/embed/player/p191660/',
|
||||
'info_dict': {
|
||||
'id': 'p191660',
|
||||
'title': 'SBS Tamil',
|
||||
},
|
||||
'playlist_mincount': 195,
|
||||
}]
|
||||
_WEBPAGE_TESTS = [{
|
||||
'url': 'https://www.martiniinthemorning.com/',
|
||||
'info_dict': {
|
||||
'id': 's55412',
|
||||
'ext': 'mp3',
|
||||
'title': 'TuneInStation video #s55412',
|
||||
'title': str,
|
||||
'alt_title': 'Now that\'s music!',
|
||||
'channel_follower_count': int,
|
||||
'description': 'md5:41588a3e2cf34b3eafc6c33522fa611a',
|
||||
'live_status': 'is_live',
|
||||
'location': 'US',
|
||||
'thumbnail': r're:https?://.+',
|
||||
},
|
||||
'expected_warnings': ['unable to extract hydration', 'Extractor failed to obtain "title"'],
|
||||
'params': {'skip_download': 'Livestream'},
|
||||
}]
|
||||
|
||||
def _real_extract(self, url):
|
||||
station_id = self._match_id(url)
|
||||
embed_id = self._match_id(url)
|
||||
kind = {
|
||||
'p': 'program',
|
||||
's': 'station',
|
||||
't': 'topic',
|
||||
}.get(embed_id[:1])
|
||||
|
||||
webpage = self._download_webpage(url, station_id)
|
||||
metadata = self._extract_metadata(webpage, station_id)
|
||||
|
||||
formats, subtitles = self._extract_formats_and_subtitles(station_id)
|
||||
return {
|
||||
'id': station_id,
|
||||
'title': traverse_obj(metadata, ('profiles', station_id, 'title')),
|
||||
'description': traverse_obj(metadata, ('profiles', station_id, 'description')),
|
||||
'thumbnail': traverse_obj(metadata, ('profiles', station_id, 'image')),
|
||||
'timestamp': parse_iso8601(
|
||||
traverse_obj(metadata, ('profiles', station_id, 'actions', 'play', 'publishTime'))),
|
||||
'location': traverse_obj(
|
||||
metadata, ('profiles', station_id, 'metadata', 'properties', 'location', 'displayName'),
|
||||
('profiles', station_id, 'properties', 'location', 'displayName')),
|
||||
'formats': formats,
|
||||
'subtitles': subtitles,
|
||||
'is_live': traverse_obj(metadata, ('profiles', station_id, 'actions', 'play', 'isLive')),
|
||||
}
|
||||
|
||||
|
||||
class TuneInPodcastIE(TuneInBaseIE):
|
||||
_VALID_URL = TuneInBaseIE._VALID_URL_BASE + r'/(?:podcasts/[^?#]+-|embed/player/)(?P<id>p\d+)/?(?:#|$)'
|
||||
_EMBED_REGEX = [r'<iframe[^>]+src=["\'](?P<url>(?:https?://)?tunein\.com/embed/player/p\d+)']
|
||||
|
||||
_TESTS = [{
|
||||
'url': 'https://tunein.com/podcasts/Technology-Podcasts/Artificial-Intelligence-p1153019',
|
||||
'info_dict': {
|
||||
'id': 'p1153019',
|
||||
'title': 'Lex Fridman Podcast',
|
||||
'description': 'md5:bedc4e5f1c94f7dec6e4317b5654b00d',
|
||||
},
|
||||
'playlist_mincount': 200,
|
||||
}, {
|
||||
'url': 'https://tunein.com/embed/player/p191660/',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://tunein.com/podcasts/World-News/BBC-News-p14/',
|
||||
'info_dict': {
|
||||
'id': 'p14',
|
||||
'title': 'BBC News',
|
||||
'description': 'md5:30b9622bcc4bd101d4acd6f38f284aed',
|
||||
},
|
||||
'playlist_mincount': 36,
|
||||
}]
|
||||
|
||||
_PAGE_SIZE = 30
|
||||
|
||||
def _real_extract(self, url):
|
||||
podcast_id = self._match_id(url)
|
||||
|
||||
webpage = self._download_webpage(url, podcast_id, fatal=False)
|
||||
metadata = self._extract_metadata(webpage, podcast_id)
|
||||
|
||||
def page_func(page_num):
|
||||
api_response = self._download_json(
|
||||
f'https://api.tunein.com/profiles/{podcast_id}/contents', podcast_id,
|
||||
note=f'Downloading page {page_num + 1}', query={
|
||||
'filter': 't:free',
|
||||
'offset': page_num * self._PAGE_SIZE,
|
||||
'limit': self._PAGE_SIZE,
|
||||
})
|
||||
|
||||
return [
|
||||
self.url_result(
|
||||
f'https://tunein.com/podcasts/{podcast_id}?topicId={episode["GuideId"][1:]}',
|
||||
TuneInPodcastEpisodeIE, title=episode.get('Title'))
|
||||
for episode in api_response['Items']]
|
||||
|
||||
entries = OnDemandPagedList(page_func, self._PAGE_SIZE)
|
||||
return self.playlist_result(
|
||||
entries, playlist_id=podcast_id, title=traverse_obj(metadata, ('profiles', podcast_id, 'title')),
|
||||
description=traverse_obj(metadata, ('profiles', podcast_id, 'description')))
|
||||
|
||||
|
||||
class TuneInPodcastEpisodeIE(TuneInBaseIE):
|
||||
_VALID_URL = TuneInBaseIE._VALID_URL_BASE + r'/podcasts/(?:[^?&]+-)?(?P<podcast_id>p\d+)/?\?topicId=(?P<id>\w\d+)'
|
||||
|
||||
_TESTS = [{
|
||||
'url': 'https://tunein.com/podcasts/Technology-Podcasts/Artificial-Intelligence-p1153019/?topicId=236404354',
|
||||
'info_dict': {
|
||||
'id': 't236404354',
|
||||
'title': '#351 – MrBeast: Future of YouTube, Twitter, TikTok, and Instagram',
|
||||
'description': 'md5:2784533b98f8ac45c0820b1e4a8d8bb2',
|
||||
'thumbnail': r're:https?://cdn-profiles\.tunein\.com/.+',
|
||||
'timestamp': 1673458571,
|
||||
'upload_date': '20230111',
|
||||
'series_id': 'p1153019',
|
||||
'series': 'Lex Fridman Podcast',
|
||||
'ext': 'mp3',
|
||||
},
|
||||
}]
|
||||
|
||||
def _real_extract(self, url):
|
||||
podcast_id, episode_id = self._match_valid_url(url).group('podcast_id', 'id')
|
||||
episode_id = f't{episode_id}'
|
||||
|
||||
webpage = self._download_webpage(url, episode_id)
|
||||
metadata = self._extract_metadata(webpage, episode_id)
|
||||
|
||||
formats, subtitles = self._extract_formats_and_subtitles(episode_id)
|
||||
return {
|
||||
'id': episode_id,
|
||||
'title': traverse_obj(metadata, ('profiles', episode_id, 'title')),
|
||||
'description': traverse_obj(metadata, ('profiles', episode_id, 'description')),
|
||||
'thumbnail': traverse_obj(metadata, ('profiles', episode_id, 'image')),
|
||||
'timestamp': parse_iso8601(
|
||||
traverse_obj(metadata, ('profiles', episode_id, 'actions', 'play', 'publishTime'))),
|
||||
'series_id': podcast_id,
|
||||
'series': traverse_obj(metadata, ('profiles', podcast_id, 'title')),
|
||||
'formats': formats,
|
||||
'subtitles': subtitles,
|
||||
}
|
||||
return self.url_result(
|
||||
f'https://tunein.com/{kind}/?{kind}id={embed_id[1:]}')
|
||||
|
||||
|
||||
class TuneInShortenerIE(InfoExtractor):
|
||||
_WORKING = False
|
||||
IE_NAME = 'tunein:shortener'
|
||||
IE_DESC = False # Do not list
|
||||
_VALID_URL = r'https?://tun\.in/(?P<id>[A-Za-z0-9]+)'
|
||||
|
||||
_VALID_URL = r'https?://tun\.in/(?P<id>[^/?#]+)'
|
||||
_TESTS = [{
|
||||
# test redirection
|
||||
'url': 'http://tun.in/ser7s',
|
||||
'info_dict': {
|
||||
'id': 's34682',
|
||||
'title': str,
|
||||
'description': 'md5:d6d0b89063fd68d529fa7058ee98619b',
|
||||
'thumbnail': r're:https?://cdn-profiles\.tunein\.com/.+',
|
||||
'location': 'Seattle-Tacoma, US',
|
||||
'ext': 'mp3',
|
||||
'alt_title': 'World Class Jazz',
|
||||
'channel_follower_count': int,
|
||||
'description': 'md5:d6d0b89063fd68d529fa7058ee98619b',
|
||||
'location': r're:Seattle-Tacoma, (?:US|WA)',
|
||||
'live_status': 'is_live',
|
||||
'thumbnail': r're:https?://.+',
|
||||
},
|
||||
'params': {
|
||||
'skip_download': True, # live stream
|
||||
'params': {'skip_download': 'Livestream'},
|
||||
}, {
|
||||
'url': 'http://tun.in/tqeeFw',
|
||||
'info_dict': {
|
||||
'id': 't236404354',
|
||||
'title': str,
|
||||
'ext': 'mp3',
|
||||
'alt_title': 'Technology Podcasts >',
|
||||
'cast': 'count:1',
|
||||
'description': 'md5:1029895354ef073ff00f20b82eb6eb71',
|
||||
'display_id': '236404354',
|
||||
'duration': 8330,
|
||||
'series': 'Lex Fridman Podcast',
|
||||
'series_id': 'p1153019',
|
||||
'thumbnail': r're:https?://.+',
|
||||
'timestamp': 1673458571,
|
||||
'upload_date': '20230111',
|
||||
},
|
||||
'params': {'skip_download': 'Livestream'},
|
||||
}, {
|
||||
'url': 'http://tun.in/pei6i',
|
||||
'info_dict': {
|
||||
'id': 'p14',
|
||||
'title': 'BBC News',
|
||||
},
|
||||
'playlist_mincount': 35,
|
||||
}]
|
||||
|
||||
def _real_extract(self, url):
|
||||
redirect_id = self._match_id(url)
|
||||
# The server doesn't support HEAD requests
|
||||
urlh = self._request_webpage(
|
||||
url, redirect_id, note='Downloading redirect page')
|
||||
|
||||
url = urlh.url
|
||||
url_parsed = urllib.parse.urlparse(url)
|
||||
if url_parsed.port == 443:
|
||||
url = url_parsed._replace(netloc=url_parsed.hostname).url
|
||||
|
||||
self.to_screen(f'Following redirect: {url}')
|
||||
return self.url_result(url)
|
||||
urlh = self._request_webpage(url, redirect_id, 'Downloading redirect page')
|
||||
# Need to strip port from URL
|
||||
parsed = urllib.parse.urlparse(urlh.url)
|
||||
new_url = parsed._replace(netloc=parsed.hostname).geturl()
|
||||
# Prevent infinite loop in case redirect fails
|
||||
if self.suitable(new_url):
|
||||
raise UnsupportedError(new_url)
|
||||
return self.url_result(new_url)
|
||||
|
@@ -30,13 +30,13 @@ class KnownDRMIE(UnsupportedInfoExtractor):
|
||||
r'play\.hbomax\.com',
|
||||
r'channel(?:4|5)\.com',
|
||||
r'peacocktv\.com',
|
||||
r'(?:[\w\.]+\.)?disneyplus\.com',
|
||||
r'open\.spotify\.com/(?:track|playlist|album|artist)',
|
||||
r'(?:[\w.]+\.)?disneyplus\.com',
|
||||
r'open\.spotify\.com',
|
||||
r'tvnz\.co\.nz',
|
||||
r'oneplus\.ch',
|
||||
r'artstation\.com/learning/courses',
|
||||
r'philo\.com',
|
||||
r'(?:[\w\.]+\.)?mech-plus\.com',
|
||||
r'(?:[\w.]+\.)?mech-plus\.com',
|
||||
r'aha\.video',
|
||||
r'mubi\.com',
|
||||
r'vootkids\.com',
|
||||
@@ -57,6 +57,14 @@ class KnownDRMIE(UnsupportedInfoExtractor):
|
||||
r'ctv\.ca',
|
||||
r'noovo\.ca',
|
||||
r'tsn\.ca',
|
||||
r'paramountplus\.com',
|
||||
r'(?:m\.)?(?:sony)?crackle\.com',
|
||||
r'cw(?:tv(?:pr)?|seed)\.com',
|
||||
r'6play\.fr',
|
||||
r'rtlplay\.be',
|
||||
r'play\.rtl\.hr',
|
||||
r'rtlmost\.hu',
|
||||
r'plus\.rtl\.de(?!/podcast/)',
|
||||
)
|
||||
|
||||
_TESTS = [{
|
||||
@@ -78,10 +86,7 @@ class KnownDRMIE(UnsupportedInfoExtractor):
|
||||
'url': r'https://www.disneyplus.com',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://open.spotify.com/artist/',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://open.spotify.com/track/',
|
||||
'url': 'https://open.spotify.com',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
# https://github.com/yt-dlp/yt-dlp/issues/4122
|
||||
@@ -184,6 +189,39 @@ class KnownDRMIE(UnsupportedInfoExtractor):
|
||||
}, {
|
||||
'url': 'https://www.tsn.ca/video/relaxed-oilers-look-to-put-emotional-game-2-loss-in-the-rearview%7E3148747',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://www.paramountplus.com',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://www.crackle.com',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://m.sonycrackle.com',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://www.cwtv.com',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://www.cwseed.com',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://cwtvpr.com',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://www.6play.fr',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://www.rtlplay.be',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://play.rtl.hr',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://www.rtlmost.hu',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://plus.rtl.de/video-tv/',
|
||||
'only_matching': True,
|
||||
}]
|
||||
|
||||
def _real_extract(self, url):
|
||||
@@ -222,6 +260,7 @@ class KnownPiracyIE(UnsupportedInfoExtractor):
|
||||
r'91porn\.com',
|
||||
r'einthusan\.(?:tv|com|ca)',
|
||||
r'yourupload\.com',
|
||||
r'xanimu\.com',
|
||||
)
|
||||
|
||||
_TESTS = [{
|
||||
|
@@ -1,119 +0,0 @@
|
||||
import re
|
||||
|
||||
from .common import InfoExtractor
|
||||
from .rutv import RUTVIE
|
||||
from ..utils import ExtractorError
|
||||
|
||||
|
||||
class VestiIE(InfoExtractor):
|
||||
_WORKING = False
|
||||
IE_DESC = 'Вести.Ru'
|
||||
_VALID_URL = r'https?://(?:.+?\.)?vesti\.ru/(?P<id>.+)'
|
||||
|
||||
_TESTS = [
|
||||
{
|
||||
'url': 'http://www.vesti.ru/videos?vid=575582&cid=1',
|
||||
'info_dict': {
|
||||
'id': '765035',
|
||||
'ext': 'mp4',
|
||||
'title': 'Вести.net: биткоины в России не являются законными',
|
||||
'description': 'md5:d4bb3859dc1177b28a94c5014c35a36b',
|
||||
'duration': 302,
|
||||
},
|
||||
'params': {
|
||||
# m3u8 download
|
||||
'skip_download': True,
|
||||
},
|
||||
},
|
||||
{
|
||||
'url': 'http://www.vesti.ru/doc.html?id=1349233',
|
||||
'info_dict': {
|
||||
'id': '773865',
|
||||
'ext': 'mp4',
|
||||
'title': 'Участники митинга штурмуют Донецкую областную администрацию',
|
||||
'description': 'md5:1a160e98b3195379b4c849f2f4958009',
|
||||
'duration': 210,
|
||||
},
|
||||
'params': {
|
||||
# m3u8 download
|
||||
'skip_download': True,
|
||||
},
|
||||
},
|
||||
{
|
||||
'url': 'http://www.vesti.ru/only_video.html?vid=576180',
|
||||
'info_dict': {
|
||||
'id': '766048',
|
||||
'ext': 'mp4',
|
||||
'title': 'США заморозило, Британию затопило',
|
||||
'description': 'md5:f0ed0695ec05aed27c56a70a58dc4cc1',
|
||||
'duration': 87,
|
||||
},
|
||||
'params': {
|
||||
# m3u8 download
|
||||
'skip_download': True,
|
||||
},
|
||||
},
|
||||
{
|
||||
'url': 'http://hitech.vesti.ru/news/view/id/4000',
|
||||
'info_dict': {
|
||||
'id': '766888',
|
||||
'ext': 'mp4',
|
||||
'title': 'Вести.net: интернет-гиганты начали перетягивание программных "одеял"',
|
||||
'description': 'md5:65ddd47f9830c4f42ed6475f8730c995',
|
||||
'duration': 279,
|
||||
},
|
||||
'params': {
|
||||
# m3u8 download
|
||||
'skip_download': True,
|
||||
},
|
||||
},
|
||||
{
|
||||
'url': 'http://sochi2014.vesti.ru/video/index/video_id/766403',
|
||||
'info_dict': {
|
||||
'id': '766403',
|
||||
'ext': 'mp4',
|
||||
'title': 'XXII зимние Олимпийские игры. Российские хоккеисты стартовали на Олимпиаде с победы',
|
||||
'description': 'md5:55805dfd35763a890ff50fa9e35e31b3',
|
||||
'duration': 271,
|
||||
},
|
||||
'params': {
|
||||
# m3u8 download
|
||||
'skip_download': True,
|
||||
},
|
||||
'skip': 'Blocked outside Russia',
|
||||
},
|
||||
{
|
||||
'url': 'http://sochi2014.vesti.ru/live/play/live_id/301',
|
||||
'info_dict': {
|
||||
'id': '51499',
|
||||
'ext': 'flv',
|
||||
'title': 'Сочи-2014. Биатлон. Индивидуальная гонка. Мужчины ',
|
||||
'description': 'md5:9e0ed5c9d2fa1efbfdfed90c9a6d179c',
|
||||
},
|
||||
'params': {
|
||||
# rtmp download
|
||||
'skip_download': True,
|
||||
},
|
||||
'skip': 'Translation has finished',
|
||||
},
|
||||
]
|
||||
|
||||
def _real_extract(self, url):
|
||||
mobj = self._match_valid_url(url)
|
||||
video_id = mobj.group('id')
|
||||
|
||||
page = self._download_webpage(url, video_id, 'Downloading page')
|
||||
|
||||
mobj = re.search(
|
||||
r'<meta[^>]+?property="og:video"[^>]+?content="http://www\.vesti\.ru/i/flvplayer_videoHost\.swf\?vid=(?P<id>\d+)',
|
||||
page)
|
||||
if mobj:
|
||||
video_id = mobj.group('id')
|
||||
page = self._download_webpage(f'http://www.vesti.ru/only_video.html?vid={video_id}', video_id,
|
||||
'Downloading video page')
|
||||
|
||||
rutv_url = RUTVIE._extract_url(page)
|
||||
if rutv_url:
|
||||
return self.url_result(rutv_url, 'RUTV')
|
||||
|
||||
raise ExtractorError('No video found', expected=True)
|
@@ -1,52 +0,0 @@
|
||||
import re
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..utils import int_or_none
|
||||
|
||||
|
||||
class XanimuIE(InfoExtractor):
|
||||
_VALID_URL = r'https?://(?:www\.)?xanimu\.com/(?P<id>[^/]+)/?'
|
||||
_TESTS = [{
|
||||
'url': 'https://xanimu.com/51944-the-princess-the-frog-hentai/',
|
||||
'md5': '899b88091d753d92dad4cb63bbf357a7',
|
||||
'info_dict': {
|
||||
'id': '51944-the-princess-the-frog-hentai',
|
||||
'ext': 'mp4',
|
||||
'title': 'The Princess + The Frog Hentai',
|
||||
'thumbnail': 'https://xanimu.com/storage/2020/09/the-princess-and-the-frog-hentai.jpg',
|
||||
'description': r're:^Enjoy The Princess \+ The Frog Hentai',
|
||||
'duration': 207.0,
|
||||
'age_limit': 18,
|
||||
},
|
||||
}, {
|
||||
'url': 'https://xanimu.com/huge-expansion/',
|
||||
'only_matching': True,
|
||||
}]
|
||||
|
||||
def _real_extract(self, url):
|
||||
video_id = self._match_id(url)
|
||||
webpage = self._download_webpage(url, video_id)
|
||||
|
||||
formats = []
|
||||
for format_id in ['videoHigh', 'videoLow']:
|
||||
format_url = self._search_json(
|
||||
rf'var\s+{re.escape(format_id)}\s*=', webpage, format_id,
|
||||
video_id, default=None, contains_pattern=r'[\'"]([^\'"]+)[\'"]')
|
||||
if format_url:
|
||||
formats.append({
|
||||
'url': format_url,
|
||||
'format_id': format_id,
|
||||
'quality': -2 if format_id.endswith('Low') else None,
|
||||
})
|
||||
|
||||
return {
|
||||
'id': video_id,
|
||||
'formats': formats,
|
||||
'title': self._search_regex(r'[\'"]headline[\'"]:\s*[\'"]([^"]+)[\'"]', webpage,
|
||||
'title', default=None) or self._html_extract_title(webpage),
|
||||
'thumbnail': self._html_search_meta('thumbnailUrl', webpage, default=None),
|
||||
'description': self._html_search_meta('description', webpage, default=None),
|
||||
'duration': int_or_none(self._search_regex(r'duration:\s*[\'"]([^\'"]+?)[\'"]',
|
||||
webpage, 'duration', fatal=False)),
|
||||
'age_limit': 18,
|
||||
}
|
Reference in New Issue
Block a user