Update On Sun Nov 24 19:33:47 CET 2024

This commit is contained in:
github-action[bot]
2024-11-24 19:33:47 +01:00
parent 8002e47064
commit d1d2d4712c
64 changed files with 825 additions and 652 deletions
+1
View File
@@ -834,3 +834,4 @@ Update On Wed Nov 20 19:36:13 CET 2024
Update On Thu Nov 21 19:38:10 CET 2024 Update On Thu Nov 21 19:38:10 CET 2024
Update On Fri Nov 22 19:38:14 CET 2024 Update On Fri Nov 22 19:38:14 CET 2024
Update On Sat Nov 23 19:33:03 CET 2024 Update On Sat Nov 23 19:33:03 CET 2024
Update On Sun Nov 24 19:33:36 CET 2024
+8 -5
View File
@@ -44,7 +44,7 @@ static partial class BBDownMuxer
return string.IsNullOrEmpty(str) ? str : str.Replace("\"", "'").Replace("\\", "\\\\"); return string.IsNullOrEmpty(str) ? str : str.Replace("\"", "'").Replace("\\", "\\\\");
} }
private static int MuxByMp4box(string videoPath, string audioPath, string outPath, string desc, string title, string author, string episodeId, string pic, string lang, List<Subtitle>? subs, bool audioOnly, bool videoOnly, List<ViewPoint>? points) private static int MuxByMp4box(string url, string videoPath, string audioPath, string outPath, string desc, string title, string author, string episodeId, string pic, string lang, List<Subtitle>? subs, bool audioOnly, bool videoOnly, List<ViewPoint>? points)
{ {
StringBuilder inputArg = new(); StringBuilder inputArg = new();
StringBuilder metaArg = new(); StringBuilder metaArg = new();
@@ -73,7 +73,8 @@ static partial class BBDownMuxer
metaArg.Append($":album=\"{title}\":title=\"{episodeId}\""); metaArg.Append($":album=\"{title}\":title=\"{episodeId}\"");
else else
metaArg.Append($":title=\"{title}\""); metaArg.Append($":title=\"{title}\"");
metaArg.Append($":comment=\"{desc}\""); metaArg.Append($":sdesc=\"{desc}\"");
metaArg.Append($":comment=\"{url}\"");
metaArg.Append($":artist=\"{author}\""); metaArg.Append($":artist=\"{author}\"");
if (subs != null) if (subs != null)
@@ -90,12 +91,12 @@ static partial class BBDownMuxer
} }
//----分析完毕 //----分析完毕
var arguments = (Config.DEBUG_LOG ? " -verbose " : "") + inputArg.ToString() + (metaArg.ToString() == "" ? "" : " -itags tool=" + metaArg.ToString()) + $" -new -- \"{outPath}\""; var arguments = (Config.DEBUG_LOG ? " -v " : "") + inputArg + (metaArg.ToString() == "" ? "" : " -itags tool=" + metaArg) + $" -new -- \"{outPath}\"";
LogDebug("mp4box命令: {0}", arguments); LogDebug("mp4box命令: {0}", arguments);
return RunExe(MP4BOX, arguments, MP4BOX != "mp4box"); return RunExe(MP4BOX, arguments, MP4BOX != "mp4box");
} }
public static int MuxAV(bool useMp4box, string videoPath, string audioPath, List<AudioMaterial> audioMaterial, string outPath, string desc = "", string title = "", string author = "", string episodeId = "", string pic = "", string lang = "", List<Subtitle>? subs = null, bool audioOnly = false, bool videoOnly = false, List<ViewPoint>? points = null, long pubTime = 0, bool simplyMux = false) public static int MuxAV(bool useMp4box, string bvid, string videoPath, string audioPath, List<AudioMaterial> audioMaterial, string outPath, string desc = "", string title = "", string author = "", string episodeId = "", string pic = "", string lang = "", List<Subtitle>? subs = null, bool audioOnly = false, bool videoOnly = false, List<ViewPoint>? points = null, long pubTime = 0, bool simplyMux = false)
{ {
if (audioOnly && audioPath != "") if (audioOnly && audioPath != "")
videoPath = ""; videoPath = "";
@@ -104,10 +105,11 @@ static partial class BBDownMuxer
desc = EscapeString(desc); desc = EscapeString(desc);
title = EscapeString(title); title = EscapeString(title);
episodeId = EscapeString(episodeId); episodeId = EscapeString(episodeId);
var url = $"https://www.bilibili.com/video/{bvid}/";
if (useMp4box) if (useMp4box)
{ {
return MuxByMp4box(videoPath, audioPath, outPath, desc, title, author, episodeId, pic, lang, subs, audioOnly, videoOnly, points); return MuxByMp4box(url, videoPath, audioPath, outPath, desc, title, author, episodeId, pic, lang, subs, audioOnly, videoOnly, points);
} }
if (outPath.Contains('/') && ! Directory.Exists(Path.GetDirectoryName(outPath))) if (outPath.Contains('/') && ! Directory.Exists(Path.GetDirectoryName(outPath)))
@@ -179,6 +181,7 @@ static partial class BBDownMuxer
argsBuilder.Append(metaArg); argsBuilder.Append(metaArg);
if (!simplyMux) { if (!simplyMux) {
argsBuilder.Append($"-metadata title=\"{(episodeId == "" ? title : episodeId)}\" "); argsBuilder.Append($"-metadata title=\"{(episodeId == "" ? title : episodeId)}\" ");
argsBuilder.Append($"-metadata comment=\"{url}\" ");
if (lang != "") argsBuilder.Append($"-metadata:s:a:0 language={lang} "); if (lang != "") argsBuilder.Append($"-metadata:s:a:0 language={lang} ");
if (!string.IsNullOrWhiteSpace(desc)) argsBuilder.Append($"-metadata description=\"{desc}\" "); if (!string.IsNullOrWhiteSpace(desc)) argsBuilder.Append($"-metadata description=\"{desc}\" ");
if (!string.IsNullOrEmpty(author)) argsBuilder.Append($"-metadata artist=\"{author}\" "); if (!string.IsNullOrEmpty(author)) argsBuilder.Append($"-metadata artist=\"{author}\" ");
+8 -3
View File
@@ -244,7 +244,7 @@ partial class Program
Log("获取aid..."); Log("获取aid...");
aidOri = await GetAvIdAsync(input); aidOri = await GetAvIdAsync(input);
Log("获取aid结束: " + aidOri); Log($"获取aid结束: {aidOri}");
if (string.IsNullOrEmpty(aidOri)) if (string.IsNullOrEmpty(aidOri))
{ {
@@ -287,6 +287,11 @@ partial class Program
{ {
Log("发布时间: " + FormatTimeStamp(pubTime, "yyyy-MM-dd HH:mm:ss zzz")); Log("发布时间: " + FormatTimeStamp(pubTime, "yyyy-MM-dd HH:mm:ss zzz"));
} }
var bvid = vInfo.PagesInfo.FirstOrDefault()?.bvid;
if (!string.IsNullOrEmpty(bvid))
{
Log($"视频URL: https://www.bilibili.com/video/{bvid}/");
}
var mid = vInfo.PagesInfo.FirstOrDefault(p => !string.IsNullOrEmpty(p.ownerMid))?.ownerMid; var mid = vInfo.PagesInfo.FirstOrDefault(p => !string.IsNullOrEmpty(p.ownerMid))?.ownerMid;
if (!string.IsNullOrEmpty(mid)) if (!string.IsNullOrEmpty(mid))
{ {
@@ -664,7 +669,7 @@ partial class Program
Log($"开始合并音视频{(subtitleInfo.Any() ? "" : "")}..."); Log($"开始合并音视频{(subtitleInfo.Any() ? "" : "")}...");
if (myOption.AudioOnly) if (myOption.AudioOnly)
savePath = savePath[..^4] + ".m4a"; savePath = savePath[..^4] + ".m4a";
int code = BBDownMuxer.MuxAV(myOption.UseMP4box, videoPath, audioPath, audioMaterial, savePath, int code = BBDownMuxer.MuxAV(myOption.UseMP4box, p.bvid, videoPath, audioPath, audioMaterial, savePath,
desc, desc,
title, title,
p.ownerName ?? "", p.ownerName ?? "",
@@ -753,7 +758,7 @@ partial class Program
Log($"开始混流视频{(subtitleInfo.Any() ? "" : "")}..."); Log($"开始混流视频{(subtitleInfo.Any() ? "" : "")}...");
if (myOption.AudioOnly) if (myOption.AudioOnly)
savePath = savePath[..^4] + ".m4a"; savePath = savePath[..^4] + ".m4a";
int code = BBDownMuxer.MuxAV(false, videoPath, "", audioMaterial, savePath, int code = BBDownMuxer.MuxAV(false, p.bvid, videoPath, "", audioMaterial, savePath,
desc, desc,
title, title,
p.ownerName ?? "", p.ownerName ?? "",
Binary file not shown.
Binary file not shown.
+207 -208
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -9,7 +9,7 @@ license = "GPL-3.0"
authors = ["zzzgydi", "keiko233"] authors = ["zzzgydi", "keiko233"]
[workspace.dependencies] [workspace.dependencies]
thiserror = "1" thiserror = "2"
tracing = "0.1" tracing = "0.1"
boa_engine = { version = "0.19.1" } boa_engine = { version = "0.19.1" }
+2 -2
View File
@@ -109,12 +109,12 @@ atomic_enum = "0.3.0"
enumflags2 = "0.7" enumflags2 = "0.7"
# System Utilities # System Utilities
auto-launch = { git = "https://github.com/zzzgydi/auto-launch.git", version = "0.5" } auto-launch = { git = "https://github.com/libnyanpasu/auto-launch.git", version = "0.5" }
delay_timer = { version = "0.11", git = "https://github.com/libnyanpasu/delay-timer.git" } # Task scheduler with timer delay_timer = { version = "0.11", git = "https://github.com/libnyanpasu/delay-timer.git" } # Task scheduler with timer
dunce = "1.0.4" # for cross platform path normalization dunce = "1.0.4" # for cross platform path normalization
runas = { git = "https://github.com/libnyanpasu/rust-runas.git" } runas = { git = "https://github.com/libnyanpasu/rust-runas.git" }
single-instance = "0.3.3" single-instance = "0.3.3"
which = "6" which = "7"
dirs = "5.0.1" dirs = "5.0.1"
open = "5.0.1" open = "5.0.1"
sysinfo = "0.32" sysinfo = "0.32"
@@ -84,7 +84,7 @@
} }
}, },
"permissions": { "permissions": {
"description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ```", "description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ] ```",
"type": "array", "type": "array",
"items": { "items": {
"$ref": "#/definitions/PermissionEntry" "$ref": "#/definitions/PermissionEntry"
@@ -84,7 +84,7 @@
} }
}, },
"permissions": { "permissions": {
"description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ```", "description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ] ```",
"type": "array", "type": "array",
"items": { "items": {
"$ref": "#/definitions/PermissionEntry" "$ref": "#/definitions/PermissionEntry"
@@ -36,6 +36,8 @@ enum Commands {
#[arg(raw = true)] #[arg(raw = true)]
args: Vec<String>, args: Vec<String>,
}, },
/// Show a panic dialog while the application is enter panic handler.
PanicDialog { message: String },
} }
struct DelayedExitGuard; struct DelayedExitGuard;
@@ -87,6 +89,9 @@ pub fn parse() -> anyhow::Result<()> {
let envs = crate::utils::collect::collect_envs().unwrap(); let envs = crate::utils::collect::collect_envs().unwrap();
println!("{:#?}", envs); println!("{:#?}", envs);
} }
Commands::PanicDialog { message } => {
crate::utils::dialog::panic_dialog(message);
}
} }
drop(guard); drop(guard);
std::process::exit(0); std::process::exit(0);
+48 -44
View File
@@ -108,7 +108,6 @@ pub fn run() -> std::io::Result<()> {
crate::log_err!(init::init_config()); crate::log_err!(init::init_config());
// Panic Hook to show a panic dialog and save logs // Panic Hook to show a panic dialog and save logs
let default_panic = std::panic::take_hook();
std::panic::set_hook(Box::new(move |panic_info| { std::panic::set_hook(Box::new(move |panic_info| {
use std::backtrace::{Backtrace, BacktraceStatus}; use std::backtrace::{Backtrace, BacktraceStatus};
let payload = panic_info.payload(); let payload = panic_info.payload();
@@ -124,7 +123,7 @@ pub fn run() -> std::io::Result<()> {
let location = panic_info.location().map(|l| l.to_string()); let location = panic_info.location().map(|l| l.to_string());
let (backtrace, note) = { let (backtrace, note) = {
let backtrace = Backtrace::capture(); let backtrace = Backtrace::force_capture();
let note = (backtrace.status() == BacktraceStatus::Disabled) let note = (backtrace.status() == BacktraceStatus::Disabled)
.then_some("run with RUST_BACKTRACE=1 environment variable to display a backtrace"); .then_some("run with RUST_BACKTRACE=1 environment variable to display a backtrace");
(Some(backtrace), note) (Some(backtrace), note)
@@ -145,20 +144,29 @@ pub fn run() -> std::io::Result<()> {
return; return;
} }
utils::dialog::panic_dialog(&format!( // FIXME: maybe move this logic to a util function?
"payload: {:#?}\nlocation: {:?}\nbacktrace: {:#?}\n\nnote: {:?}", let msg = format!(
payload, location, backtrace, note "Oops, we encountered some issues and program will exit immediately.\n\npayload: {:#?}\nlocation: {:?}\nbacktrace: {:#?}\n\n",
)); payload, location, backtrace,
);
let child = std::process::Command::new(tauri::utils::platform::current_exe().unwrap())
.arg("panic-dialog")
.arg(msg.as_str())
.spawn();
// fallback to show a dialog directly
if child.is_err() {
utils::dialog::panic_dialog(msg.as_str());
}
// cleanup the core manager match Handle::global().app_handle.lock().as_ref() {
let task = std::thread::spawn(move || { Some(app_handle) => {
nyanpasu_utils::runtime::block_on(async { app_handle.exit(1);
let _ = crate::core::CoreManager::global().stop_core().await; }
}); None => {
}); log::error!("app handle is not initialized");
let _ = task.join(); std::process::exit(1);
default_panic(panic_info); }
std::process::exit(1); // exit if default panic handler doesn't exit }
})); }));
let verge = { Config::verge().latest().language.clone().unwrap() }; let verge = { Config::verge().latest().language.clone().unwrap() };
@@ -322,39 +330,35 @@ pub fn run() -> std::io::Result<()> {
.build(tauri::generate_context!()) .build(tauri::generate_context!())
.expect("error while running tauri application"); .expect("error while running tauri application");
app.run(|app_handle, e| match e { app.run(|app_handle, e| match e {
tauri::RunEvent::ExitRequested { api, .. } => { tauri::RunEvent::ExitRequested { api, code, .. } if code.is_none() => {
api.prevent_exit(); api.prevent_exit();
} }
tauri::RunEvent::Exit => { tauri::RunEvent::ExitRequested { .. } => {
resolve::resolve_reset(); utils::help::cleanup_processes(app_handle);
} }
tauri::RunEvent::WindowEvent { label, event, .. } => { tauri::RunEvent::WindowEvent { label, event, .. } if label == "main" => match event {
if label == "main" { tauri::WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
match event { core::tray::on_scale_factor_changed(scale_factor);
tauri::WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
core::tray::on_scale_factor_changed(scale_factor);
}
tauri::WindowEvent::CloseRequested { .. } => {
log::debug!(target: "app", "window close requested");
let _ = resolve::save_window_state(app_handle, true);
#[cfg(target_os = "macos")]
log_err!(app_handle.run_on_main_thread(|| {
crate::utils::dock::macos::hide_dock_icon();
}));
}
tauri::WindowEvent::Destroyed => {
log::debug!(target: "app", "window destroyed");
reset_window_open_counter();
}
tauri::WindowEvent::Moved(_) | tauri::WindowEvent::Resized(_) => {
log::debug!(target: "app", "window moved or resized");
std::thread::sleep(std::time::Duration::from_nanos(1));
let _ = resolve::save_window_state(app_handle, false);
}
_ => {}
}
} }
} tauri::WindowEvent::CloseRequested { .. } => {
log::debug!(target: "app", "window close requested");
let _ = resolve::save_window_state(app_handle, true);
#[cfg(target_os = "macos")]
log_err!(app_handle.run_on_main_thread(|| {
crate::utils::dock::macos::hide_dock_icon();
}));
}
tauri::WindowEvent::Destroyed => {
log::debug!(target: "app", "window destroyed");
reset_window_open_counter();
}
tauri::WindowEvent::Moved(_) | tauri::WindowEvent::Resized(_) => {
log::debug!(target: "app", "window moved or resized");
std::thread::sleep(std::time::Duration::from_nanos(1));
let _ = resolve::save_window_state(app_handle, false);
}
_ => {}
},
_ => {} _ => {}
}); });
@@ -247,9 +247,7 @@ pub fn cleanup_processes(app_handle: &AppHandle) {
#[instrument(skip(app_handle))] #[instrument(skip(app_handle))]
pub fn quit_application(app_handle: &AppHandle) { pub fn quit_application(app_handle: &AppHandle) {
cleanup_processes(app_handle);
app_handle.exit(0); app_handle.exit(0);
std::process::exit(0);
} }
#[instrument(skip(app_handle))] #[instrument(skip(app_handle))]
@@ -10,8 +10,8 @@
"serve": "vite preview" "serve": "vite preview"
}, },
"dependencies": { "dependencies": {
"@dnd-kit/core": "6.1.0", "@dnd-kit/core": "6.2.0",
"@dnd-kit/sortable": "8.0.0", "@dnd-kit/sortable": "9.0.0",
"@dnd-kit/utilities": "3.2.2", "@dnd-kit/utilities": "3.2.2",
"@emotion/styled": "11.13.5", "@emotion/styled": "11.13.5",
"@juggle/resize-observer": "3.4.0", "@juggle/resize-observer": "3.4.0",
@@ -29,7 +29,7 @@
"country-code-emoji": "2.3.0", "country-code-emoji": "2.3.0",
"dayjs": "1.11.13", "dayjs": "1.11.13",
"framer-motion": "12.0.0-alpha.2", "framer-motion": "12.0.0-alpha.2",
"i18next": "23.16.8", "i18next": "24.0.0",
"jotai": "2.10.3", "jotai": "2.10.3",
"json-schema": "0.4.0", "json-schema": "0.4.0",
"material-react-table": "3.0.1", "material-react-table": "3.0.1",
+2 -2
View File
@@ -5,7 +5,7 @@
"mihomo_alpha": "alpha-eb985b0", "mihomo_alpha": "alpha-eb985b0",
"clash_rs": "v0.7.1", "clash_rs": "v0.7.1",
"clash_premium": "2023-09-05-gdcc8d87", "clash_premium": "2023-09-05-gdcc8d87",
"clash_rs_alpha": "0.7.1-alpha+sha.92b5bfc" "clash_rs_alpha": "0.7.1-alpha+sha.a4b59dc"
}, },
"arch_template": { "arch_template": {
"mihomo": { "mihomo": {
@@ -69,5 +69,5 @@
"linux-armv7hf": "clash-armv7-unknown-linux-gnueabihf" "linux-armv7hf": "clash-armv7-unknown-linux-gnueabihf"
} }
}, },
"updated_at": "2024-11-21T22:20:51.459Z" "updated_at": "2024-11-23T22:20:39.660Z"
} }
+32 -25
View File
@@ -184,11 +184,11 @@ importers:
frontend/nyanpasu: frontend/nyanpasu:
dependencies: dependencies:
'@dnd-kit/core': '@dnd-kit/core':
specifier: 6.1.0 specifier: 6.2.0
version: 6.1.0(react-dom@19.0.0-rc.1(react@19.0.0-rc.1))(react@19.0.0-rc.1) version: 6.2.0(react-dom@19.0.0-rc.1(react@19.0.0-rc.1))(react@19.0.0-rc.1)
'@dnd-kit/sortable': '@dnd-kit/sortable':
specifier: 8.0.0 specifier: 9.0.0
version: 8.0.0(@dnd-kit/core@6.1.0(react-dom@19.0.0-rc.1(react@19.0.0-rc.1))(react@19.0.0-rc.1))(react@19.0.0-rc.1) version: 9.0.0(@dnd-kit/core@6.2.0(react-dom@19.0.0-rc.1(react@19.0.0-rc.1))(react@19.0.0-rc.1))(react@19.0.0-rc.1)
'@dnd-kit/utilities': '@dnd-kit/utilities':
specifier: 3.2.2 specifier: 3.2.2
version: 3.2.2(react@19.0.0-rc.1) version: 3.2.2(react@19.0.0-rc.1)
@@ -241,8 +241,8 @@ importers:
specifier: 12.0.0-alpha.2 specifier: 12.0.0-alpha.2
version: 12.0.0-alpha.2(@emotion/is-prop-valid@1.3.0)(react-dom@19.0.0-rc.1(react@19.0.0-rc.1))(react@19.0.0-rc.1) version: 12.0.0-alpha.2(@emotion/is-prop-valid@1.3.0)(react-dom@19.0.0-rc.1(react@19.0.0-rc.1))(react@19.0.0-rc.1)
i18next: i18next:
specifier: 23.16.8 specifier: 24.0.0
version: 23.16.8 version: 24.0.0(typescript@5.7.2)
jotai: jotai:
specifier: 2.10.3 specifier: 2.10.3
version: 2.10.3(react@19.0.0-rc.1)(types-react@19.0.0-rc.1) version: 2.10.3(react@19.0.0-rc.1)(types-react@19.0.0-rc.1)
@@ -275,7 +275,7 @@ importers:
version: 7.4.0(c6eqiv3v4ro6nnqx6e4soqhoku) version: 7.4.0(c6eqiv3v4ro6nnqx6e4soqhoku)
react-i18next: react-i18next:
specifier: 15.1.1 specifier: 15.1.1
version: 15.1.1(i18next@23.16.8)(react-dom@19.0.0-rc.1(react@19.0.0-rc.1))(react@19.0.0-rc.1) version: 15.1.1(i18next@24.0.0(typescript@5.7.2))(react-dom@19.0.0-rc.1(react@19.0.0-rc.1))(react@19.0.0-rc.1)
react-markdown: react-markdown:
specifier: 9.0.1 specifier: 9.0.1
version: 9.0.1(react@19.0.0-rc.1)(types-react@19.0.0-rc.1) version: 9.0.1(react@19.0.0-rc.1)(types-react@19.0.0-rc.1)
@@ -468,7 +468,7 @@ importers:
version: 4.1.2(react@19.0.0-rc.1) version: 4.1.2(react@19.0.0-rc.1)
react-i18next: react-i18next:
specifier: 15.1.1 specifier: 15.1.1
version: 15.1.1(i18next@23.16.8)(react-dom@19.0.0-rc.1(react@19.0.0-rc.1))(react@19.0.0-rc.1) version: 15.1.1(i18next@24.0.0(typescript@5.7.2))(react-dom@19.0.0-rc.1(react@19.0.0-rc.1))(react@19.0.0-rc.1)
react-use: react-use:
specifier: 17.5.1 specifier: 17.5.1
version: 17.5.1(react-dom@19.0.0-rc.1(react@19.0.0-rc.1))(react@19.0.0-rc.1) version: 17.5.1(react-dom@19.0.0-rc.1(react@19.0.0-rc.1))(react@19.0.0-rc.1)
@@ -1294,21 +1294,21 @@ packages:
resolution: {integrity: sha512-WyOx8cJQ+FQus4Mm4uPIZA64gbk3Wxh0so5Lcii0aJifqwoVOlfFtorjLE0Hen4OYyHZMXDWqMmaQemBhgxFRQ==} resolution: {integrity: sha512-WyOx8cJQ+FQus4Mm4uPIZA64gbk3Wxh0so5Lcii0aJifqwoVOlfFtorjLE0Hen4OYyHZMXDWqMmaQemBhgxFRQ==}
engines: {node: '>=14'} engines: {node: '>=14'}
'@dnd-kit/accessibility@3.1.0': '@dnd-kit/accessibility@3.1.1':
resolution: {integrity: sha512-ea7IkhKvlJUv9iSHJOnxinBcoOI3ppGnnL+VDJ75O45Nss6HtZd8IdN8touXPDtASfeI2T2LImb8VOZcL47wjQ==} resolution: {integrity: sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==}
peerDependencies: peerDependencies:
react: '>=16.8.0' react: '>=16.8.0'
'@dnd-kit/core@6.1.0': '@dnd-kit/core@6.2.0':
resolution: {integrity: sha512-J3cQBClB4TVxwGo3KEjssGEXNJqGVWx17aRTZ1ob0FliR5IjYgTxl5YJbKTzA6IzrtelotH19v6y7uoIRUZPSg==} resolution: {integrity: sha512-KVK/CJmaYGTxTPU6P0+Oy4itgffTUa80B8317sXzfOr1qUzSL29jE7Th11llXiu2haB7B9Glpzo2CDElin+geQ==}
peerDependencies: peerDependencies:
react: '>=16.8.0' react: '>=16.8.0'
react-dom: '>=16.8.0' react-dom: '>=16.8.0'
'@dnd-kit/sortable@8.0.0': '@dnd-kit/sortable@9.0.0':
resolution: {integrity: sha512-U3jk5ebVXe1Lr7c2wU7SBZjcWdQP+j7peHJfCspnA81enlu88Mgd7CC8Q+pub9ubP7eKVETzJW+IBAhsqbSu/g==} resolution: {integrity: sha512-3/9r8Mmba0nKTbo8kPnVSFZKf/VSy94nXZ3aUwzPEh78j/LooQ/EFKRZENak4PHKBkN53mgTF/z+Sd8H+FcAnQ==}
peerDependencies: peerDependencies:
'@dnd-kit/core': ^6.1.0 '@dnd-kit/core': ^6.2.0
react: '>=16.8.0' react: '>=16.8.0'
'@dnd-kit/utilities@3.2.2': '@dnd-kit/utilities@3.2.2':
@@ -4849,8 +4849,13 @@ packages:
hyphenate-style-name@1.1.0: hyphenate-style-name@1.1.0:
resolution: {integrity: sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==} resolution: {integrity: sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==}
i18next@23.16.8: i18next@24.0.0:
resolution: {integrity: sha512-06r/TitrM88Mg5FdUXAKL96dJMzgqLE5dv3ryBAra4KCwD9mJ4ndOTS95ZuymIGoE+2hzfdaMak2X11/es7ZWg==} resolution: {integrity: sha512-ORGCwMrXxpmB/AljFbGEe0UK/9Pz6umb9aZgLZ9qJGE+kjKhlnLj423WX2mt+N0MlEJ78pQXFMBmeMzrkLxriQ==}
peerDependencies:
typescript: ^5
peerDependenciesMeta:
typescript:
optional: true
iconv-lite@0.6.3: iconv-lite@0.6.3:
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
@@ -8777,22 +8782,22 @@ snapshots:
'@ctrl/tinycolor@4.1.0': {} '@ctrl/tinycolor@4.1.0': {}
'@dnd-kit/accessibility@3.1.0(react@19.0.0-rc.1)': '@dnd-kit/accessibility@3.1.1(react@19.0.0-rc.1)':
dependencies: dependencies:
react: 19.0.0-rc.1 react: 19.0.0-rc.1
tslib: 2.7.0 tslib: 2.7.0
'@dnd-kit/core@6.1.0(react-dom@19.0.0-rc.1(react@19.0.0-rc.1))(react@19.0.0-rc.1)': '@dnd-kit/core@6.2.0(react-dom@19.0.0-rc.1(react@19.0.0-rc.1))(react@19.0.0-rc.1)':
dependencies: dependencies:
'@dnd-kit/accessibility': 3.1.0(react@19.0.0-rc.1) '@dnd-kit/accessibility': 3.1.1(react@19.0.0-rc.1)
'@dnd-kit/utilities': 3.2.2(react@19.0.0-rc.1) '@dnd-kit/utilities': 3.2.2(react@19.0.0-rc.1)
react: 19.0.0-rc.1 react: 19.0.0-rc.1
react-dom: 19.0.0-rc.1(react@19.0.0-rc.1) react-dom: 19.0.0-rc.1(react@19.0.0-rc.1)
tslib: 2.7.0 tslib: 2.7.0
'@dnd-kit/sortable@8.0.0(@dnd-kit/core@6.1.0(react-dom@19.0.0-rc.1(react@19.0.0-rc.1))(react@19.0.0-rc.1))(react@19.0.0-rc.1)': '@dnd-kit/sortable@9.0.0(@dnd-kit/core@6.2.0(react-dom@19.0.0-rc.1(react@19.0.0-rc.1))(react@19.0.0-rc.1))(react@19.0.0-rc.1)':
dependencies: dependencies:
'@dnd-kit/core': 6.1.0(react-dom@19.0.0-rc.1(react@19.0.0-rc.1))(react@19.0.0-rc.1) '@dnd-kit/core': 6.2.0(react-dom@19.0.0-rc.1(react@19.0.0-rc.1))(react@19.0.0-rc.1)
'@dnd-kit/utilities': 3.2.2(react@19.0.0-rc.1) '@dnd-kit/utilities': 3.2.2(react@19.0.0-rc.1)
react: 19.0.0-rc.1 react: 19.0.0-rc.1
tslib: 2.7.0 tslib: 2.7.0
@@ -12589,9 +12594,11 @@ snapshots:
hyphenate-style-name@1.1.0: {} hyphenate-style-name@1.1.0: {}
i18next@23.16.8: i18next@24.0.0(typescript@5.7.2):
dependencies: dependencies:
'@babel/runtime': 7.26.0 '@babel/runtime': 7.26.0
optionalDependencies:
typescript: 5.7.2
iconv-lite@0.6.3: iconv-lite@0.6.3:
dependencies: dependencies:
@@ -14113,11 +14120,11 @@ snapshots:
dependencies: dependencies:
react: 19.0.0-rc.1 react: 19.0.0-rc.1
react-i18next@15.1.1(i18next@23.16.8)(react-dom@19.0.0-rc.1(react@19.0.0-rc.1))(react@19.0.0-rc.1): react-i18next@15.1.1(i18next@24.0.0(typescript@5.7.2))(react-dom@19.0.0-rc.1(react@19.0.0-rc.1))(react@19.0.0-rc.1):
dependencies: dependencies:
'@babel/runtime': 7.26.0 '@babel/runtime': 7.26.0
html-parse-stringify: 3.0.1 html-parse-stringify: 3.0.1
i18next: 23.16.8 i18next: 24.0.0(typescript@5.7.2)
react: 19.0.0-rc.1 react: 19.0.0-rc.1
optionalDependencies: optionalDependencies:
react-dom: 19.0.0-rc.1(react@19.0.0-rc.1) react-dom: 19.0.0-rc.1(react@19.0.0-rc.1)
+3 -1
View File
@@ -33,7 +33,9 @@ $(call KernelPackage/r8125)
endef endef
ifeq ($(BUILD_VARIANT),rss) ifeq ($(BUILD_VARIANT),rss)
PKG_MAKE_FLAGS += ENABLE_RSS_SUPPORT=y PKG_MAKE_FLAGS += \
ENABLE_RSS_SUPPORT=y \
ENABLE_MULTIPLE_TX_QUEUE=y
endif endif
PKG_MAKE_FLAGS += CONFIG_ASPM=n PKG_MAKE_FLAGS += CONFIG_ASPM=n
+3 -1
View File
@@ -33,7 +33,9 @@ $(call KernelPackage/r8126)
endef endef
ifeq ($(BUILD_VARIANT),rss) ifeq ($(BUILD_VARIANT),rss)
PKG_MAKE_FLAGS += ENABLE_RSS_SUPPORT=y PKG_MAKE_FLAGS += \
ENABLE_RSS_SUPPORT=y \
ENABLE_MULTIPLE_TX_QUEUE=y
endif endif
PKG_MAKE_FLAGS += CONFIG_ASPM=n PKG_MAKE_FLAGS += CONFIG_ASPM=n
@@ -296,10 +296,6 @@
}; };
}; };
/*
* fspi is unavailable
* use i2c instead
*/
&i2c8 { &i2c8 {
pinctrl-names = "default"; pinctrl-names = "default";
pinctrl-0 = <&i2c8m1_xfer>; pinctrl-0 = <&i2c8m1_xfer>;
@@ -840,9 +836,6 @@
status = "okay"; status = "okay";
}; };
/*
* Disabled due to driver bug.
*/
&usb_host2_xhci { &usb_host2_xhci {
status = "disabled"; status = "okay";
}; };
@@ -0,0 +1,39 @@
--- a/arch/arm64/boot/dts/rockchip/rk3568.dtsi
+++ b/arch/arm64/boot/dts/rockchip/rk3568.dtsi
@@ -225,6 +225,7 @@
assigned-clocks = <&pmucru CLK_PCIEPHY0_REF>;
assigned-clock-rates = <100000000>;
resets = <&cru SRST_PIPEPHY0>;
+ reset-names = "phy";
rockchip,pipe-grf = <&pipegrf>;
rockchip,pipe-phy-grf = <&pipe_phy_grf0>;
#phy-cells = <1>;
--- a/arch/arm64/boot/dts/rockchip/rk356x.dtsi
+++ b/arch/arm64/boot/dts/rockchip/rk356x.dtsi
@@ -1686,6 +1686,7 @@
assigned-clocks = <&pmucru CLK_PCIEPHY1_REF>;
assigned-clock-rates = <100000000>;
resets = <&cru SRST_PIPEPHY1>;
+ reset-names = "phy";
rockchip,pipe-grf = <&pipegrf>;
rockchip,pipe-phy-grf = <&pipe_phy_grf1>;
#phy-cells = <1>;
@@ -1702,6 +1703,7 @@
assigned-clocks = <&pmucru CLK_PCIEPHY2_REF>;
assigned-clock-rates = <100000000>;
resets = <&cru SRST_PIPEPHY2>;
+ reset-names = "phy";
rockchip,pipe-grf = <&pipegrf>;
rockchip,pipe-phy-grf = <&pipe_phy_grf2>;
#phy-cells = <1>;
--- a/drivers/phy/rockchip/phy-rockchip-naneng-combphy.c
+++ b/drivers/phy/rockchip/phy-rockchip-naneng-combphy.c
@@ -299,7 +299,7 @@ static int rockchip_combphy_parse_dt(str
priv->ext_refclk = device_property_present(dev, "rockchip,ext-refclk");
- priv->phy_rst = devm_reset_control_array_get_exclusive(dev);
+ priv->phy_rst = devm_reset_control_get(dev, "phy");
if (IS_ERR(priv->phy_rst))
return dev_err_probe(dev, PTR_ERR(priv->phy_rst), "failed to get phy reset\n");
@@ -0,0 +1,39 @@
--- a/arch/arm64/boot/dts/rockchip/rk3568.dtsi
+++ b/arch/arm64/boot/dts/rockchip/rk3568.dtsi
@@ -225,6 +225,7 @@
assigned-clocks = <&pmucru CLK_PCIEPHY0_REF>;
assigned-clock-rates = <100000000>;
resets = <&cru SRST_PIPEPHY0>;
+ reset-names = "phy";
rockchip,pipe-grf = <&pipegrf>;
rockchip,pipe-phy-grf = <&pipe_phy_grf0>;
#phy-cells = <1>;
--- a/arch/arm64/boot/dts/rockchip/rk356x.dtsi
+++ b/arch/arm64/boot/dts/rockchip/rk356x.dtsi
@@ -1719,6 +1719,7 @@
assigned-clocks = <&pmucru CLK_PCIEPHY1_REF>;
assigned-clock-rates = <100000000>;
resets = <&cru SRST_PIPEPHY1>;
+ reset-names = "phy";
rockchip,pipe-grf = <&pipegrf>;
rockchip,pipe-phy-grf = <&pipe_phy_grf1>;
#phy-cells = <1>;
@@ -1735,6 +1736,7 @@
assigned-clocks = <&pmucru CLK_PCIEPHY2_REF>;
assigned-clock-rates = <100000000>;
resets = <&cru SRST_PIPEPHY2>;
+ reset-names = "phy";
rockchip,pipe-grf = <&pipegrf>;
rockchip,pipe-phy-grf = <&pipe_phy_grf2>;
#phy-cells = <1>;
--- a/drivers/phy/rockchip/phy-rockchip-naneng-combphy.c
+++ b/drivers/phy/rockchip/phy-rockchip-naneng-combphy.c
@@ -324,7 +324,7 @@ static int rockchip_combphy_parse_dt(str
priv->ext_refclk = device_property_present(dev, "rockchip,ext-refclk");
- priv->phy_rst = devm_reset_control_array_get_exclusive(dev);
+ priv->phy_rst = devm_reset_control_get(dev, "phy");
if (IS_ERR(priv->phy_rst))
return dev_err_probe(dev, PTR_ERR(priv->phy_rst), "failed to get phy reset\n");
@@ -98,9 +98,50 @@ if not fs.access(CACHE_PATH) then
fs.mkdir(CACHE_PATH) fs.mkdir(CACHE_PATH)
end end
local LOCAL_EXTEND_ARG = ""
if LOCAL_GROUP == "nil" then if LOCAL_GROUP == "nil" then
LOCAL_GROUP = nil LOCAL_GROUP = nil
log(" * 注意:国内分组名未设置,可能会导致 DNS 解析异常") log(" * 注意:国内分组名未设置,可能会导致 DNS 分流错误")
else
--从smartdns配置中读取参数
local custom_conf_path = "/etc/smartdns/custom.conf"
local options = {
{key = "dualstack_ip_selection", config_key = "dualstack-ip-selection", yes_no = true, arg_yes = "-d yes", arg_no = "-d no", default = "yes"},
{key = "speed_check_mode", config_key = "speed-check-mode", prefix = "-c ", default = "ping,tcp:80,tcp:443"},
{key = "serve_expired", config_key = "serve-expired", yes_no = true, arg_yes = "", arg_no = "-no-serve-expired", default = "yes"},
{key = "response_mode", config_key = "response-mode", prefix = "-r ", default = "first-ping"},
{key = "rr_ttl", config_key = "rr-ttl", prefix = "-rr-ttl "},
{key = "rr_ttl_min", config_key = "rr-ttl-min", prefix = "-rr-ttl-min "},
{key = "rr_ttl_max", config_key = "rr-ttl-max", prefix = "-rr-ttl-max "}
}
-- 从 custom.conf 中读取值,以最后出现的值为准
local custom_config = {}
local f_in = io.open(custom_conf_path, "r")
if f_in then
for line in f_in:lines() do
line = line:match("^%s*(.-)%s*$")
if line ~= "" and not line:match("^#") then
local param, value = line:match("^(%S+)%s+(%S+)$")
if param and value then custom_config[param] = value end
end
end
f_in:close()
end
-- 从 smartdns 配置中读取值,优先级以 custom.conf 为准
for _, opt in ipairs(options) do
local val = custom_config[opt.config_key] or uci:get("smartdns", "@smartdns[0]", opt.key) or opt.default
if val == "yes" then val = "1" elseif val == "no" then val = "0" end
if opt.yes_no then
local arg = (val == "1" and opt.arg_yes or opt.arg_no)
if arg and arg ~= "" then
LOCAL_EXTEND_ARG = LOCAL_EXTEND_ARG .. (LOCAL_EXTEND_ARG ~= "" and " " or "") .. arg
end
else
if val and (not opt.value or (opt.invert and val ~= opt.value) or (not opt.invert and val == opt.value)) then
LOCAL_EXTEND_ARG = LOCAL_EXTEND_ARG .. (LOCAL_EXTEND_ARG ~= "" and " " or "") .. (opt.prefix or "") .. (opt.arg or val)
end
end
end
end end
if not REMOTE_GROUP or REMOTE_GROUP == "nil" then if not REMOTE_GROUP or REMOTE_GROUP == "nil" then
@@ -167,6 +208,8 @@ if DEFAULT_DNS_GROUP then
if NO_PROXY_IPV6 == "1" and only_global == 1 and uci:get(appname, TCP_NODE, "protocol") ~= "_shunt" then if NO_PROXY_IPV6 == "1" and only_global == 1 and uci:get(appname, TCP_NODE, "protocol") ~= "_shunt" then
domain_rules_str = domain_rules_str .. " -address #6" domain_rules_str = domain_rules_str .. " -address #6"
end end
elseif DEFAULT_DNS_GROUP == LOCAL_GROUP then
domain_rules_str = domain_rules_str .. (LOCAL_EXTEND_ARG ~= "" and " " .. LOCAL_EXTEND_ARG or "")
end end
table.insert(config_lines, domain_rules_str) table.insert(config_lines, domain_rules_str)
end end
@@ -226,6 +269,7 @@ if is_file_nonzero(file_vpslist) then
} }
local domain_rules_str = string.format('domain-rules /domain-set:%s/ %s', domain_set_name, LOCAL_GROUP and "-nameserver " .. LOCAL_GROUP or "") local domain_rules_str = string.format('domain-rules /domain-set:%s/ %s', domain_set_name, LOCAL_GROUP and "-nameserver " .. LOCAL_GROUP or "")
domain_rules_str = domain_rules_str .. " " .. set_type .. " #4:" .. setflag .. "passwall_vpslist,#6:" .. setflag .. "passwall_vpslist6" domain_rules_str = domain_rules_str .. " " .. set_type .. " #4:" .. setflag .. "passwall_vpslist,#6:" .. setflag .. "passwall_vpslist6"
domain_rules_str = domain_rules_str .. (LOCAL_EXTEND_ARG ~= "" and " " .. LOCAL_EXTEND_ARG or "")
table.insert(tmp_lines, domain_rules_str) table.insert(tmp_lines, domain_rules_str)
insert_array_after(config_lines, tmp_lines, "#--8") insert_array_after(config_lines, tmp_lines, "#--8")
log(string.format(" - 节点列表中的域名(vpslist)使用分组:%s", LOCAL_GROUP or "默认")) log(string.format(" - 节点列表中的域名(vpslist)使用分组:%s", LOCAL_GROUP or "默认"))
@@ -256,6 +300,7 @@ if USE_DIRECT_LIST == "1" and is_file_nonzero(file_direct_host) then
} }
local domain_rules_str = string.format('domain-rules /domain-set:%s/ %s', domain_set_name, LOCAL_GROUP and "-nameserver " .. LOCAL_GROUP or "") local domain_rules_str = string.format('domain-rules /domain-set:%s/ %s', domain_set_name, LOCAL_GROUP and "-nameserver " .. LOCAL_GROUP or "")
domain_rules_str = domain_rules_str .. " " .. set_type .. " #4:" .. setflag .. "passwall_whitelist,#6:" .. setflag .. "passwall_whitelist6" domain_rules_str = domain_rules_str .. " " .. set_type .. " #4:" .. setflag .. "passwall_whitelist,#6:" .. setflag .. "passwall_whitelist6"
domain_rules_str = domain_rules_str .. (LOCAL_EXTEND_ARG ~= "" and " " .. LOCAL_EXTEND_ARG or "")
table.insert(tmp_lines, domain_rules_str) table.insert(tmp_lines, domain_rules_str)
insert_array_after(config_lines, tmp_lines, "#--6") insert_array_after(config_lines, tmp_lines, "#--6")
log(string.format(" - 域名白名单(whitelist)使用分组:%s", LOCAL_GROUP or "默认")) log(string.format(" - 域名白名单(whitelist)使用分组:%s", LOCAL_GROUP or "默认"))
@@ -328,6 +373,7 @@ if CHN_LIST ~= "0" and is_file_nonzero(RULES_PATH .. "/chnlist") then
if CHN_LIST == "direct" then if CHN_LIST == "direct" then
local domain_rules_str = string.format('domain-rules /domain-set:%s/ %s', domain_set_name, LOCAL_GROUP and "-nameserver " .. LOCAL_GROUP or "") local domain_rules_str = string.format('domain-rules /domain-set:%s/ %s', domain_set_name, LOCAL_GROUP and "-nameserver " .. LOCAL_GROUP or "")
domain_rules_str = domain_rules_str .. " " .. set_type .. " #4:" .. setflag .. "passwall_chnroute,#6:" .. setflag .. "passwall_chnroute6" domain_rules_str = domain_rules_str .. " " .. set_type .. " #4:" .. setflag .. "passwall_chnroute,#6:" .. setflag .. "passwall_chnroute6"
domain_rules_str = domain_rules_str .. (LOCAL_EXTEND_ARG ~= "" and " " .. LOCAL_EXTEND_ARG or "")
table.insert(tmp_lines, domain_rules_str) table.insert(tmp_lines, domain_rules_str)
insert_array_after(config_lines, tmp_lines, "#--2") insert_array_after(config_lines, tmp_lines, "#--2")
log(string.format(" - 中国域名表(chnroute)使用分组:%s", LOCAL_GROUP or "默认")) log(string.format(" - 中国域名表(chnroute)使用分组:%s", LOCAL_GROUP or "默认"))
@@ -419,6 +465,7 @@ if uci:get(appname, TCP_NODE, "protocol") == "_shunt" then
} }
local domain_rules_str = string.format('domain-rules /domain-set:%s/ %s', domain_set_name, LOCAL_GROUP and "-nameserver " .. LOCAL_GROUP or "") local domain_rules_str = string.format('domain-rules /domain-set:%s/ %s', domain_set_name, LOCAL_GROUP and "-nameserver " .. LOCAL_GROUP or "")
domain_rules_str = domain_rules_str .. " " .. set_type .. " #4:" .. setflag .. "passwall_whitelist,#6:" .. setflag .. "passwall_whitelist6" domain_rules_str = domain_rules_str .. " " .. set_type .. " #4:" .. setflag .. "passwall_whitelist,#6:" .. setflag .. "passwall_whitelist6"
domain_rules_str = domain_rules_str .. (LOCAL_EXTEND_ARG ~= "" and " " .. LOCAL_EXTEND_ARG or "")
table.insert(tmp_lines, domain_rules_str) table.insert(tmp_lines, domain_rules_str)
insert_array_after(config_lines, tmp_lines, "#--3") insert_array_after(config_lines, tmp_lines, "#--3")
end end
+6 -4
View File
@@ -70,10 +70,12 @@ type InboundContext struct {
InboundOptions option.InboundOptions InboundOptions option.InboundOptions
UDPDisableDomainUnmapping bool UDPDisableDomainUnmapping bool
UDPConnect bool UDPConnect bool
NetworkStrategy C.NetworkStrategy UDPTimeout time.Duration
NetworkType []C.InterfaceType
FallbackNetworkType []C.InterfaceType NetworkStrategy C.NetworkStrategy
FallbackDelay time.Duration NetworkType []C.InterfaceType
FallbackNetworkType []C.InterfaceType
FallbackDelay time.Duration
DNSServer string DNSServer string
-157
View File
@@ -1,157 +0,0 @@
package outbound
import (
"context"
"net"
"net/netip"
"os"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/dialer"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
"github.com/sagernet/sing/common/canceler"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
func NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata adapter.InboundContext) error {
defer conn.Close()
ctx = adapter.WithContext(ctx, &metadata)
var outConn net.Conn
var err error
if len(metadata.DestinationAddresses) > 0 {
outConn, err = dialer.DialSerialNetwork(ctx, this, N.NetworkTCP, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.NetworkType, metadata.FallbackNetworkType, metadata.FallbackDelay)
} else {
outConn, err = this.DialContext(ctx, N.NetworkTCP, metadata.Destination)
}
if err != nil {
return N.ReportHandshakeFailure(conn, err)
}
err = N.ReportConnHandshakeSuccess(conn, outConn)
if err != nil {
outConn.Close()
return err
}
return CopyEarlyConn(ctx, conn, outConn)
}
func NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, metadata adapter.InboundContext) error {
defer conn.Close()
ctx = adapter.WithContext(ctx, &metadata)
var (
outPacketConn net.PacketConn
outConn net.Conn
destinationAddress netip.Addr
err error
)
if metadata.UDPConnect {
if len(metadata.DestinationAddresses) > 0 {
if parallelDialer, isParallelDialer := this.(dialer.ParallelInterfaceDialer); isParallelDialer {
outConn, err = dialer.DialSerialNetwork(ctx, parallelDialer, N.NetworkUDP, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.NetworkType, metadata.FallbackNetworkType, metadata.FallbackDelay)
} else {
outConn, err = N.DialSerial(ctx, this, N.NetworkUDP, metadata.Destination, metadata.DestinationAddresses)
}
} else {
outConn, err = this.DialContext(ctx, N.NetworkUDP, metadata.Destination)
}
if err != nil {
return N.ReportHandshakeFailure(conn, err)
}
outPacketConn = bufio.NewUnbindPacketConn(outConn)
connRemoteAddr := M.AddrFromNet(outConn.RemoteAddr())
if connRemoteAddr != metadata.Destination.Addr {
destinationAddress = connRemoteAddr
}
} else {
if len(metadata.DestinationAddresses) > 0 {
outPacketConn, destinationAddress, err = dialer.ListenSerialNetworkPacket(ctx, this, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.NetworkType, metadata.FallbackNetworkType, metadata.FallbackDelay)
} else {
outPacketConn, err = this.ListenPacket(ctx, metadata.Destination)
}
if err != nil {
return N.ReportHandshakeFailure(conn, err)
}
}
err = N.ReportPacketConnHandshakeSuccess(conn, outPacketConn)
if err != nil {
outPacketConn.Close()
return err
}
if destinationAddress.IsValid() {
var originDestination M.Socksaddr
if metadata.RouteOriginalDestination.IsValid() {
originDestination = metadata.RouteOriginalDestination
} else {
originDestination = metadata.Destination
}
if metadata.Destination != M.SocksaddrFrom(destinationAddress, metadata.Destination.Port) {
if metadata.UDPDisableDomainUnmapping {
outPacketConn = bufio.NewUnidirectionalNATPacketConn(bufio.NewPacketConn(outPacketConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), originDestination)
} else {
outPacketConn = bufio.NewNATPacketConn(bufio.NewPacketConn(outPacketConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), originDestination)
}
}
if natConn, loaded := common.Cast[bufio.NATPacketConn](conn); loaded {
natConn.UpdateDestination(destinationAddress)
}
}
switch metadata.Protocol {
case C.ProtocolSTUN:
ctx, conn = canceler.NewPacketConn(ctx, conn, C.STUNTimeout)
case C.ProtocolQUIC:
ctx, conn = canceler.NewPacketConn(ctx, conn, C.QUICTimeout)
case C.ProtocolDNS:
ctx, conn = canceler.NewPacketConn(ctx, conn, C.DNSTimeout)
}
return bufio.CopyPacketConn(ctx, conn, bufio.NewPacketConn(outPacketConn))
}
func CopyEarlyConn(ctx context.Context, conn net.Conn, serverConn net.Conn) error {
if cachedReader, isCached := conn.(N.CachedReader); isCached {
payload := cachedReader.ReadCached()
if payload != nil && !payload.IsEmpty() {
_, err := serverConn.Write(payload.Bytes())
payload.Release()
if err != nil {
serverConn.Close()
return err
}
return bufio.CopyConn(ctx, conn, serverConn)
}
}
if earlyConn, isEarlyConn := common.Cast[N.EarlyConn](serverConn); isEarlyConn && earlyConn.NeedHandshake() {
payload := buf.NewPacket()
err := conn.SetReadDeadline(time.Now().Add(C.ReadPayloadTimeout))
if err != os.ErrInvalid {
if err != nil {
payload.Release()
serverConn.Close()
return err
}
_, err = payload.ReadOnceFrom(conn)
if err != nil && !E.IsTimeout(err) {
payload.Release()
serverConn.Close()
return E.Cause(err, "read payload")
}
err = conn.SetReadDeadline(time.Time{})
if err != nil {
payload.Release()
serverConn.Close()
return err
}
}
_, err = serverConn.Write(payload.Bytes())
payload.Release()
if err != nil {
serverConn.Close()
return N.ReportHandshakeFailure(conn, err)
}
}
return bufio.CopyConn(ctx, conn, serverConn)
}
+1
View File
@@ -10,6 +10,7 @@ const (
ProtocolDTLS = "dtls" ProtocolDTLS = "dtls"
ProtocolSSH = "ssh" ProtocolSSH = "ssh"
ProtocolRDP = "rdp" ProtocolRDP = "rdp"
ProtocolNTP = "ntp"
) )
const ( const (
+15 -2
View File
@@ -9,8 +9,6 @@ const (
TCPTimeout = 15 * time.Second TCPTimeout = 15 * time.Second
ReadPayloadTimeout = 300 * time.Millisecond ReadPayloadTimeout = 300 * time.Millisecond
DNSTimeout = 10 * time.Second DNSTimeout = 10 * time.Second
QUICTimeout = 30 * time.Second
STUNTimeout = 15 * time.Second
UDPTimeout = 5 * time.Minute UDPTimeout = 5 * time.Minute
DefaultURLTestInterval = 3 * time.Minute DefaultURLTestInterval = 3 * time.Minute
DefaultURLTestIdleTimeout = 30 * time.Minute DefaultURLTestIdleTimeout = 30 * time.Minute
@@ -19,3 +17,18 @@ const (
FatalStopTimeout = 10 * time.Second FatalStopTimeout = 10 * time.Second
FakeIPMetadataSaveInterval = 10 * time.Second FakeIPMetadataSaveInterval = 10 * time.Second
) )
var PortProtocols = map[uint16]string{
53: ProtocolDNS,
123: ProtocolNTP,
3478: ProtocolSTUN,
443: ProtocolQUIC,
}
var ProtocolTimeouts = map[string]time.Duration{
ProtocolDNS: 10 * time.Second,
ProtocolNTP: 10 * time.Second,
ProtocolSTUN: 10 * time.Second,
ProtocolQUIC: 30 * time.Second,
ProtocolDTLS: 30 * time.Second,
}
+13
View File
@@ -2,6 +2,19 @@
icon: material/alert-decagram icon: material/alert-decagram
--- ---
#### 1.11.0-alpha.23
* Fixes and improvements
#### 1.11.0-alpha.22
* Add UDP timeout route option **1**
* Fixes and improvements
**1**:
See [Rule Action](/configuration/route/rule_action/#udp_timeout).
#### 1.11.0-alpha.20 #### 1.11.0-alpha.20
* Add UDP GSO support for WireGuard * Add UDP GSO support for WireGuard
@@ -41,7 +41,8 @@ See `route-options` fields below.
"network_strategy": "", "network_strategy": "",
"fallback_delay": "", "fallback_delay": "",
"udp_disable_domain_unmapping": false, "udp_disable_domain_unmapping": false,
"udp_connect": false "udp_connect": false,
"udp_timeout": ""
} }
``` ```
@@ -86,6 +87,28 @@ do not support receiving UDP packets with domain addresses, such as Surge.
If enabled, attempts to connect UDP connection to the destination instead of listen. If enabled, attempts to connect UDP connection to the destination instead of listen.
#### udp_timeout
Timeout for UDP connections.
Setting a larger value than the UDP timeout in inbounds will have no effect.
Default value for protocol sniffed connections:
| Timeout | Protocol |
|---------|----------------------|
| `10s` | `dns`, `ntp`, `stun` |
| `30s` | `quic`, `dtls` |
If no protocol is sniffed, the following ports will be recognized as protocols by default:
| Port | Protocol |
|------|----------|
| 53 | `dns` |
| 123 | `ntp` |
| 443 | `quic` |
| 3478 | `stun` |
### reject ### reject
```json ```json
@@ -37,7 +37,8 @@ icon: material/new-box
"network_strategy": "", "network_strategy": "",
"fallback_delay": "", "fallback_delay": "",
"udp_disable_domain_unmapping": false, "udp_disable_domain_unmapping": false,
"udp_connect": false "udp_connect": false,
"udp_timeout": ""
} }
``` ```
@@ -84,6 +85,28 @@ icon: material/new-box
如果启用,将尝试将 UDP 连接 connect 到目标而不是 listen。 如果启用,将尝试将 UDP 连接 connect 到目标而不是 listen。
#### udp_timeout
UDP 连接超时时间。
设置比入站 UDP 超时更大的值将无效。
已探测协议连接的默认值:
| 超时 | 协议 |
|-------|----------------------|
| `10s` | `dns`, `ntp`, `stun` |
| `30s` | `quic`, `dtls` |
如果没有探测到协议,以下端口将默认识别为协议:
| 端口 | 协议 |
|------|--------|
| 53 | `dns` |
| 123 | `ntp` |
| 443 | `quic` |
| 3478 | `stun` |
### reject ### reject
```json ```json
+1 -1
View File
@@ -25,7 +25,7 @@ require (
github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff
github.com/sagernet/quic-go v0.48.1-beta.1 github.com/sagernet/quic-go v0.48.1-beta.1
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
github.com/sagernet/sing v0.6.0-alpha.18 github.com/sagernet/sing v0.6.0-alpha.20
github.com/sagernet/sing-dns v0.4.0-alpha.3 github.com/sagernet/sing-dns v0.4.0-alpha.3
github.com/sagernet/sing-mux v0.3.0-alpha.1 github.com/sagernet/sing-mux v0.3.0-alpha.1
github.com/sagernet/sing-quic v0.4.0-alpha.4 github.com/sagernet/sing-quic v0.4.0-alpha.4
+2 -2
View File
@@ -110,8 +110,8 @@ github.com/sagernet/quic-go v0.48.1-beta.1/go.mod h1:1WgdDIVD1Gybp40JTWketeSfKA/
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc=
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo= github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo=
github.com/sagernet/sing v0.6.0-alpha.18 h1:ih4CurU8KvbhfagYjSqVrE2LR0oBSXSZTNH2sAGPGiM= github.com/sagernet/sing v0.6.0-alpha.20 h1:coxvnzeEGSLNNPntUW7l8WUEHPIwqKszZNbU019To9c=
github.com/sagernet/sing v0.6.0-alpha.18/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing v0.6.0-alpha.20/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing-dns v0.4.0-alpha.3 h1:TcAQdz68Gs28VD9o9zDIW7IS8A9LZDruTPI9g9JbGHA= github.com/sagernet/sing-dns v0.4.0-alpha.3 h1:TcAQdz68Gs28VD9o9zDIW7IS8A9LZDruTPI9g9JbGHA=
github.com/sagernet/sing-dns v0.4.0-alpha.3/go.mod h1:9LHcYKg2bGQpbtXrfNbopz8ok/zBK9ljiI2kmFG9JKg= github.com/sagernet/sing-dns v0.4.0-alpha.3/go.mod h1:9LHcYKg2bGQpbtXrfNbopz8ok/zBK9ljiI2kmFG9JKg=
github.com/sagernet/sing-mux v0.3.0-alpha.1 h1:IgNX5bJBpL41gGbp05pdDOvh/b5eUQ6cv9240+Ngipg= github.com/sagernet/sing-mux v0.3.0-alpha.1 h1:IgNX5bJBpL41gGbp05pdDOvh/b5eUQ6cv9240+Ngipg=
+3 -2
View File
@@ -148,8 +148,9 @@ type RawRouteOptionsActionOptions struct {
NetworkStrategy NetworkStrategy `json:"network_strategy,omitempty"` NetworkStrategy NetworkStrategy `json:"network_strategy,omitempty"`
FallbackDelay uint32 `json:"fallback_delay,omitempty"` FallbackDelay uint32 `json:"fallback_delay,omitempty"`
UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty"` UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty"`
UDPConnect bool `json:"udp_connect,omitempty"` UDPConnect bool `json:"udp_connect,omitempty"`
UDPTimeout badoption.Duration `json:"udp_timeout,omitempty"`
} }
type RouteOptionsActionOptions RawRouteOptionsActionOptions type RouteOptionsActionOptions RawRouteOptionsActionOptions
+1 -1
View File
@@ -14,7 +14,7 @@ type WireGuardEndpointOptions struct {
PrivateKey string `json:"private_key"` PrivateKey string `json:"private_key"`
ListenPort uint16 `json:"listen_port,omitempty"` ListenPort uint16 `json:"listen_port,omitempty"`
Peers []WireGuardPeer `json:"peers,omitempty"` Peers []WireGuardPeer `json:"peers,omitempty"`
UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"` UDPTimeout badoption.Duration `json:"udp_timeout,omitempty"`
Workers int `json:"workers,omitempty"` Workers int `json:"workers,omitempty"`
DialerOptions DialerOptions
} }
+32 -27
View File
@@ -10,6 +10,7 @@ import (
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common/atomic"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger" "github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
@@ -21,17 +22,22 @@ func RegisterSelector(registry *outbound.Registry) {
outbound.Register[option.SelectorOutboundOptions](registry, C.TypeSelector, NewSelector) outbound.Register[option.SelectorOutboundOptions](registry, C.TypeSelector, NewSelector)
} }
var _ adapter.OutboundGroup = (*Selector)(nil) var (
_ adapter.OutboundGroup = (*Selector)(nil)
_ adapter.ConnectionHandlerEx = (*Selector)(nil)
_ adapter.PacketConnectionHandlerEx = (*Selector)(nil)
)
type Selector struct { type Selector struct {
outbound.Adapter outbound.Adapter
ctx context.Context ctx context.Context
outboundManager adapter.OutboundManager outbound adapter.OutboundManager
connection adapter.ConnectionManager
logger logger.ContextLogger logger logger.ContextLogger
tags []string tags []string
defaultTag string defaultTag string
outbounds map[string]adapter.Outbound outbounds map[string]adapter.Outbound
selected adapter.Outbound selected atomic.TypedValue[adapter.Outbound]
interruptGroup *interrupt.Group interruptGroup *interrupt.Group
interruptExternalConnections bool interruptExternalConnections bool
} }
@@ -40,7 +46,8 @@ func NewSelector(ctx context.Context, router adapter.Router, logger log.ContextL
outbound := &Selector{ outbound := &Selector{
Adapter: outbound.NewAdapter(C.TypeSelector, tag, nil, options.Outbounds), Adapter: outbound.NewAdapter(C.TypeSelector, tag, nil, options.Outbounds),
ctx: ctx, ctx: ctx,
outboundManager: service.FromContext[adapter.OutboundManager](ctx), outbound: service.FromContext[adapter.OutboundManager](ctx),
connection: service.FromContext[adapter.ConnectionManager](ctx),
logger: logger, logger: logger,
tags: options.Outbounds, tags: options.Outbounds,
defaultTag: options.Default, defaultTag: options.Default,
@@ -55,15 +62,16 @@ func NewSelector(ctx context.Context, router adapter.Router, logger log.ContextL
} }
func (s *Selector) Network() []string { func (s *Selector) Network() []string {
if s.selected == nil { selected := s.selected.Load()
if selected == nil {
return []string{N.NetworkTCP, N.NetworkUDP} return []string{N.NetworkTCP, N.NetworkUDP}
} }
return s.selected.Network() return selected.Network()
} }
func (s *Selector) Start() error { func (s *Selector) Start() error {
for i, tag := range s.tags { for i, tag := range s.tags {
detour, loaded := s.outboundManager.Outbound(tag) detour, loaded := s.outbound.Outbound(tag)
if !loaded { if !loaded {
return E.New("outbound ", i, " not found: ", tag) return E.New("outbound ", i, " not found: ", tag)
} }
@@ -77,7 +85,7 @@ func (s *Selector) Start() error {
if selected != "" { if selected != "" {
detour, loaded := s.outbounds[selected] detour, loaded := s.outbounds[selected]
if loaded { if loaded {
s.selected = detour s.selected.Store(detour)
return nil return nil
} }
} }
@@ -89,16 +97,16 @@ func (s *Selector) Start() error {
if !loaded { if !loaded {
return E.New("default outbound not found: ", s.defaultTag) return E.New("default outbound not found: ", s.defaultTag)
} }
s.selected = detour s.selected.Store(detour)
return nil return nil
} }
s.selected = s.outbounds[s.tags[0]] s.selected.Store(s.outbounds[s.tags[0]])
return nil return nil
} }
func (s *Selector) Now() string { func (s *Selector) Now() string {
selected := s.selected selected := s.selected.Load()
if selected == nil { if selected == nil {
return s.tags[0] return s.tags[0]
} }
@@ -114,10 +122,9 @@ func (s *Selector) SelectOutbound(tag string) bool {
if !loaded { if !loaded {
return false return false
} }
if s.selected == detour { if s.selected.Swap(detour) == detour {
return true return true
} }
s.selected = detour
if s.Tag() != "" { if s.Tag() != "" {
cacheFile := service.FromContext[adapter.CacheFile](s.ctx) cacheFile := service.FromContext[adapter.CacheFile](s.ctx)
if cacheFile != nil { if cacheFile != nil {
@@ -132,7 +139,7 @@ func (s *Selector) SelectOutbound(tag string) bool {
} }
func (s *Selector) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { func (s *Selector) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
conn, err := s.selected.DialContext(ctx, network, destination) conn, err := s.selected.Load().DialContext(ctx, network, destination)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -140,32 +147,30 @@ func (s *Selector) DialContext(ctx context.Context, network string, destination
} }
func (s *Selector) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { func (s *Selector) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
conn, err := s.selected.ListenPacket(ctx, destination) conn, err := s.selected.Load().ListenPacket(ctx, destination)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return s.interruptGroup.NewPacketConn(conn, interrupt.IsExternalConnectionFromContext(ctx)), nil return s.interruptGroup.NewPacketConn(conn, interrupt.IsExternalConnectionFromContext(ctx)), nil
} }
// TODO func (s *Selector) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
// Deprecated
func (s *Selector) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
ctx = interrupt.ContextWithIsExternalConnection(ctx) ctx = interrupt.ContextWithIsExternalConnection(ctx)
if legacyHandler, ok := s.selected.(adapter.ConnectionHandler); ok { selected := s.selected.Load()
return legacyHandler.NewConnection(ctx, conn, metadata) if outboundHandler, isHandler := selected.(adapter.ConnectionHandlerEx); isHandler {
outboundHandler.NewConnectionEx(ctx, conn, metadata, onClose)
} else { } else {
return outbound.NewConnection(ctx, s.selected, conn, metadata) s.connection.NewConnection(ctx, selected, conn, metadata, onClose)
} }
} }
// TODO func (s *Selector) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
// Deprecated
func (s *Selector) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
ctx = interrupt.ContextWithIsExternalConnection(ctx) ctx = interrupt.ContextWithIsExternalConnection(ctx)
if legacyHandler, ok := s.selected.(adapter.PacketConnectionHandler); ok { selected := s.selected.Load()
return legacyHandler.NewPacketConnection(ctx, conn, metadata) if outboundHandler, isHandler := selected.(adapter.PacketConnectionHandlerEx); isHandler {
outboundHandler.NewPacketConnectionEx(ctx, conn, metadata, onClose)
} else { } else {
return outbound.NewPacketConnection(ctx, s.selected, conn, metadata) s.connection.NewPacketConnection(ctx, selected, conn, metadata, onClose)
} }
} }
+10 -12
View File
@@ -36,7 +36,8 @@ type URLTest struct {
outbound.Adapter outbound.Adapter
ctx context.Context ctx context.Context
router adapter.Router router adapter.Router
outboundManager adapter.OutboundManager outbound adapter.OutboundManager
connection adapter.ConnectionManager
logger log.ContextLogger logger log.ContextLogger
tags []string tags []string
link string link string
@@ -52,7 +53,8 @@ func NewURLTest(ctx context.Context, router adapter.Router, logger log.ContextLo
Adapter: outbound.NewAdapter(C.TypeURLTest, tag, []string{N.NetworkTCP, N.NetworkUDP}, options.Outbounds), Adapter: outbound.NewAdapter(C.TypeURLTest, tag, []string{N.NetworkTCP, N.NetworkUDP}, options.Outbounds),
ctx: ctx, ctx: ctx,
router: router, router: router,
outboundManager: service.FromContext[adapter.OutboundManager](ctx), outbound: service.FromContext[adapter.OutboundManager](ctx),
connection: service.FromContext[adapter.ConnectionManager](ctx),
logger: logger, logger: logger,
tags: options.Outbounds, tags: options.Outbounds,
link: options.URL, link: options.URL,
@@ -70,13 +72,13 @@ func NewURLTest(ctx context.Context, router adapter.Router, logger log.ContextLo
func (s *URLTest) Start() error { func (s *URLTest) Start() error {
outbounds := make([]adapter.Outbound, 0, len(s.tags)) outbounds := make([]adapter.Outbound, 0, len(s.tags))
for i, tag := range s.tags { for i, tag := range s.tags {
detour, loaded := s.outboundManager.Outbound(tag) detour, loaded := s.outbound.Outbound(tag)
if !loaded { if !loaded {
return E.New("outbound ", i, " not found: ", tag) return E.New("outbound ", i, " not found: ", tag)
} }
outbounds = append(outbounds, detour) outbounds = append(outbounds, detour)
} }
group, err := NewURLTestGroup(s.ctx, s.outboundManager, s.logger, outbounds, s.link, s.interval, s.tolerance, s.idleTimeout, s.interruptExternalConnections) group, err := NewURLTestGroup(s.ctx, s.outbound, s.logger, outbounds, s.link, s.interval, s.tolerance, s.idleTimeout, s.interruptExternalConnections)
if err != nil { if err != nil {
return err return err
} }
@@ -160,18 +162,14 @@ func (s *URLTest) ListenPacket(ctx context.Context, destination M.Socksaddr) (ne
return nil, err return nil, err
} }
// TODO func (s *URLTest) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
// Deprecated
func (s *URLTest) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
ctx = interrupt.ContextWithIsExternalConnection(ctx) ctx = interrupt.ContextWithIsExternalConnection(ctx)
return outbound.NewConnection(ctx, s, conn, metadata) s.connection.NewConnection(ctx, s, conn, metadata, onClose)
} }
// TODO func (s *URLTest) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
// Deprecated
func (s *URLTest) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
ctx = interrupt.ContextWithIsExternalConnection(ctx) ctx = interrupt.ContextWithIsExternalConnection(ctx)
return outbound.NewPacketConnection(ctx, s, conn, metadata) s.connection.NewPacketConnection(ctx, s, conn, metadata, onClose)
} }
func (s *URLTest) InterfaceUpdated() { func (s *URLTest) InterfaceUpdated() {
+42 -29
View File
@@ -6,11 +6,14 @@ import (
"net" "net"
"net/netip" "net/netip"
"sync/atomic" "sync/atomic"
"time"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/common/dialer"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/bufio" "github.com/sagernet/sing/common/bufio"
"github.com/sagernet/sing/common/canceler"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger" "github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
@@ -79,39 +82,34 @@ func (m *ConnectionManager) connectionCopy(ctx context.Context, source io.Reader
if cachedSrc, isCached := source.(N.CachedReader); isCached { if cachedSrc, isCached := source.(N.CachedReader); isCached {
cachedBuffer := cachedSrc.ReadCached() cachedBuffer := cachedSrc.ReadCached()
if cachedBuffer != nil { if cachedBuffer != nil {
if !cachedBuffer.IsEmpty() { dataLen := cachedBuffer.Len()
dataLen := cachedBuffer.Len() _, err := destination.Write(cachedBuffer.Bytes())
for _, counter := range readCounters {
counter(int64(dataLen))
}
_, err := destination.Write(cachedBuffer.Bytes())
if err != nil {
m.logger.ErrorContext(ctx, "connection upload payload: ", err)
cachedBuffer.Release()
if done.Swap(true) {
if onClose != nil {
onClose(err)
}
common.Close(source, destination)
}
return
}
for _, counter := range writeCounters {
counter(int64(dataLen))
}
}
cachedBuffer.Release() cachedBuffer.Release()
continue if err != nil {
m.logger.ErrorContext(ctx, "connection upload payload: ", err)
if done.Swap(true) {
if onClose != nil {
onClose(err)
}
}
common.Close(source, destination)
return
}
for _, counter := range readCounters {
counter(int64(dataLen))
}
for _, counter := range writeCounters {
counter(int64(dataLen))
}
} }
continue
} }
break break
} }
var ( _, err := bufio.CopyWithCounters(destination, source, originSource, readCounters, writeCounters)
dstDuplex bool if err != nil {
err error common.Close(destination, source)
) } else if _, dstDuplex := destination.(N.WriteCloser); dstDuplex {
_, err = bufio.CopyWithCounters(destination, source, originSource, readCounters, writeCounters)
if _, dstDuplex = common.Cast[N.WriteCloser](destination); dstDuplex && err == nil {
N.CloseWrite(destination) N.CloseWrite(destination)
} else { } else {
common.Close(destination) common.Close(destination)
@@ -206,6 +204,21 @@ func (m *ConnectionManager) NewPacketConnection(ctx context.Context, this N.Dial
natConn.UpdateDestination(destinationAddress) natConn.UpdateDestination(destinationAddress)
} }
} }
var udpTimeout time.Duration
if metadata.UDPTimeout > 0 {
udpTimeout = metadata.UDPTimeout
} else {
protocol := metadata.Protocol
if protocol == "" {
protocol = C.PortProtocols[metadata.Destination.Port]
}
if protocol != "" {
udpTimeout = C.ProtocolTimeouts[protocol]
}
}
if udpTimeout > 0 {
ctx, conn = canceler.NewPacketConn(ctx, conn, udpTimeout)
}
destination := bufio.NewPacketConn(remotePacketConn) destination := bufio.NewPacketConn(remotePacketConn)
if ctx.Done() != nil { if ctx.Done() != nil {
onClose = N.AppendClose(onClose, m.monitor.Add(ctx, conn)) onClose = N.AppendClose(onClose, m.monitor.Add(ctx, conn))
@@ -273,11 +286,11 @@ func (m *ConnectionManager) packetConnectionCopy(ctx context.Context, source N.P
} }
} }
if !done.Swap(true) { if !done.Swap(true) {
common.Close(source, destination)
if onClose != nil { if onClose != nil {
onClose(err) onClose(err)
} }
} }
common.Close(source, destination)
} }
/*type udpHijacker struct { /*type udpHijacker struct {
+7 -16
View File
@@ -132,23 +132,11 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad
if r.tracker != nil { if r.tracker != nil {
conn = r.tracker.RoutedConnection(ctx, conn, metadata, selectedRule, selectedOutbound) conn = r.tracker.RoutedConnection(ctx, conn, metadata, selectedRule, selectedOutbound)
} }
legacyOutbound, isLegacy := selectedOutbound.(adapter.ConnectionHandler) if outboundHandler, isHandler := selectedOutbound.(adapter.ConnectionHandlerEx); isHandler {
if isLegacy { outboundHandler.NewConnectionEx(ctx, conn, metadata, onClose)
err = legacyOutbound.NewConnection(ctx, conn, metadata) } else {
if err != nil { r.connection.NewConnection(ctx, selectedOutbound, conn, metadata, onClose)
conn.Close()
if onClose != nil {
onClose(err)
}
return E.Cause(err, F.ToString("outbound/", selectedOutbound.Type(), "[", selectedOutbound.Tag(), "]"))
} else {
if onClose != nil {
onClose(nil)
}
}
return nil
} }
r.connection.NewConnection(ctx, selectedOutbound, conn, metadata, onClose)
return nil return nil
} }
@@ -440,6 +428,9 @@ match:
if routeOptions.UDPConnect { if routeOptions.UDPConnect {
metadata.UDPConnect = true metadata.UDPConnect = true
} }
if routeOptions.UDPTimeout > 0 {
metadata.UDPTimeout = routeOptions.UDPTimeout
}
} }
switch action := currentRule.Action().(type) { switch action := currentRule.Action().(type) {
case *rule.RuleActionSniff: case *rule.RuleActionSniff:
+2
View File
@@ -47,6 +47,7 @@ func NewRuleAction(ctx context.Context, logger logger.ContextLogger, action opti
FallbackDelay: time.Duration(action.RouteOptionsOptions.FallbackDelay), FallbackDelay: time.Duration(action.RouteOptionsOptions.FallbackDelay),
UDPDisableDomainUnmapping: action.RouteOptionsOptions.UDPDisableDomainUnmapping, UDPDisableDomainUnmapping: action.RouteOptionsOptions.UDPDisableDomainUnmapping,
UDPConnect: action.RouteOptionsOptions.UDPConnect, UDPConnect: action.RouteOptionsOptions.UDPConnect,
UDPTimeout: time.Duration(action.RouteOptionsOptions.UDPTimeout),
}, nil }, nil
case C.RuleActionTypeDirect: case C.RuleActionTypeDirect:
directDialer, err := dialer.New(ctx, option.DialerOptions(action.DirectOptions)) directDialer, err := dialer.New(ctx, option.DialerOptions(action.DirectOptions))
@@ -152,6 +153,7 @@ type RuleActionRouteOptions struct {
FallbackDelay time.Duration FallbackDelay time.Duration
UDPDisableDomainUnmapping bool UDPDisableDomainUnmapping bool
UDPConnect bool UDPConnect bool
UDPTimeout time.Duration
} }
func (r *RuleActionRouteOptions) Type() string { func (r *RuleActionRouteOptions) Type() string {
@@ -71,6 +71,7 @@ func (w *systemDevice) Start() error {
Inet6RouteAddress: common.Filter(w.options.AllowedAddress, func(it netip.Prefix) bool { return it.Addr().Is6() }), Inet6RouteAddress: common.Filter(w.options.AllowedAddress, func(it netip.Prefix) bool { return it.Addr().Is6() }),
InterfaceMonitor: networkManager.InterfaceMonitor(), InterfaceMonitor: networkManager.InterfaceMonitor(),
InterfaceFinder: networkManager.InterfaceFinder(), InterfaceFinder: networkManager.InterfaceFinder(),
Logger: w.options.Logger,
} }
// works with Linux, macOS with IFSCOPE routes, not tested on Windows // works with Linux, macOS with IFSCOPE routes, not tested on Windows
if runtime.GOOS == "darwin" { if runtime.GOOS == "darwin" {
+3 -3
View File
@@ -9,9 +9,9 @@ PKG_RELEASE:=1
PKG_SOURCE_PROTO:=git PKG_SOURCE_PROTO:=git
PKG_SOURCE_URL:=https://gn.googlesource.com/gn.git PKG_SOURCE_URL:=https://gn.googlesource.com/gn.git
PKG_SOURCE_DATE:=2024-10-14 PKGOURCE_DATE:=2024-11-22
PKG_SOURCE_VERSION:=feafd1012a32c05ec6095f69ddc3850afb621f3a PKG_SOURCE_VERSION:=468c6128db7fabe32a29d4753460ef53594406fc
PKG_MIRROR_HASH:=c5e7d8357105b8b6708772490d6c2a7670c9298472efc203e8e409f229de8fb7 PKG_MIRROR_HASH:=22b5814a82742e8a39835c26390550a22ecbac5f2d853216057ab3fb4e377fd9
PKG_LICENSE:=BSD 3-Clause PKG_LICENSE:=BSD 3-Clause
PKG_LICENSE_FILES:=LICENSE PKG_LICENSE_FILES:=LICENSE
+2 -2
View File
@@ -3,7 +3,7 @@
#ifndef OUT_LAST_COMMIT_POSITION_H_ #ifndef OUT_LAST_COMMIT_POSITION_H_
#define OUT_LAST_COMMIT_POSITION_H_ #define OUT_LAST_COMMIT_POSITION_H_
#define LAST_COMMIT_POSITION_NUM 2202 #define LAST_COMMIT_POSITION_NUM 2205
#define LAST_COMMIT_POSITION "2202 (feafd1012a32)" #define LAST_COMMIT_POSITION "2205 (468c6128db7f)"
#endif // OUT_LAST_COMMIT_POSITION_H_ #endif // OUT_LAST_COMMIT_POSITION_H_
@@ -378,10 +378,10 @@ return view.extend({
o = s.taboption('external_control', form.Value, 'ui_url', '*' + ' ' + _('UI Url')); o = s.taboption('external_control', form.Value, 'ui_url', '*' + ' ' + _('UI Url'));
o.rmempty = false; o.rmempty = false;
o.value('https://mirror.ghproxy.com/https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip', 'MetaCubeXD'); o.value('https://ghp.ci/https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip', 'MetaCubeXD');
o.value('https://mirror.ghproxy.com/https://github.com/MetaCubeX/Yacd-meta/archive/refs/heads/gh-pages.zip', 'YACD'); o.value('https://ghp.ci/https://github.com/MetaCubeX/Yacd-meta/archive/refs/heads/gh-pages.zip', 'YACD');
o.value('https://mirror.ghproxy.com/https://github.com/MetaCubeX/Razord-meta/archive/refs/heads/gh-pages.zip', 'Razord'); o.value('https://ghp.ci/https://github.com/MetaCubeX/Razord-meta/archive/refs/heads/gh-pages.zip', 'Razord');
o.value('https://mirror.ghproxy.com/https://github.com/Zephyruso/sing-box-dashboard/archive/refs/heads/gh-pages.zip', 'SD'); o.value('https://ghp.ci/https://github.com/Zephyruso/zashboard/archive/refs/heads/gh-pages.zip', 'Zashboard');
o = s.taboption('external_control', form.Value, 'api_port', '*' + ' ' + _('API Port')); o = s.taboption('external_control', form.Value, 'api_port', '*' + ' ' + _('API Port'));
o.datatype = 'port'; o.datatype = 'port';
@@ -98,9 +98,50 @@ if not fs.access(CACHE_PATH) then
fs.mkdir(CACHE_PATH) fs.mkdir(CACHE_PATH)
end end
local LOCAL_EXTEND_ARG = ""
if LOCAL_GROUP == "nil" then if LOCAL_GROUP == "nil" then
LOCAL_GROUP = nil LOCAL_GROUP = nil
log(" * 注意:国内分组名未设置,可能会导致 DNS 解析异常") log(" * 注意:国内分组名未设置,可能会导致 DNS 分流错误")
else
--从smartdns配置中读取参数
local custom_conf_path = "/etc/smartdns/custom.conf"
local options = {
{key = "dualstack_ip_selection", config_key = "dualstack-ip-selection", yes_no = true, arg_yes = "-d yes", arg_no = "-d no", default = "yes"},
{key = "speed_check_mode", config_key = "speed-check-mode", prefix = "-c ", default = "ping,tcp:80,tcp:443"},
{key = "serve_expired", config_key = "serve-expired", yes_no = true, arg_yes = "", arg_no = "-no-serve-expired", default = "yes"},
{key = "response_mode", config_key = "response-mode", prefix = "-r ", default = "first-ping"},
{key = "rr_ttl", config_key = "rr-ttl", prefix = "-rr-ttl "},
{key = "rr_ttl_min", config_key = "rr-ttl-min", prefix = "-rr-ttl-min "},
{key = "rr_ttl_max", config_key = "rr-ttl-max", prefix = "-rr-ttl-max "}
}
-- 从 custom.conf 中读取值,以最后出现的值为准
local custom_config = {}
local f_in = io.open(custom_conf_path, "r")
if f_in then
for line in f_in:lines() do
line = line:match("^%s*(.-)%s*$")
if line ~= "" and not line:match("^#") then
local param, value = line:match("^(%S+)%s+(%S+)$")
if param and value then custom_config[param] = value end
end
end
f_in:close()
end
-- 从 smartdns 配置中读取值,优先级以 custom.conf 为准
for _, opt in ipairs(options) do
local val = custom_config[opt.config_key] or uci:get("smartdns", "@smartdns[0]", opt.key) or opt.default
if val == "yes" then val = "1" elseif val == "no" then val = "0" end
if opt.yes_no then
local arg = (val == "1" and opt.arg_yes or opt.arg_no)
if arg and arg ~= "" then
LOCAL_EXTEND_ARG = LOCAL_EXTEND_ARG .. (LOCAL_EXTEND_ARG ~= "" and " " or "") .. arg
end
else
if val and (not opt.value or (opt.invert and val ~= opt.value) or (not opt.invert and val == opt.value)) then
LOCAL_EXTEND_ARG = LOCAL_EXTEND_ARG .. (LOCAL_EXTEND_ARG ~= "" and " " or "") .. (opt.prefix or "") .. (opt.arg or val)
end
end
end
end end
if not REMOTE_GROUP or REMOTE_GROUP == "nil" then if not REMOTE_GROUP or REMOTE_GROUP == "nil" then
@@ -167,6 +208,8 @@ if DEFAULT_DNS_GROUP then
if NO_PROXY_IPV6 == "1" and only_global == 1 and uci:get(appname, TCP_NODE, "protocol") ~= "_shunt" then if NO_PROXY_IPV6 == "1" and only_global == 1 and uci:get(appname, TCP_NODE, "protocol") ~= "_shunt" then
domain_rules_str = domain_rules_str .. " -address #6" domain_rules_str = domain_rules_str .. " -address #6"
end end
elseif DEFAULT_DNS_GROUP == LOCAL_GROUP then
domain_rules_str = domain_rules_str .. (LOCAL_EXTEND_ARG ~= "" and " " .. LOCAL_EXTEND_ARG or "")
end end
table.insert(config_lines, domain_rules_str) table.insert(config_lines, domain_rules_str)
end end
@@ -226,6 +269,7 @@ if is_file_nonzero(file_vpslist) then
} }
local domain_rules_str = string.format('domain-rules /domain-set:%s/ %s', domain_set_name, LOCAL_GROUP and "-nameserver " .. LOCAL_GROUP or "") local domain_rules_str = string.format('domain-rules /domain-set:%s/ %s', domain_set_name, LOCAL_GROUP and "-nameserver " .. LOCAL_GROUP or "")
domain_rules_str = domain_rules_str .. " " .. set_type .. " #4:" .. setflag .. "passwall_vpslist,#6:" .. setflag .. "passwall_vpslist6" domain_rules_str = domain_rules_str .. " " .. set_type .. " #4:" .. setflag .. "passwall_vpslist,#6:" .. setflag .. "passwall_vpslist6"
domain_rules_str = domain_rules_str .. (LOCAL_EXTEND_ARG ~= "" and " " .. LOCAL_EXTEND_ARG or "")
table.insert(tmp_lines, domain_rules_str) table.insert(tmp_lines, domain_rules_str)
insert_array_after(config_lines, tmp_lines, "#--8") insert_array_after(config_lines, tmp_lines, "#--8")
log(string.format(" - 节点列表中的域名(vpslist)使用分组:%s", LOCAL_GROUP or "默认")) log(string.format(" - 节点列表中的域名(vpslist)使用分组:%s", LOCAL_GROUP or "默认"))
@@ -256,6 +300,7 @@ if USE_DIRECT_LIST == "1" and is_file_nonzero(file_direct_host) then
} }
local domain_rules_str = string.format('domain-rules /domain-set:%s/ %s', domain_set_name, LOCAL_GROUP and "-nameserver " .. LOCAL_GROUP or "") local domain_rules_str = string.format('domain-rules /domain-set:%s/ %s', domain_set_name, LOCAL_GROUP and "-nameserver " .. LOCAL_GROUP or "")
domain_rules_str = domain_rules_str .. " " .. set_type .. " #4:" .. setflag .. "passwall_whitelist,#6:" .. setflag .. "passwall_whitelist6" domain_rules_str = domain_rules_str .. " " .. set_type .. " #4:" .. setflag .. "passwall_whitelist,#6:" .. setflag .. "passwall_whitelist6"
domain_rules_str = domain_rules_str .. (LOCAL_EXTEND_ARG ~= "" and " " .. LOCAL_EXTEND_ARG or "")
table.insert(tmp_lines, domain_rules_str) table.insert(tmp_lines, domain_rules_str)
insert_array_after(config_lines, tmp_lines, "#--6") insert_array_after(config_lines, tmp_lines, "#--6")
log(string.format(" - 域名白名单(whitelist)使用分组:%s", LOCAL_GROUP or "默认")) log(string.format(" - 域名白名单(whitelist)使用分组:%s", LOCAL_GROUP or "默认"))
@@ -328,6 +373,7 @@ if CHN_LIST ~= "0" and is_file_nonzero(RULES_PATH .. "/chnlist") then
if CHN_LIST == "direct" then if CHN_LIST == "direct" then
local domain_rules_str = string.format('domain-rules /domain-set:%s/ %s', domain_set_name, LOCAL_GROUP and "-nameserver " .. LOCAL_GROUP or "") local domain_rules_str = string.format('domain-rules /domain-set:%s/ %s', domain_set_name, LOCAL_GROUP and "-nameserver " .. LOCAL_GROUP or "")
domain_rules_str = domain_rules_str .. " " .. set_type .. " #4:" .. setflag .. "passwall_chnroute,#6:" .. setflag .. "passwall_chnroute6" domain_rules_str = domain_rules_str .. " " .. set_type .. " #4:" .. setflag .. "passwall_chnroute,#6:" .. setflag .. "passwall_chnroute6"
domain_rules_str = domain_rules_str .. (LOCAL_EXTEND_ARG ~= "" and " " .. LOCAL_EXTEND_ARG or "")
table.insert(tmp_lines, domain_rules_str) table.insert(tmp_lines, domain_rules_str)
insert_array_after(config_lines, tmp_lines, "#--2") insert_array_after(config_lines, tmp_lines, "#--2")
log(string.format(" - 中国域名表(chnroute)使用分组:%s", LOCAL_GROUP or "默认")) log(string.format(" - 中国域名表(chnroute)使用分组:%s", LOCAL_GROUP or "默认"))
@@ -419,6 +465,7 @@ if uci:get(appname, TCP_NODE, "protocol") == "_shunt" then
} }
local domain_rules_str = string.format('domain-rules /domain-set:%s/ %s', domain_set_name, LOCAL_GROUP and "-nameserver " .. LOCAL_GROUP or "") local domain_rules_str = string.format('domain-rules /domain-set:%s/ %s', domain_set_name, LOCAL_GROUP and "-nameserver " .. LOCAL_GROUP or "")
domain_rules_str = domain_rules_str .. " " .. set_type .. " #4:" .. setflag .. "passwall_whitelist,#6:" .. setflag .. "passwall_whitelist6" domain_rules_str = domain_rules_str .. " " .. set_type .. " #4:" .. setflag .. "passwall_whitelist,#6:" .. setflag .. "passwall_whitelist6"
domain_rules_str = domain_rules_str .. (LOCAL_EXTEND_ARG ~= "" and " " .. LOCAL_EXTEND_ARG or "")
table.insert(tmp_lines, domain_rules_str) table.insert(tmp_lines, domain_rules_str)
insert_array_after(config_lines, tmp_lines, "#--3") insert_array_after(config_lines, tmp_lines, "#--3")
end end
+5 -5
View File
@@ -57,7 +57,7 @@ config mixin 'mixin'
option 'tcp_keep_alive_idle' '600' option 'tcp_keep_alive_idle' '600'
option 'tcp_keep_alive_interval' '15' option 'tcp_keep_alive_interval' '15'
option 'ui_name' 'metacubexd' option 'ui_name' 'metacubexd'
option 'ui_url' 'https://mirror.ghproxy.com/https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip' option 'ui_url' 'https://ghp.ci/https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip'
option 'api_port' '9090' option 'api_port' '9090'
option 'api_secret' '' option 'api_secret' ''
option 'selection_cache' '1' option 'selection_cache' '1'
@@ -90,10 +90,10 @@ config mixin 'mixin'
option 'dns_nameserver_policy' '0' option 'dns_nameserver_policy' '0'
option 'geoip_format' 'dat' option 'geoip_format' 'dat'
option 'geodata_loader' 'memconservative' option 'geodata_loader' 'memconservative'
option 'geosite_url' 'https://mirror.ghproxy.com/https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.dat' option 'geosite_url' 'https://ghp.ci/https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.dat'
option 'geoip_mmdb_url' 'https://mirror.ghproxy.com/https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip-lite.metadb' option 'geoip_mmdb_url' 'https://ghp.ci/https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip-lite.metadb'
option 'geoip_dat_url' 'https://mirror.ghproxy.com/https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip-lite.dat' option 'geoip_dat_url' 'https://ghp.ci/https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip-lite.dat'
option 'geoip_asn_url' 'https://mirror.ghproxy.com/https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/GeoLite2-ASN.mmdb' option 'geoip_asn_url' 'https://ghp.ci/https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/GeoLite2-ASN.mmdb'
option 'geox_auto_update' '0' option 'geox_auto_update' '0'
option 'geox_update_interval' '24' option 'geox_update_interval' '24'
option 'mixin_file_content' '0' option 'mixin_file_content' '0'
+19 -1
View File
@@ -623,11 +623,23 @@ update_subscription() {
if [ -z "$subscription_section" ]; then if [ -z "$subscription_section" ]; then
return return
fi fi
# load config
config_load mihomo config_load mihomo
# get subscription config
local subscription_name subscription_url subscription_user_agent local subscription_name subscription_url subscription_user_agent
config_get subscription_name "$subscription_section" "name" config_get subscription_name "$subscription_section" "name"
config_get subscription_url "$subscription_section" "url" config_get subscription_url "$subscription_section" "url"
config_get subscription_user_agent "$subscription_section" "user_agent" config_get subscription_user_agent "$subscription_section" "user_agent"
# reset subscription info
uci_remove "mihomo" "$subscription_section" "expire"
uci_remove "mihomo" "$subscription_section" "upload"
uci_remove "mihomo" "$subscription_section" "download"
uci_remove "mihomo" "$subscription_section" "total"
uci_remove "mihomo" "$subscription_section" "used"
uci_remove "mihomo" "$subscription_section" "avaliable"
uci_remove "mihomo" "$subscription_section" "update"
uci_remove "mihomo" "$subscription_section" "success"
# update subscription
log "Update Subscription: $subscription_name." log "Update Subscription: $subscription_name."
local subscription_header_tmpfile; subscription_header_tmpfile="/tmp/$subscription_section.header" local subscription_header_tmpfile; subscription_header_tmpfile="/tmp/$subscription_section.header"
local subscription_tmpfile; subscription_tmpfile="/tmp/$subscription_section.yaml" local subscription_tmpfile; subscription_tmpfile="/tmp/$subscription_section.yaml"
@@ -645,6 +657,7 @@ update_subscription() {
subscription_avaliable=$((subscription_total - subscription_upload - subscription_download)) subscription_avaliable=$((subscription_total - subscription_upload - subscription_download))
fi fi
fi fi
# update subscription info
if [ -n "$subscription_expire" ]; then if [ -n "$subscription_expire" ]; then
uci_set "mihomo" "$subscription_section" "expire" "$(date "+%Y-%m-%d %H:%M:%S" -d @$subscription_expire)" uci_set "mihomo" "$subscription_section" "expire" "$(date "+%Y-%m-%d %H:%M:%S" -d @$subscription_expire)"
fi fi
@@ -664,12 +677,17 @@ update_subscription() {
uci_set "mihomo" "$subscription_section" "avaliable" "$(format_filesize $subscription_avaliable)" uci_set "mihomo" "$subscription_section" "avaliable" "$(format_filesize $subscription_avaliable)"
fi fi
uci_set "mihomo" "$subscription_section" "update" "$(date "+%Y-%m-%d %H:%M:%S")" uci_set "mihomo" "$subscription_section" "update" "$(date "+%Y-%m-%d %H:%M:%S")"
uci_commit "mihomo" uci_set "mihomo" "$subscription_section" "success" "1"
# update subscription file
rm -f "$subscription_header_tmpfile" rm -f "$subscription_header_tmpfile"
mv -f "$subscription_tmpfile" "$subscription_file" mv -f "$subscription_tmpfile" "$subscription_file"
else else
log "Subscription update failed." log "Subscription update failed."
# update subscription info
uci_set "mihomo" "$subscription_section" "success" "0"
# remove tmpfile
rm -f "$subscription_header_tmpfile" rm -f "$subscription_header_tmpfile"
rm -f "$subscription_tmpfile" rm -f "$subscription_tmpfile"
fi fi
uci_commit "mihomo"
} }
+6 -5
View File
@@ -15,11 +15,12 @@ jobs:
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Set up Node.js - name: Set up Node.js
uses: actions/setup-node@v4 shell: bash
with: run: |
node-version: lts/* eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
cache: 'yarn' brew install node@20
cache-dependency-path: gui/yarn.lock echo "PATH=\"$(brew --prefix)/opt/node@20/bin:$PATH\"" >> $GITHUB_ENV
echo "PATH=\"$(brew --prefix)/opt/node@20/bin:$PATH\"" >> ~/.bash_profile
- name: Install Dependencies - name: Install Dependencies
run: | run: |
sudo apt-get update -y && sudo apt-get install -y gzip sudo apt-get update -y && sudo apt-get install -y gzip
+6 -5
View File
@@ -15,11 +15,12 @@ jobs:
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Set up Node.js - name: Set up Node.js
uses: actions/setup-node@v4 shell: bash
with: run: |
node-version: lts/* eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
cache: 'yarn' brew install node@20
cache-dependency-path: gui/yarn.lock echo "PATH=\"$(brew --prefix)/opt/node@20/bin:$PATH\"" >> $GITHUB_ENV
echo "PATH=\"$(brew --prefix)/opt/node@20/bin:$PATH\"" >> ~/.bash_profile
- name: Install Dependencies - name: Install Dependencies
run: | run: |
sudo apt-get update -y && sudo apt-get install -y gzip sudo apt-get update -y && sudo apt-get install -y gzip
+8 -18
View File
@@ -1,12 +1,10 @@
name: Build & Release v2rayA name: Build & Release v2rayA
on: on:
workflow_dispatch: workflow_dispatch:
inputs: inputs:
tag: tag:
type: string type: string
required: true required: true
jobs: jobs:
Build_v2rayA_Web: Build_v2rayA_Web:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
@@ -15,11 +13,12 @@ jobs:
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Set up Node.js - name: Set up Node.js
uses: actions/setup-node@v4 shell: bash
with: run: |
node-version: lts/* eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
cache: 'yarn' brew install node@20
cache-dependency-path: gui/yarn.lock echo "PATH=\"$(brew --prefix)/opt/node@20/bin:$PATH\"" >> $GITHUB_ENV
echo "PATH=\"$(brew --prefix)/opt/node@20/bin:$PATH\"" >> ~/.bash_profile
- name: Install Dependencies - name: Install Dependencies
run: | run: |
sudo apt-get update -y && sudo apt-get install -y gzip sudo apt-get update -y && sudo apt-get install -y gzip
@@ -123,13 +122,11 @@ jobs:
go build -tags "with_gvisor" -o ../v2raya_binaries/v2raya_${filename}_${env:VERSION} -ldflags="-X github.com/v2rayA/v2rayA/conf.Version=${env:VERSION} -s -w" -trimpath go build -tags "with_gvisor" -o ../v2raya_binaries/v2raya_${filename}_${env:VERSION} -ldflags="-X github.com/v2rayA/v2rayA/conf.Version=${env:VERSION} -s -w" -trimpath
Set-Location -Path .. Set-Location -Path ..
} }
- name: Upload Artifact - name: Upload Artifact
uses: nanoufo/action-upload-artifacts-and-release-assets@v2 uses: nanoufo/action-upload-artifacts-and-release-assets@v2
with: with:
path: | path: |
v2raya_binaries/* v2raya_binaries/*
Build_Windows_Installers: Build_Windows_Installers:
runs-on: windows-latest runs-on: windows-latest
needs: [Build_v2rayA_Binaries] needs: [Build_v2rayA_Binaries]
@@ -399,7 +396,6 @@ jobs:
--data '{"purge_everything":true}' --data '{"purge_everything":true}'
Build_v2ray_Debian_Packages: Build_v2ray_Debian_Packages:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- name: Install Tools - name: Install Tools
run: | run: |
@@ -543,7 +539,6 @@ jobs:
run: | run: |
echo "V2RAY_VERSION=$V2RAY_VERSION" >> ./v2ray_packages_version.txt echo "V2RAY_VERSION=$V2RAY_VERSION" >> ./v2ray_packages_version.txt
echo "XRAY_VERSION=$XRAY_VERSION" >> ./xray_packages_version.txt echo "XRAY_VERSION=$XRAY_VERSION" >> ./xray_packages_version.txt
- name: Upload Artifact - name: Upload Artifact
uses: nanoufo/action-upload-artifacts-and-release-assets@v2 uses: nanoufo/action-upload-artifacts-and-release-assets@v2
with: with:
@@ -555,7 +550,6 @@ jobs:
Build_APT_Repository_and_AUR: Build_APT_Repository_and_AUR:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
needs: [Build_v2rayA_Binaries, Build_Linux_Packages, Build_v2ray_Debian_Packages] needs: [Build_v2rayA_Binaries, Build_Linux_Packages, Build_v2ray_Debian_Packages]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
@@ -638,7 +632,6 @@ jobs:
Release_to_Homebrew: Release_to_Homebrew:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
needs: [Build_v2rayA_Binaries] needs: [Build_v2rayA_Binaries]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
@@ -693,7 +686,6 @@ jobs:
Release_v2rayA_to_Docker: Release_v2rayA_to_Docker:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
needs: [GitHub_Release] needs: [GitHub_Release]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
@@ -733,7 +725,7 @@ jobs:
context: . context: .
builder: ${{ steps.buildx.outputs.name }} builder: ${{ steps.buildx.outputs.name }}
file: install/docker/Dockerfile.Action file: install/docker/Dockerfile.Action
platforms: linux/arm,linux/arm64,linux/amd64 platforms: linux/arm,linux/arm64,linux/amd64,linux/riscv64
push: true push: true
tags: | tags: |
${{ steps.prep.outputs.image }}:${{ steps.prep.outputs.tag }} ${{ steps.prep.outputs.image }}:${{ steps.prep.outputs.tag }}
@@ -745,7 +737,6 @@ jobs:
Release_v2rayA_GUI_to_Docker: Release_v2rayA_GUI_to_Docker:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
needs: [Build_v2rayA_Web] needs: [Build_v2rayA_Web]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
@@ -788,7 +779,7 @@ jobs:
context: . context: .
builder: ${{ steps.buildx.outputs.name }} builder: ${{ steps.buildx.outputs.name }}
file: install/docker/Dockerfile.GUI.Action file: install/docker/Dockerfile.GUI.Action
platforms: linux/arm,linux/arm64,linux/amd64 platforms: linux/arm,linux/arm64,linux/amd64,linux/riscv64
push: true push: true
tags: | tags: |
mzz2017/v2raya-gui:latest mzz2017/v2raya-gui:latest
@@ -798,7 +789,6 @@ jobs:
Submit_to_Microsoft_winget: Submit_to_Microsoft_winget:
runs-on: windows-latest runs-on: windows-latest
needs: [GitHub_Release] needs: [GitHub_Release]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
+7 -5
View File
@@ -4,6 +4,7 @@ on:
push: push:
branches: branches:
- main - main
- dependabot/*
paths: paths:
- "**/*.go" - "**/*.go"
- "go.mod" - "go.mod"
@@ -19,11 +20,12 @@ jobs:
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Set up Node.js - name: Set up Node.js
uses: actions/setup-node@v4 shell: bash
with: run: |
node-version: lts/* eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
cache: 'yarn' brew install node@20
cache-dependency-path: gui/yarn.lock echo "PATH=\"$(brew --prefix)/opt/node@20/bin:$PATH\"" >> $GITHUB_ENV
echo "PATH=\"$(brew --prefix)/opt/node@20/bin:$PATH\"" >> ~/.bash_profile
- name: Install Dependencies - name: Install Dependencies
run: | run: |
sudo apt-get update -y && sudo apt-get install -y gzip sudo apt-get update -y && sudo apt-get install -y gzip
+4 -3
View File
@@ -1,4 +1,5 @@
FROM nginx:stable-alpine FROM busybox:latest AS builder
WORKDIR /build WORKDIR /
COPY ./web /usr/share/nginx/html COPY ./web /usr/share/v2raya-web
ENTRYPOINT ["/bin/httpd", "-f", "-h", "/usr/share/v2raya-web", "-p", "80"]
EXPOSE 80 EXPOSE 80
+4
View File
@@ -14,6 +14,10 @@ case "$(arch)" in
v2ray_arch="arm64-v8a" v2ray_arch="arm64-v8a"
v2raya_arch="arm64" v2raya_arch="arm64"
;; ;;
riscv64)
v2ray_arch="riscv64"
v2raya_arch="riscv64"
;;
*) *)
;; ;;
esac esac
+1 -1
View File
@@ -4,7 +4,7 @@
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<Version>7.2.0</Version> <Version>7.2.1</Version>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
+2 -2
View File
@@ -12,8 +12,8 @@ android {
applicationId = "com.v2ray.ang" applicationId = "com.v2ray.ang"
minSdk = 21 minSdk = 21
targetSdk = 35 targetSdk = 35
versionCode = 615 versionCode = 616
versionName = "1.9.19" versionName = "1.9.20"
multiDexEnabled = true multiDexEnabled = true
splits { splits {
+1 -1
View File
@@ -8,7 +8,7 @@
#### Important changes #### Important changes
- **Login with OAuth is no longer supported for YouTube** - **Login with OAuth is no longer supported for YouTube**
Due to a change made by the site, yt-dlp is longer able to support OAuth login for YouTube. [Read more](https://github.com/yt-dlp/yt-dlp/issues/11462#issuecomment-2471703090) Due to a change made by the site, yt-dlp is no longer able to support OAuth login for YouTube. [Read more](https://github.com/yt-dlp/yt-dlp/issues/11462#issuecomment-2471703090)
#### Core changes #### Core changes
- [Catch broken Cryptodome installations](https://github.com/yt-dlp/yt-dlp/commit/b83ca24eb72e1e558b0185bd73975586c0bc0546) ([#11486](https://github.com/yt-dlp/yt-dlp/issues/11486)) by [seproDev](https://github.com/seproDev) - [Catch broken Cryptodome installations](https://github.com/yt-dlp/yt-dlp/commit/b83ca24eb72e1e558b0185bd73975586c0bc0546) ([#11486](https://github.com/yt-dlp/yt-dlp/issues/11486)) by [seproDev](https://github.com/seproDev)
+1
View File
@@ -1294,6 +1294,7 @@ The available fields are:
- `playlist_uploader_id` (string): Nickname or id of the playlist uploader - `playlist_uploader_id` (string): Nickname or id of the playlist uploader
- `playlist_channel` (string): Display name of the channel that uploaded the playlist - `playlist_channel` (string): Display name of the channel that uploaded the playlist
- `playlist_channel_id` (string): Identifier of the channel that uploaded the playlist - `playlist_channel_id` (string): Identifier of the channel that uploaded the playlist
- `playlist_webpage_url` (string): URL of the playlist webpage
- `webpage_url` (string): A URL to the video webpage which, if given to yt-dlp, should yield the same result again - `webpage_url` (string): A URL to the video webpage which, if given to yt-dlp, should yield the same result again
- `webpage_url_basename` (string): The basename of the webpage URL - `webpage_url_basename` (string): The basename of the webpage URL
- `webpage_url_domain` (string): The domain of the webpage URL - `webpage_url_domain` (string): The domain of the webpage URL
+1 -1
View File
@@ -238,6 +238,6 @@
{ {
"action": "add", "action": "add",
"when": "52c0ffe40ad6e8404d93296f575007b05b04c686", "when": "52c0ffe40ad6e8404d93296f575007b05b04c686",
"short": "[priority] **Login with OAuth is no longer supported for YouTube**\nDue to a change made by the site, yt-dlp is longer able to support OAuth login for YouTube. [Read more](https://github.com/yt-dlp/yt-dlp/issues/11462#issuecomment-2471703090)" "short": "[priority] **Login with OAuth is no longer supported for YouTube**\nDue to a change made by the site, yt-dlp is no longer able to support OAuth login for YouTube. [Read more](https://github.com/yt-dlp/yt-dlp/issues/11462#issuecomment-2471703090)"
} }
] ]
+1 -1
View File
@@ -52,7 +52,7 @@ default = [
"pycryptodomex", "pycryptodomex",
"requests>=2.32.2,<3", "requests>=2.32.2,<3",
"urllib3>=1.26.17,<3", "urllib3>=1.26.17,<3",
"websockets>=13.0,<14", "websockets>=13.0",
] ]
curl-cffi = [ curl-cffi = [
"curl-cffi==0.5.10; os_name=='nt' and implementation_name=='cpython'", "curl-cffi==0.5.10; os_name=='nt' and implementation_name=='cpython'",
+3 -1
View File
@@ -216,7 +216,9 @@ class SocksWebSocketTestRequestHandler(SocksTestRequestHandler):
protocol = websockets.ServerProtocol() protocol = websockets.ServerProtocol()
connection = websockets.sync.server.ServerConnection(socket=self.request, protocol=protocol, close_timeout=0) connection = websockets.sync.server.ServerConnection(socket=self.request, protocol=protocol, close_timeout=0)
connection.handshake() connection.handshake()
connection.send(json.dumps(self.socks_info)) for message in connection:
if message == 'socks_info':
connection.send(json.dumps(self.socks_info))
connection.close() connection.close()
+1
View File
@@ -1947,6 +1947,7 @@ class YoutubeDL:
'playlist_uploader_id': ie_result.get('uploader_id'), 'playlist_uploader_id': ie_result.get('uploader_id'),
'playlist_channel': ie_result.get('channel'), 'playlist_channel': ie_result.get('channel'),
'playlist_channel_id': ie_result.get('channel_id'), 'playlist_channel_id': ie_result.get('channel_id'),
'playlist_webpage_url': ie_result.get('webpage_url'),
**kwargs, **kwargs,
} }
if strict: if strict:
+21 -1
View File
@@ -261,6 +261,7 @@ class DailymotionIE(DailymotionBaseInfoExtractor):
'tags': [], 'tags': [],
'view_count': int, 'view_count': int,
'like_count': int, 'like_count': int,
'thumbnail': r're:https://\w+.dmcdn.net/v/WnEY61cmvMxt2Fi6d/x1080',
}, },
}, { }, {
# https://geo.dailymotion.com/player/xf7zn.html?playlist=x7wdsj # https://geo.dailymotion.com/player/xf7zn.html?playlist=x7wdsj
@@ -288,6 +289,25 @@ class DailymotionIE(DailymotionBaseInfoExtractor):
'description': 'À bord du « véloto », lalternative à la voiture pour la campagne', 'description': 'À bord du « véloto », lalternative à la voiture pour la campagne',
'tags': ['biclou', 'vélo', 'véloto', 'campagne', 'voiture', 'environnement', 'véhicules intermédiaires'], 'tags': ['biclou', 'vélo', 'véloto', 'campagne', 'voiture', 'environnement', 'véhicules intermédiaires'],
}, },
}, {
# https://geo.dailymotion.com/player/xry80.html?video=x8vu47w
'url': 'https://www.metatube.com/en/videos/546765/This-frogs-decorates-Christmas-tree/',
'info_dict': {
'id': 'x8vu47w',
'ext': 'mp4',
'like_count': int,
'uploader': 'Metatube',
'thumbnail': r're:https://\w+.dmcdn.net/v/W1G_S1coGSFTfkTeR/x1080',
'upload_date': '20240326',
'view_count': int,
'timestamp': 1711496732,
'age_limit': 0,
'uploader_id': 'x2xpy74',
'title': 'Está lindas ranitas ponen su arbolito',
'duration': 28,
'description': 'Que lindura',
'tags': [],
},
}] }]
_GEO_BYPASS = False _GEO_BYPASS = False
_COMMON_MEDIA_FIELDS = '''description _COMMON_MEDIA_FIELDS = '''description
@@ -302,7 +322,7 @@ class DailymotionIE(DailymotionBaseInfoExtractor):
yield from super()._extract_embed_urls(url, webpage) yield from super()._extract_embed_urls(url, webpage)
for mobj in re.finditer( for mobj in re.finditer(
r'(?s)DM\.player\([^,]+,\s*{.*?video[\'"]?\s*:\s*["\']?(?P<id>[0-9a-zA-Z]+).+?}\s*\);', webpage): r'(?s)DM\.player\([^,]+,\s*{.*?video[\'"]?\s*:\s*["\']?(?P<id>[0-9a-zA-Z]+).+?}\s*\);', webpage):
yield from 'https://www.dailymotion.com/embed/video/' + mobj.group('id') yield 'https://www.dailymotion.com/embed/video/' + mobj.group('id')
for mobj in re.finditer( for mobj in re.finditer(
r'(?s)<script [^>]*\bsrc=(["\'])(?:https?:)?//[\w-]+\.dailymotion\.com/player/(?:(?!\1).)+\1[^>]*>', webpage): r'(?s)<script [^>]*\bsrc=(["\'])(?:https?:)?//[\w-]+\.dailymotion\.com/player/(?:(?!\1).)+\1[^>]*>', webpage):
attrs = extract_attributes(mobj.group(0)) attrs = extract_attributes(mobj.group(0))
+4 -1
View File
@@ -50,7 +50,7 @@ class FacebookIE(InfoExtractor):
[^/]+/videos/(?:[^/]+/)?| [^/]+/videos/(?:[^/]+/)?|
[^/]+/posts/| [^/]+/posts/|
events/(?:[^/]+/)?| events/(?:[^/]+/)?|
groups/[^/]+/(?:permalink|posts)/| groups/[^/]+/(?:permalink|posts)/(?:[\da-f]+/)?|
watchparty/ watchparty/
)| )|
facebook: facebook:
@@ -410,6 +410,9 @@ class FacebookIE(InfoExtractor):
'uploader': 'Comitato Liberi Pensatori', 'uploader': 'Comitato Liberi Pensatori',
'uploader_id': '100065709540881', 'uploader_id': '100065709540881',
}, },
}, {
'url': 'https://www.facebook.com/groups/1513990329015294/posts/d41d8cd9/2013209885760000/?app=fbl',
'only_matching': True,
}] }]
_SUPPORTED_PAGLETS_REGEX = r'(?:pagelet_group_mall|permalink_video_pagelet|hyperfeed_story_id_[0-9a-f]+)' _SUPPORTED_PAGLETS_REGEX = r'(?:pagelet_group_mall|permalink_video_pagelet|hyperfeed_story_id_[0-9a-f]+)'
_api_config = { _api_config = {
+11 -14
View File
@@ -28,24 +28,21 @@ class StripchatIE(InfoExtractor):
def _real_extract(self, url): def _real_extract(self, url):
video_id = self._match_id(url) video_id = self._match_id(url)
webpage = self._download_webpage(url, video_id, headers=self.geo_verification_headers()) webpage = self._download_webpage(url, video_id, headers=self.geo_verification_headers())
data = self._search_json(
r'<script\b[^>]*>\s*window\.__PRELOADED_STATE__\s*=',
webpage, 'data', video_id, transform_source=lowercase_escape)
data = self._parse_json( if traverse_obj(data, ('viewCam', 'show', {dict})):
self._search_regex( raise ExtractorError('Model is in a private show', expected=True)
r'<script\b[^>]*>\s*window\.__PRELOADED_STATE__\s*=(?P<value>.*?)<\/script>', if not traverse_obj(data, ('viewCam', 'model', 'isLive', {bool})):
webpage, 'data', default='{}', group='value'),
video_id, transform_source=lowercase_escape, fatal=False)
if not data:
raise ExtractorError('Unable to find configuration for stream.')
if traverse_obj(data, ('viewCam', 'show'), expected_type=dict):
raise ExtractorError('Model is in private show', expected=True)
elif not traverse_obj(data, ('viewCam', 'model', 'isLive'), expected_type=bool):
raise UserNotLive(video_id=video_id) raise UserNotLive(video_id=video_id)
model_id = traverse_obj(data, ('viewCam', 'model', 'id'), expected_type=int) model_id = data['viewCam']['model']['id']
formats = [] formats = []
for host in traverse_obj(data, ('config', 'data', ( # HLS hosts are currently found in .configV3.static.features.hlsFallback.fallbackDomains[]
# The rest of the path is for backwards compatibility and to guard against A/B testing
for host in traverse_obj(data, ((('config', 'data'), ('configV3', 'static')), (
(('features', 'featuresV2'), 'hlsFallback', 'fallbackDomains', ...), 'hlsStreamHost'))): (('features', 'featuresV2'), 'hlsFallback', 'fallbackDomains', ...), 'hlsStreamHost'))):
formats = self._extract_m3u8_formats( formats = self._extract_m3u8_formats(
f'https://edge-hls.{host}/hls/{model_id}/master/{model_id}_auto.m3u8', f'https://edge-hls.{host}/hls/{model_id}/master/{model_id}_auto.m3u8',
@@ -53,7 +50,7 @@ class StripchatIE(InfoExtractor):
if formats: if formats:
break break
if not formats: if not formats:
self.raise_no_formats('No active streams found', expected=True) self.raise_no_formats('Unable to extract stream host', video_id=video_id)
return { return {
'id': video_id, 'id': video_id,
+26 -14
View File
@@ -4986,6 +4986,10 @@ class YoutubeTabBaseInfoExtractor(YoutubeBaseInfoExtractor):
for item in grid_renderer['items']: for item in grid_renderer['items']:
if not isinstance(item, dict): if not isinstance(item, dict):
continue continue
if lockup_view_model := traverse_obj(item, ('lockupViewModel', {dict})):
if entry := self._extract_lockup_view_model(lockup_view_model):
yield entry
continue
renderer = self._extract_basic_item_renderer(item) renderer = self._extract_basic_item_renderer(item)
if not isinstance(renderer, dict): if not isinstance(renderer, dict):
continue continue
@@ -5084,10 +5088,30 @@ class YoutubeTabBaseInfoExtractor(YoutubeBaseInfoExtractor):
continue continue
yield self._extract_video(renderer) yield self._extract_video(renderer)
def _extract_lockup_view_model(self, view_model):
content_id = view_model.get('contentId')
if not content_id:
return
content_type = view_model.get('contentType')
if content_type not in ('LOCKUP_CONTENT_TYPE_PLAYLIST', 'LOCKUP_CONTENT_TYPE_PODCAST'):
self.report_warning(
f'Unsupported lockup view model content type "{content_type}"{bug_reports_message()}', only_once=True)
return
return self.url_result(
f'https://www.youtube.com/playlist?list={content_id}', ie=YoutubeTabIE, video_id=content_id,
title=traverse_obj(view_model, (
'metadata', 'lockupMetadataViewModel', 'title', 'content', {str})),
thumbnails=self._extract_thumbnails(view_model, (
'contentImage', 'collectionThumbnailViewModel', 'primaryThumbnail', 'thumbnailViewModel', 'image'), final_key='sources'))
def _rich_entries(self, rich_grid_renderer): def _rich_entries(self, rich_grid_renderer):
if lockup_view_model := traverse_obj(rich_grid_renderer, ('content', 'lockupViewModel', {dict})):
if entry := self._extract_lockup_view_model(lockup_view_model):
yield entry
return
renderer = traverse_obj( renderer = traverse_obj(
rich_grid_renderer, rich_grid_renderer,
('content', ('videoRenderer', 'reelItemRenderer', 'playlistRenderer', 'shortsLockupViewModel', 'lockupViewModel'), any)) or {} ('content', ('videoRenderer', 'reelItemRenderer', 'playlistRenderer', 'shortsLockupViewModel'), any)) or {}
video_id = renderer.get('videoId') video_id = renderer.get('videoId')
if video_id: if video_id:
yield self._extract_video(renderer) yield self._extract_video(renderer)
@@ -5114,18 +5138,6 @@ class YoutubeTabBaseInfoExtractor(YoutubeBaseInfoExtractor):
})), })),
thumbnails=self._extract_thumbnails(renderer, 'thumbnail', final_key='sources')) thumbnails=self._extract_thumbnails(renderer, 'thumbnail', final_key='sources'))
return return
# lockupViewModel extraction
content_id = renderer.get('contentId')
if content_id and renderer.get('contentType') == 'LOCKUP_CONTENT_TYPE_PODCAST':
yield self.url_result(
f'https://www.youtube.com/playlist?list={content_id}',
ie=YoutubeTabIE, video_id=content_id,
**traverse_obj(renderer, {
'title': ('metadata', 'lockupMetadataViewModel', 'title', 'content', {str}),
}),
thumbnails=self._extract_thumbnails(renderer, (
'contentImage', 'collectionThumbnailViewModel', 'primaryThumbnail', 'thumbnailViewModel', 'image'), final_key='sources'))
return
def _video_entry(self, video_renderer): def _video_entry(self, video_renderer):
video_id = video_renderer.get('videoId') video_id = video_renderer.get('videoId')
@@ -5794,7 +5806,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
'info_dict': { 'info_dict': {
'id': 'UCYO_jab_esuFRV4b17AJtAw', 'id': 'UCYO_jab_esuFRV4b17AJtAw',
'title': '3Blue1Brown - Playlists', 'title': '3Blue1Brown - Playlists',
'description': 'md5:4d1da95432004b7ba840ebc895b6b4c9', 'description': 'md5:602e3789e6a0cb7d9d352186b720e395',
'channel_url': 'https://www.youtube.com/channel/UCYO_jab_esuFRV4b17AJtAw', 'channel_url': 'https://www.youtube.com/channel/UCYO_jab_esuFRV4b17AJtAw',
'channel': '3Blue1Brown', 'channel': '3Blue1Brown',
'channel_id': 'UCYO_jab_esuFRV4b17AJtAw', 'channel_id': 'UCYO_jab_esuFRV4b17AJtAw',