mirror of
https://github.com/bolucat/Archive.git
synced 2026-04-22 16:07:49 +08:00
Update On Sun Nov 24 19:33:47 CET 2024
This commit is contained in:
@@ -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 Fri Nov 22 19:38:14 CET 2024
|
||||
Update On Sat Nov 23 19:33:03 CET 2024
|
||||
Update On Sun Nov 24 19:33:36 CET 2024
|
||||
|
||||
@@ -44,7 +44,7 @@ static partial class BBDownMuxer
|
||||
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 metaArg = new();
|
||||
@@ -73,7 +73,8 @@ static partial class BBDownMuxer
|
||||
metaArg.Append($":album=\"{title}\":title=\"{episodeId}\"");
|
||||
else
|
||||
metaArg.Append($":title=\"{title}\"");
|
||||
metaArg.Append($":comment=\"{desc}\"");
|
||||
metaArg.Append($":sdesc=\"{desc}\"");
|
||||
metaArg.Append($":comment=\"{url}\"");
|
||||
metaArg.Append($":artist=\"{author}\"");
|
||||
|
||||
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);
|
||||
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 != "")
|
||||
videoPath = "";
|
||||
@@ -104,10 +105,11 @@ static partial class BBDownMuxer
|
||||
desc = EscapeString(desc);
|
||||
title = EscapeString(title);
|
||||
episodeId = EscapeString(episodeId);
|
||||
var url = $"https://www.bilibili.com/video/{bvid}/";
|
||||
|
||||
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)))
|
||||
@@ -179,6 +181,7 @@ static partial class BBDownMuxer
|
||||
argsBuilder.Append(metaArg);
|
||||
if (!simplyMux) {
|
||||
argsBuilder.Append($"-metadata title=\"{(episodeId == "" ? title : episodeId)}\" ");
|
||||
argsBuilder.Append($"-metadata comment=\"{url}\" ");
|
||||
if (lang != "") argsBuilder.Append($"-metadata:s:a:0 language={lang} ");
|
||||
if (!string.IsNullOrWhiteSpace(desc)) argsBuilder.Append($"-metadata description=\"{desc}\" ");
|
||||
if (!string.IsNullOrEmpty(author)) argsBuilder.Append($"-metadata artist=\"{author}\" ");
|
||||
|
||||
@@ -244,7 +244,7 @@ partial class Program
|
||||
|
||||
Log("获取aid...");
|
||||
aidOri = await GetAvIdAsync(input);
|
||||
Log("获取aid结束: " + aidOri);
|
||||
Log($"获取aid结束: {aidOri}");
|
||||
|
||||
if (string.IsNullOrEmpty(aidOri))
|
||||
{
|
||||
@@ -287,6 +287,11 @@ partial class Program
|
||||
{
|
||||
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;
|
||||
if (!string.IsNullOrEmpty(mid))
|
||||
{
|
||||
@@ -664,7 +669,7 @@ partial class Program
|
||||
Log($"开始合并音视频{(subtitleInfo.Any() ? "和字幕" : "")}...");
|
||||
if (myOption.AudioOnly)
|
||||
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,
|
||||
title,
|
||||
p.ownerName ?? "",
|
||||
@@ -753,7 +758,7 @@ partial class Program
|
||||
Log($"开始混流视频{(subtitleInfo.Any() ? "和字幕" : "")}...");
|
||||
if (myOption.AudioOnly)
|
||||
savePath = savePath[..^4] + ".m4a";
|
||||
int code = BBDownMuxer.MuxAV(false, videoPath, "", audioMaterial, savePath,
|
||||
int code = BBDownMuxer.MuxAV(false, p.bvid, videoPath, "", audioMaterial, savePath,
|
||||
desc,
|
||||
title,
|
||||
p.ownerName ?? "",
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Generated
+207
-208
File diff suppressed because it is too large
Load Diff
@@ -9,7 +9,7 @@ license = "GPL-3.0"
|
||||
authors = ["zzzgydi", "keiko233"]
|
||||
|
||||
[workspace.dependencies]
|
||||
thiserror = "1"
|
||||
thiserror = "2"
|
||||
tracing = "0.1"
|
||||
boa_engine = { version = "0.19.1" }
|
||||
|
||||
|
||||
@@ -109,12 +109,12 @@ atomic_enum = "0.3.0"
|
||||
enumflags2 = "0.7"
|
||||
|
||||
# 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
|
||||
dunce = "1.0.4" # for cross platform path normalization
|
||||
runas = { git = "https://github.com/libnyanpasu/rust-runas.git" }
|
||||
single-instance = "0.3.3"
|
||||
which = "6"
|
||||
which = "7"
|
||||
dirs = "5.0.1"
|
||||
open = "5.0.1"
|
||||
sysinfo = "0.32"
|
||||
|
||||
@@ -84,7 +84,7 @@
|
||||
}
|
||||
},
|
||||
"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",
|
||||
"items": {
|
||||
"$ref": "#/definitions/PermissionEntry"
|
||||
|
||||
@@ -84,7 +84,7 @@
|
||||
}
|
||||
},
|
||||
"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",
|
||||
"items": {
|
||||
"$ref": "#/definitions/PermissionEntry"
|
||||
|
||||
@@ -36,6 +36,8 @@ enum Commands {
|
||||
#[arg(raw = true)]
|
||||
args: Vec<String>,
|
||||
},
|
||||
/// Show a panic dialog while the application is enter panic handler.
|
||||
PanicDialog { message: String },
|
||||
}
|
||||
|
||||
struct DelayedExitGuard;
|
||||
@@ -87,6 +89,9 @@ pub fn parse() -> anyhow::Result<()> {
|
||||
let envs = crate::utils::collect::collect_envs().unwrap();
|
||||
println!("{:#?}", envs);
|
||||
}
|
||||
Commands::PanicDialog { message } => {
|
||||
crate::utils::dialog::panic_dialog(message);
|
||||
}
|
||||
}
|
||||
drop(guard);
|
||||
std::process::exit(0);
|
||||
|
||||
@@ -108,7 +108,6 @@ pub fn run() -> std::io::Result<()> {
|
||||
crate::log_err!(init::init_config());
|
||||
|
||||
// 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| {
|
||||
use std::backtrace::{Backtrace, BacktraceStatus};
|
||||
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 (backtrace, note) = {
|
||||
let backtrace = Backtrace::capture();
|
||||
let backtrace = Backtrace::force_capture();
|
||||
let note = (backtrace.status() == BacktraceStatus::Disabled)
|
||||
.then_some("run with RUST_BACKTRACE=1 environment variable to display a backtrace");
|
||||
(Some(backtrace), note)
|
||||
@@ -145,20 +144,29 @@ pub fn run() -> std::io::Result<()> {
|
||||
return;
|
||||
}
|
||||
|
||||
utils::dialog::panic_dialog(&format!(
|
||||
"payload: {:#?}\nlocation: {:?}\nbacktrace: {:#?}\n\nnote: {:?}",
|
||||
payload, location, backtrace, note
|
||||
));
|
||||
// FIXME: maybe move this logic to a util function?
|
||||
let msg = format!(
|
||||
"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
|
||||
let task = std::thread::spawn(move || {
|
||||
nyanpasu_utils::runtime::block_on(async {
|
||||
let _ = crate::core::CoreManager::global().stop_core().await;
|
||||
});
|
||||
});
|
||||
let _ = task.join();
|
||||
default_panic(panic_info);
|
||||
std::process::exit(1); // exit if default panic handler doesn't exit
|
||||
match Handle::global().app_handle.lock().as_ref() {
|
||||
Some(app_handle) => {
|
||||
app_handle.exit(1);
|
||||
}
|
||||
None => {
|
||||
log::error!("app handle is not initialized");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
let verge = { Config::verge().latest().language.clone().unwrap() };
|
||||
@@ -322,39 +330,35 @@ pub fn run() -> std::io::Result<()> {
|
||||
.build(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
app.run(|app_handle, e| match e {
|
||||
tauri::RunEvent::ExitRequested { api, .. } => {
|
||||
tauri::RunEvent::ExitRequested { api, code, .. } if code.is_none() => {
|
||||
api.prevent_exit();
|
||||
}
|
||||
tauri::RunEvent::Exit => {
|
||||
resolve::resolve_reset();
|
||||
tauri::RunEvent::ExitRequested { .. } => {
|
||||
utils::help::cleanup_processes(app_handle);
|
||||
}
|
||||
tauri::RunEvent::WindowEvent { label, event, .. } => {
|
||||
if label == "main" {
|
||||
match event {
|
||||
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::RunEvent::WindowEvent { label, event, .. } if label == "main" => match event {
|
||||
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);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
});
|
||||
|
||||
|
||||
@@ -247,9 +247,7 @@ pub fn cleanup_processes(app_handle: &AppHandle) {
|
||||
|
||||
#[instrument(skip(app_handle))]
|
||||
pub fn quit_application(app_handle: &AppHandle) {
|
||||
cleanup_processes(app_handle);
|
||||
app_handle.exit(0);
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
#[instrument(skip(app_handle))]
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
"serve": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@dnd-kit/core": "6.1.0",
|
||||
"@dnd-kit/sortable": "8.0.0",
|
||||
"@dnd-kit/core": "6.2.0",
|
||||
"@dnd-kit/sortable": "9.0.0",
|
||||
"@dnd-kit/utilities": "3.2.2",
|
||||
"@emotion/styled": "11.13.5",
|
||||
"@juggle/resize-observer": "3.4.0",
|
||||
@@ -29,7 +29,7 @@
|
||||
"country-code-emoji": "2.3.0",
|
||||
"dayjs": "1.11.13",
|
||||
"framer-motion": "12.0.0-alpha.2",
|
||||
"i18next": "23.16.8",
|
||||
"i18next": "24.0.0",
|
||||
"jotai": "2.10.3",
|
||||
"json-schema": "0.4.0",
|
||||
"material-react-table": "3.0.1",
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"mihomo_alpha": "alpha-eb985b0",
|
||||
"clash_rs": "v0.7.1",
|
||||
"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": {
|
||||
"mihomo": {
|
||||
@@ -69,5 +69,5 @@
|
||||
"linux-armv7hf": "clash-armv7-unknown-linux-gnueabihf"
|
||||
}
|
||||
},
|
||||
"updated_at": "2024-11-21T22:20:51.459Z"
|
||||
"updated_at": "2024-11-23T22:20:39.660Z"
|
||||
}
|
||||
|
||||
Generated
+32
-25
@@ -184,11 +184,11 @@ importers:
|
||||
frontend/nyanpasu:
|
||||
dependencies:
|
||||
'@dnd-kit/core':
|
||||
specifier: 6.1.0
|
||||
version: 6.1.0(react-dom@19.0.0-rc.1(react@19.0.0-rc.1))(react@19.0.0-rc.1)
|
||||
specifier: 6.2.0
|
||||
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':
|
||||
specifier: 8.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)
|
||||
specifier: 9.0.0
|
||||
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':
|
||||
specifier: 3.2.2
|
||||
version: 3.2.2(react@19.0.0-rc.1)
|
||||
@@ -241,8 +241,8 @@ importers:
|
||||
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)
|
||||
i18next:
|
||||
specifier: 23.16.8
|
||||
version: 23.16.8
|
||||
specifier: 24.0.0
|
||||
version: 24.0.0(typescript@5.7.2)
|
||||
jotai:
|
||||
specifier: 2.10.3
|
||||
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)
|
||||
react-i18next:
|
||||
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:
|
||||
specifier: 9.0.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)
|
||||
react-i18next:
|
||||
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:
|
||||
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)
|
||||
@@ -1294,21 +1294,21 @@ packages:
|
||||
resolution: {integrity: sha512-WyOx8cJQ+FQus4Mm4uPIZA64gbk3Wxh0so5Lcii0aJifqwoVOlfFtorjLE0Hen4OYyHZMXDWqMmaQemBhgxFRQ==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
'@dnd-kit/accessibility@3.1.0':
|
||||
resolution: {integrity: sha512-ea7IkhKvlJUv9iSHJOnxinBcoOI3ppGnnL+VDJ75O45Nss6HtZd8IdN8touXPDtASfeI2T2LImb8VOZcL47wjQ==}
|
||||
'@dnd-kit/accessibility@3.1.1':
|
||||
resolution: {integrity: sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==}
|
||||
peerDependencies:
|
||||
react: '>=16.8.0'
|
||||
|
||||
'@dnd-kit/core@6.1.0':
|
||||
resolution: {integrity: sha512-J3cQBClB4TVxwGo3KEjssGEXNJqGVWx17aRTZ1ob0FliR5IjYgTxl5YJbKTzA6IzrtelotH19v6y7uoIRUZPSg==}
|
||||
'@dnd-kit/core@6.2.0':
|
||||
resolution: {integrity: sha512-KVK/CJmaYGTxTPU6P0+Oy4itgffTUa80B8317sXzfOr1qUzSL29jE7Th11llXiu2haB7B9Glpzo2CDElin+geQ==}
|
||||
peerDependencies:
|
||||
react: '>=16.8.0'
|
||||
react-dom: '>=16.8.0'
|
||||
|
||||
'@dnd-kit/sortable@8.0.0':
|
||||
resolution: {integrity: sha512-U3jk5ebVXe1Lr7c2wU7SBZjcWdQP+j7peHJfCspnA81enlu88Mgd7CC8Q+pub9ubP7eKVETzJW+IBAhsqbSu/g==}
|
||||
'@dnd-kit/sortable@9.0.0':
|
||||
resolution: {integrity: sha512-3/9r8Mmba0nKTbo8kPnVSFZKf/VSy94nXZ3aUwzPEh78j/LooQ/EFKRZENak4PHKBkN53mgTF/z+Sd8H+FcAnQ==}
|
||||
peerDependencies:
|
||||
'@dnd-kit/core': ^6.1.0
|
||||
'@dnd-kit/core': ^6.2.0
|
||||
react: '>=16.8.0'
|
||||
|
||||
'@dnd-kit/utilities@3.2.2':
|
||||
@@ -4849,8 +4849,13 @@ packages:
|
||||
hyphenate-style-name@1.1.0:
|
||||
resolution: {integrity: sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==}
|
||||
|
||||
i18next@23.16.8:
|
||||
resolution: {integrity: sha512-06r/TitrM88Mg5FdUXAKL96dJMzgqLE5dv3ryBAra4KCwD9mJ4ndOTS95ZuymIGoE+2hzfdaMak2X11/es7ZWg==}
|
||||
i18next@24.0.0:
|
||||
resolution: {integrity: sha512-ORGCwMrXxpmB/AljFbGEe0UK/9Pz6umb9aZgLZ9qJGE+kjKhlnLj423WX2mt+N0MlEJ78pQXFMBmeMzrkLxriQ==}
|
||||
peerDependencies:
|
||||
typescript: ^5
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
iconv-lite@0.6.3:
|
||||
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
|
||||
@@ -8777,22 +8782,22 @@ snapshots:
|
||||
|
||||
'@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:
|
||||
react: 19.0.0-rc.1
|
||||
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:
|
||||
'@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)
|
||||
react: 19.0.0-rc.1
|
||||
react-dom: 19.0.0-rc.1(react@19.0.0-rc.1)
|
||||
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:
|
||||
'@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)
|
||||
react: 19.0.0-rc.1
|
||||
tslib: 2.7.0
|
||||
@@ -12589,9 +12594,11 @@ snapshots:
|
||||
|
||||
hyphenate-style-name@1.1.0: {}
|
||||
|
||||
i18next@23.16.8:
|
||||
i18next@24.0.0(typescript@5.7.2):
|
||||
dependencies:
|
||||
'@babel/runtime': 7.26.0
|
||||
optionalDependencies:
|
||||
typescript: 5.7.2
|
||||
|
||||
iconv-lite@0.6.3:
|
||||
dependencies:
|
||||
@@ -14113,11 +14120,11 @@ snapshots:
|
||||
dependencies:
|
||||
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:
|
||||
'@babel/runtime': 7.26.0
|
||||
html-parse-stringify: 3.0.1
|
||||
i18next: 23.16.8
|
||||
i18next: 24.0.0(typescript@5.7.2)
|
||||
react: 19.0.0-rc.1
|
||||
optionalDependencies:
|
||||
react-dom: 19.0.0-rc.1(react@19.0.0-rc.1)
|
||||
|
||||
@@ -33,7 +33,9 @@ $(call KernelPackage/r8125)
|
||||
endef
|
||||
|
||||
ifeq ($(BUILD_VARIANT),rss)
|
||||
PKG_MAKE_FLAGS += ENABLE_RSS_SUPPORT=y
|
||||
PKG_MAKE_FLAGS += \
|
||||
ENABLE_RSS_SUPPORT=y \
|
||||
ENABLE_MULTIPLE_TX_QUEUE=y
|
||||
endif
|
||||
|
||||
PKG_MAKE_FLAGS += CONFIG_ASPM=n
|
||||
|
||||
@@ -33,7 +33,9 @@ $(call KernelPackage/r8126)
|
||||
endef
|
||||
|
||||
ifeq ($(BUILD_VARIANT),rss)
|
||||
PKG_MAKE_FLAGS += ENABLE_RSS_SUPPORT=y
|
||||
PKG_MAKE_FLAGS += \
|
||||
ENABLE_RSS_SUPPORT=y \
|
||||
ENABLE_MULTIPLE_TX_QUEUE=y
|
||||
endif
|
||||
|
||||
PKG_MAKE_FLAGS += CONFIG_ASPM=n
|
||||
|
||||
+1
-8
@@ -296,10 +296,6 @@
|
||||
};
|
||||
};
|
||||
|
||||
/*
|
||||
* fspi is unavailable
|
||||
* use i2c instead
|
||||
*/
|
||||
&i2c8 {
|
||||
pinctrl-names = "default";
|
||||
pinctrl-0 = <&i2c8m1_xfer>;
|
||||
@@ -840,9 +836,6 @@
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
/*
|
||||
* Disabled due to driver bug.
|
||||
*/
|
||||
&usb_host2_xhci {
|
||||
status = "disabled";
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
+39
@@ -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");
|
||||
|
||||
+39
@@ -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)
|
||||
end
|
||||
|
||||
local LOCAL_EXTEND_ARG = ""
|
||||
if LOCAL_GROUP == "nil" then
|
||||
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
|
||||
|
||||
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
|
||||
domain_rules_str = domain_rules_str .. " -address #6"
|
||||
end
|
||||
elseif DEFAULT_DNS_GROUP == LOCAL_GROUP then
|
||||
domain_rules_str = domain_rules_str .. (LOCAL_EXTEND_ARG ~= "" and " " .. LOCAL_EXTEND_ARG or "")
|
||||
end
|
||||
table.insert(config_lines, domain_rules_str)
|
||||
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 "")
|
||||
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)
|
||||
insert_array_after(config_lines, tmp_lines, "#--8")
|
||||
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 "")
|
||||
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)
|
||||
insert_array_after(config_lines, tmp_lines, "#--6")
|
||||
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
|
||||
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 .. (LOCAL_EXTEND_ARG ~= "" and " " .. LOCAL_EXTEND_ARG or "")
|
||||
table.insert(tmp_lines, domain_rules_str)
|
||||
insert_array_after(config_lines, tmp_lines, "#--2")
|
||||
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 "")
|
||||
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)
|
||||
insert_array_after(config_lines, tmp_lines, "#--3")
|
||||
end
|
||||
|
||||
@@ -70,10 +70,12 @@ type InboundContext struct {
|
||||
InboundOptions option.InboundOptions
|
||||
UDPDisableDomainUnmapping bool
|
||||
UDPConnect bool
|
||||
NetworkStrategy C.NetworkStrategy
|
||||
NetworkType []C.InterfaceType
|
||||
FallbackNetworkType []C.InterfaceType
|
||||
FallbackDelay time.Duration
|
||||
UDPTimeout time.Duration
|
||||
|
||||
NetworkStrategy C.NetworkStrategy
|
||||
NetworkType []C.InterfaceType
|
||||
FallbackNetworkType []C.InterfaceType
|
||||
FallbackDelay time.Duration
|
||||
|
||||
DNSServer string
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -10,6 +10,7 @@ const (
|
||||
ProtocolDTLS = "dtls"
|
||||
ProtocolSSH = "ssh"
|
||||
ProtocolRDP = "rdp"
|
||||
ProtocolNTP = "ntp"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -9,8 +9,6 @@ const (
|
||||
TCPTimeout = 15 * time.Second
|
||||
ReadPayloadTimeout = 300 * time.Millisecond
|
||||
DNSTimeout = 10 * time.Second
|
||||
QUICTimeout = 30 * time.Second
|
||||
STUNTimeout = 15 * time.Second
|
||||
UDPTimeout = 5 * time.Minute
|
||||
DefaultURLTestInterval = 3 * time.Minute
|
||||
DefaultURLTestIdleTimeout = 30 * time.Minute
|
||||
@@ -19,3 +17,18 @@ const (
|
||||
FatalStopTimeout = 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,
|
||||
}
|
||||
|
||||
@@ -2,6 +2,19 @@
|
||||
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
|
||||
|
||||
* Add UDP GSO support for WireGuard
|
||||
|
||||
@@ -41,7 +41,8 @@ See `route-options` fields below.
|
||||
"network_strategy": "",
|
||||
"fallback_delay": "",
|
||||
"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.
|
||||
|
||||
#### 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
|
||||
|
||||
```json
|
||||
|
||||
@@ -37,7 +37,8 @@ icon: material/new-box
|
||||
"network_strategy": "",
|
||||
"fallback_delay": "",
|
||||
"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_timeout
|
||||
|
||||
UDP 连接超时时间。
|
||||
|
||||
设置比入站 UDP 超时更大的值将无效。
|
||||
|
||||
已探测协议连接的默认值:
|
||||
|
||||
| 超时 | 协议 |
|
||||
|-------|----------------------|
|
||||
| `10s` | `dns`, `ntp`, `stun` |
|
||||
| `30s` | `quic`, `dtls` |
|
||||
|
||||
如果没有探测到协议,以下端口将默认识别为协议:
|
||||
|
||||
| 端口 | 协议 |
|
||||
|------|--------|
|
||||
| 53 | `dns` |
|
||||
| 123 | `ntp` |
|
||||
| 443 | `quic` |
|
||||
| 3478 | `stun` |
|
||||
|
||||
### reject
|
||||
|
||||
```json
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@ require (
|
||||
github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff
|
||||
github.com/sagernet/quic-go v0.48.1-beta.1
|
||||
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-mux v0.3.0-alpha.1
|
||||
github.com/sagernet/sing-quic v0.4.0-alpha.4
|
||||
|
||||
+2
-2
@@ -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/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
|
||||
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.18/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||
github.com/sagernet/sing v0.6.0-alpha.20 h1:coxvnzeEGSLNNPntUW7l8WUEHPIwqKszZNbU019To9c=
|
||||
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/go.mod h1:9LHcYKg2bGQpbtXrfNbopz8ok/zBK9ljiI2kmFG9JKg=
|
||||
github.com/sagernet/sing-mux v0.3.0-alpha.1 h1:IgNX5bJBpL41gGbp05pdDOvh/b5eUQ6cv9240+Ngipg=
|
||||
|
||||
@@ -148,8 +148,9 @@ type RawRouteOptionsActionOptions struct {
|
||||
NetworkStrategy NetworkStrategy `json:"network_strategy,omitempty"`
|
||||
FallbackDelay uint32 `json:"fallback_delay,omitempty"`
|
||||
|
||||
UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty"`
|
||||
UDPConnect bool `json:"udp_connect,omitempty"`
|
||||
UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty"`
|
||||
UDPConnect bool `json:"udp_connect,omitempty"`
|
||||
UDPTimeout badoption.Duration `json:"udp_timeout,omitempty"`
|
||||
}
|
||||
|
||||
type RouteOptionsActionOptions RawRouteOptionsActionOptions
|
||||
|
||||
@@ -14,7 +14,7 @@ type WireGuardEndpointOptions struct {
|
||||
PrivateKey string `json:"private_key"`
|
||||
ListenPort uint16 `json:"listen_port,omitempty"`
|
||||
Peers []WireGuardPeer `json:"peers,omitempty"`
|
||||
UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"`
|
||||
UDPTimeout badoption.Duration `json:"udp_timeout,omitempty"`
|
||||
Workers int `json:"workers,omitempty"`
|
||||
DialerOptions
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common/atomic"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
@@ -21,17 +22,22 @@ func RegisterSelector(registry *outbound.Registry) {
|
||||
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 {
|
||||
outbound.Adapter
|
||||
ctx context.Context
|
||||
outboundManager adapter.OutboundManager
|
||||
outbound adapter.OutboundManager
|
||||
connection adapter.ConnectionManager
|
||||
logger logger.ContextLogger
|
||||
tags []string
|
||||
defaultTag string
|
||||
outbounds map[string]adapter.Outbound
|
||||
selected adapter.Outbound
|
||||
selected atomic.TypedValue[adapter.Outbound]
|
||||
interruptGroup *interrupt.Group
|
||||
interruptExternalConnections bool
|
||||
}
|
||||
@@ -40,7 +46,8 @@ func NewSelector(ctx context.Context, router adapter.Router, logger log.ContextL
|
||||
outbound := &Selector{
|
||||
Adapter: outbound.NewAdapter(C.TypeSelector, tag, nil, options.Outbounds),
|
||||
ctx: ctx,
|
||||
outboundManager: service.FromContext[adapter.OutboundManager](ctx),
|
||||
outbound: service.FromContext[adapter.OutboundManager](ctx),
|
||||
connection: service.FromContext[adapter.ConnectionManager](ctx),
|
||||
logger: logger,
|
||||
tags: options.Outbounds,
|
||||
defaultTag: options.Default,
|
||||
@@ -55,15 +62,16 @@ func NewSelector(ctx context.Context, router adapter.Router, logger log.ContextL
|
||||
}
|
||||
|
||||
func (s *Selector) Network() []string {
|
||||
if s.selected == nil {
|
||||
selected := s.selected.Load()
|
||||
if selected == nil {
|
||||
return []string{N.NetworkTCP, N.NetworkUDP}
|
||||
}
|
||||
return s.selected.Network()
|
||||
return selected.Network()
|
||||
}
|
||||
|
||||
func (s *Selector) Start() error {
|
||||
for i, tag := range s.tags {
|
||||
detour, loaded := s.outboundManager.Outbound(tag)
|
||||
detour, loaded := s.outbound.Outbound(tag)
|
||||
if !loaded {
|
||||
return E.New("outbound ", i, " not found: ", tag)
|
||||
}
|
||||
@@ -77,7 +85,7 @@ func (s *Selector) Start() error {
|
||||
if selected != "" {
|
||||
detour, loaded := s.outbounds[selected]
|
||||
if loaded {
|
||||
s.selected = detour
|
||||
s.selected.Store(detour)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -89,16 +97,16 @@ func (s *Selector) Start() error {
|
||||
if !loaded {
|
||||
return E.New("default outbound not found: ", s.defaultTag)
|
||||
}
|
||||
s.selected = detour
|
||||
s.selected.Store(detour)
|
||||
return nil
|
||||
}
|
||||
|
||||
s.selected = s.outbounds[s.tags[0]]
|
||||
s.selected.Store(s.outbounds[s.tags[0]])
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Selector) Now() string {
|
||||
selected := s.selected
|
||||
selected := s.selected.Load()
|
||||
if selected == nil {
|
||||
return s.tags[0]
|
||||
}
|
||||
@@ -114,10 +122,9 @@ func (s *Selector) SelectOutbound(tag string) bool {
|
||||
if !loaded {
|
||||
return false
|
||||
}
|
||||
if s.selected == detour {
|
||||
if s.selected.Swap(detour) == detour {
|
||||
return true
|
||||
}
|
||||
s.selected = detour
|
||||
if s.Tag() != "" {
|
||||
cacheFile := service.FromContext[adapter.CacheFile](s.ctx)
|
||||
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) {
|
||||
conn, err := s.selected.DialContext(ctx, network, destination)
|
||||
conn, err := s.selected.Load().DialContext(ctx, network, destination)
|
||||
if err != nil {
|
||||
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) {
|
||||
conn, err := s.selected.ListenPacket(ctx, destination)
|
||||
conn, err := s.selected.Load().ListenPacket(ctx, destination)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.interruptGroup.NewPacketConn(conn, interrupt.IsExternalConnectionFromContext(ctx)), nil
|
||||
}
|
||||
|
||||
// TODO
|
||||
// Deprecated
|
||||
func (s *Selector) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||
func (s *Selector) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
||||
ctx = interrupt.ContextWithIsExternalConnection(ctx)
|
||||
if legacyHandler, ok := s.selected.(adapter.ConnectionHandler); ok {
|
||||
return legacyHandler.NewConnection(ctx, conn, metadata)
|
||||
selected := s.selected.Load()
|
||||
if outboundHandler, isHandler := selected.(adapter.ConnectionHandlerEx); isHandler {
|
||||
outboundHandler.NewConnectionEx(ctx, conn, metadata, onClose)
|
||||
} else {
|
||||
return outbound.NewConnection(ctx, s.selected, conn, metadata)
|
||||
s.connection.NewConnection(ctx, selected, conn, metadata, onClose)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO
|
||||
// Deprecated
|
||||
func (s *Selector) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
||||
func (s *Selector) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
||||
ctx = interrupt.ContextWithIsExternalConnection(ctx)
|
||||
if legacyHandler, ok := s.selected.(adapter.PacketConnectionHandler); ok {
|
||||
return legacyHandler.NewPacketConnection(ctx, conn, metadata)
|
||||
selected := s.selected.Load()
|
||||
if outboundHandler, isHandler := selected.(adapter.PacketConnectionHandlerEx); isHandler {
|
||||
outboundHandler.NewPacketConnectionEx(ctx, conn, metadata, onClose)
|
||||
} else {
|
||||
return outbound.NewPacketConnection(ctx, s.selected, conn, metadata)
|
||||
s.connection.NewPacketConnection(ctx, selected, conn, metadata, onClose)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,8 @@ type URLTest struct {
|
||||
outbound.Adapter
|
||||
ctx context.Context
|
||||
router adapter.Router
|
||||
outboundManager adapter.OutboundManager
|
||||
outbound adapter.OutboundManager
|
||||
connection adapter.ConnectionManager
|
||||
logger log.ContextLogger
|
||||
tags []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),
|
||||
ctx: ctx,
|
||||
router: router,
|
||||
outboundManager: service.FromContext[adapter.OutboundManager](ctx),
|
||||
outbound: service.FromContext[adapter.OutboundManager](ctx),
|
||||
connection: service.FromContext[adapter.ConnectionManager](ctx),
|
||||
logger: logger,
|
||||
tags: options.Outbounds,
|
||||
link: options.URL,
|
||||
@@ -70,13 +72,13 @@ func NewURLTest(ctx context.Context, router adapter.Router, logger log.ContextLo
|
||||
func (s *URLTest) Start() error {
|
||||
outbounds := make([]adapter.Outbound, 0, len(s.tags))
|
||||
for i, tag := range s.tags {
|
||||
detour, loaded := s.outboundManager.Outbound(tag)
|
||||
detour, loaded := s.outbound.Outbound(tag)
|
||||
if !loaded {
|
||||
return E.New("outbound ", i, " not found: ", tag)
|
||||
}
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
@@ -160,18 +162,14 @@ func (s *URLTest) ListenPacket(ctx context.Context, destination M.Socksaddr) (ne
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO
|
||||
// Deprecated
|
||||
func (s *URLTest) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||
func (s *URLTest) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
||||
ctx = interrupt.ContextWithIsExternalConnection(ctx)
|
||||
return outbound.NewConnection(ctx, s, conn, metadata)
|
||||
s.connection.NewConnection(ctx, s, conn, metadata, onClose)
|
||||
}
|
||||
|
||||
// TODO
|
||||
// Deprecated
|
||||
func (s *URLTest) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
||||
func (s *URLTest) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
||||
ctx = interrupt.ContextWithIsExternalConnection(ctx)
|
||||
return outbound.NewPacketConnection(ctx, s, conn, metadata)
|
||||
s.connection.NewPacketConnection(ctx, s, conn, metadata, onClose)
|
||||
}
|
||||
|
||||
func (s *URLTest) InterfaceUpdated() {
|
||||
|
||||
+42
-29
@@ -6,11 +6,14 @@ import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"sync/atomic"
|
||||
"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/bufio"
|
||||
"github.com/sagernet/sing/common/canceler"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
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 {
|
||||
cachedBuffer := cachedSrc.ReadCached()
|
||||
if cachedBuffer != nil {
|
||||
if !cachedBuffer.IsEmpty() {
|
||||
dataLen := cachedBuffer.Len()
|
||||
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))
|
||||
}
|
||||
}
|
||||
dataLen := cachedBuffer.Len()
|
||||
_, err := destination.Write(cachedBuffer.Bytes())
|
||||
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
|
||||
}
|
||||
var (
|
||||
dstDuplex bool
|
||||
err error
|
||||
)
|
||||
_, err = bufio.CopyWithCounters(destination, source, originSource, readCounters, writeCounters)
|
||||
if _, dstDuplex = common.Cast[N.WriteCloser](destination); dstDuplex && err == nil {
|
||||
_, err := bufio.CopyWithCounters(destination, source, originSource, readCounters, writeCounters)
|
||||
if err != nil {
|
||||
common.Close(destination, source)
|
||||
} else if _, dstDuplex := destination.(N.WriteCloser); dstDuplex {
|
||||
N.CloseWrite(destination)
|
||||
} else {
|
||||
common.Close(destination)
|
||||
@@ -206,6 +204,21 @@ func (m *ConnectionManager) NewPacketConnection(ctx context.Context, this N.Dial
|
||||
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)
|
||||
if ctx.Done() != nil {
|
||||
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) {
|
||||
common.Close(source, destination)
|
||||
if onClose != nil {
|
||||
onClose(err)
|
||||
}
|
||||
}
|
||||
common.Close(source, destination)
|
||||
}
|
||||
|
||||
/*type udpHijacker struct {
|
||||
|
||||
+7
-16
@@ -132,23 +132,11 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad
|
||||
if r.tracker != nil {
|
||||
conn = r.tracker.RoutedConnection(ctx, conn, metadata, selectedRule, selectedOutbound)
|
||||
}
|
||||
legacyOutbound, isLegacy := selectedOutbound.(adapter.ConnectionHandler)
|
||||
if isLegacy {
|
||||
err = legacyOutbound.NewConnection(ctx, conn, metadata)
|
||||
if err != nil {
|
||||
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
|
||||
if outboundHandler, isHandler := selectedOutbound.(adapter.ConnectionHandlerEx); isHandler {
|
||||
outboundHandler.NewConnectionEx(ctx, conn, metadata, onClose)
|
||||
} else {
|
||||
r.connection.NewConnection(ctx, selectedOutbound, conn, metadata, onClose)
|
||||
}
|
||||
r.connection.NewConnection(ctx, selectedOutbound, conn, metadata, onClose)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -440,6 +428,9 @@ match:
|
||||
if routeOptions.UDPConnect {
|
||||
metadata.UDPConnect = true
|
||||
}
|
||||
if routeOptions.UDPTimeout > 0 {
|
||||
metadata.UDPTimeout = routeOptions.UDPTimeout
|
||||
}
|
||||
}
|
||||
switch action := currentRule.Action().(type) {
|
||||
case *rule.RuleActionSniff:
|
||||
|
||||
@@ -47,6 +47,7 @@ func NewRuleAction(ctx context.Context, logger logger.ContextLogger, action opti
|
||||
FallbackDelay: time.Duration(action.RouteOptionsOptions.FallbackDelay),
|
||||
UDPDisableDomainUnmapping: action.RouteOptionsOptions.UDPDisableDomainUnmapping,
|
||||
UDPConnect: action.RouteOptionsOptions.UDPConnect,
|
||||
UDPTimeout: time.Duration(action.RouteOptionsOptions.UDPTimeout),
|
||||
}, nil
|
||||
case C.RuleActionTypeDirect:
|
||||
directDialer, err := dialer.New(ctx, option.DialerOptions(action.DirectOptions))
|
||||
@@ -152,6 +153,7 @@ type RuleActionRouteOptions struct {
|
||||
FallbackDelay time.Duration
|
||||
UDPDisableDomainUnmapping bool
|
||||
UDPConnect bool
|
||||
UDPTimeout time.Duration
|
||||
}
|
||||
|
||||
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() }),
|
||||
InterfaceMonitor: networkManager.InterfaceMonitor(),
|
||||
InterfaceFinder: networkManager.InterfaceFinder(),
|
||||
Logger: w.options.Logger,
|
||||
}
|
||||
// works with Linux, macOS with IFSCOPE routes, not tested on Windows
|
||||
if runtime.GOOS == "darwin" {
|
||||
|
||||
+3
-3
@@ -9,9 +9,9 @@ PKG_RELEASE:=1
|
||||
|
||||
PKG_SOURCE_PROTO:=git
|
||||
PKG_SOURCE_URL:=https://gn.googlesource.com/gn.git
|
||||
PKG_SOURCE_DATE:=2024-10-14
|
||||
PKG_SOURCE_VERSION:=feafd1012a32c05ec6095f69ddc3850afb621f3a
|
||||
PKG_MIRROR_HASH:=c5e7d8357105b8b6708772490d6c2a7670c9298472efc203e8e409f229de8fb7
|
||||
PKGOURCE_DATE:=2024-11-22
|
||||
PKG_SOURCE_VERSION:=468c6128db7fabe32a29d4753460ef53594406fc
|
||||
PKG_MIRROR_HASH:=22b5814a82742e8a39835c26390550a22ecbac5f2d853216057ab3fb4e377fd9
|
||||
|
||||
PKG_LICENSE:=BSD 3-Clause
|
||||
PKG_LICENSE_FILES:=LICENSE
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#ifndef OUT_LAST_COMMIT_POSITION_H_
|
||||
#define OUT_LAST_COMMIT_POSITION_H_
|
||||
|
||||
#define LAST_COMMIT_POSITION_NUM 2202
|
||||
#define LAST_COMMIT_POSITION "2202 (feafd1012a32)"
|
||||
#define LAST_COMMIT_POSITION_NUM 2205
|
||||
#define LAST_COMMIT_POSITION "2205 (468c6128db7f)"
|
||||
|
||||
#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.rmempty = false;
|
||||
o.value('https://mirror.ghproxy.com/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://mirror.ghproxy.com/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/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip', 'MetaCubeXD');
|
||||
o.value('https://ghp.ci/https://github.com/MetaCubeX/Yacd-meta/archive/refs/heads/gh-pages.zip', 'YACD');
|
||||
o.value('https://ghp.ci/https://github.com/MetaCubeX/Razord-meta/archive/refs/heads/gh-pages.zip', 'Razord');
|
||||
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.datatype = 'port';
|
||||
|
||||
@@ -98,9 +98,50 @@ if not fs.access(CACHE_PATH) then
|
||||
fs.mkdir(CACHE_PATH)
|
||||
end
|
||||
|
||||
local LOCAL_EXTEND_ARG = ""
|
||||
if LOCAL_GROUP == "nil" then
|
||||
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
|
||||
|
||||
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
|
||||
domain_rules_str = domain_rules_str .. " -address #6"
|
||||
end
|
||||
elseif DEFAULT_DNS_GROUP == LOCAL_GROUP then
|
||||
domain_rules_str = domain_rules_str .. (LOCAL_EXTEND_ARG ~= "" and " " .. LOCAL_EXTEND_ARG or "")
|
||||
end
|
||||
table.insert(config_lines, domain_rules_str)
|
||||
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 "")
|
||||
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)
|
||||
insert_array_after(config_lines, tmp_lines, "#--8")
|
||||
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 "")
|
||||
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)
|
||||
insert_array_after(config_lines, tmp_lines, "#--6")
|
||||
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
|
||||
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 .. (LOCAL_EXTEND_ARG ~= "" and " " .. LOCAL_EXTEND_ARG or "")
|
||||
table.insert(tmp_lines, domain_rules_str)
|
||||
insert_array_after(config_lines, tmp_lines, "#--2")
|
||||
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 "")
|
||||
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)
|
||||
insert_array_after(config_lines, tmp_lines, "#--3")
|
||||
end
|
||||
|
||||
@@ -57,7 +57,7 @@ config mixin 'mixin'
|
||||
option 'tcp_keep_alive_idle' '600'
|
||||
option 'tcp_keep_alive_interval' '15'
|
||||
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_secret' ''
|
||||
option 'selection_cache' '1'
|
||||
@@ -90,10 +90,10 @@ config mixin 'mixin'
|
||||
option 'dns_nameserver_policy' '0'
|
||||
option 'geoip_format' 'dat'
|
||||
option 'geodata_loader' 'memconservative'
|
||||
option 'geosite_url' 'https://mirror.ghproxy.com/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_dat_url' 'https://mirror.ghproxy.com/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 'geosite_url' 'https://ghp.ci/https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.dat'
|
||||
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://ghp.ci/https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip-lite.dat'
|
||||
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_update_interval' '24'
|
||||
option 'mixin_file_content' '0'
|
||||
|
||||
@@ -623,11 +623,23 @@ update_subscription() {
|
||||
if [ -z "$subscription_section" ]; then
|
||||
return
|
||||
fi
|
||||
# load config
|
||||
config_load mihomo
|
||||
# get subscription config
|
||||
local subscription_name subscription_url subscription_user_agent
|
||||
config_get subscription_name "$subscription_section" "name"
|
||||
config_get subscription_url "$subscription_section" "url"
|
||||
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."
|
||||
local subscription_header_tmpfile; subscription_header_tmpfile="/tmp/$subscription_section.header"
|
||||
local subscription_tmpfile; subscription_tmpfile="/tmp/$subscription_section.yaml"
|
||||
@@ -645,6 +657,7 @@ update_subscription() {
|
||||
subscription_avaliable=$((subscription_total - subscription_upload - subscription_download))
|
||||
fi
|
||||
fi
|
||||
# update subscription info
|
||||
if [ -n "$subscription_expire" ]; then
|
||||
uci_set "mihomo" "$subscription_section" "expire" "$(date "+%Y-%m-%d %H:%M:%S" -d @$subscription_expire)"
|
||||
fi
|
||||
@@ -664,12 +677,17 @@ update_subscription() {
|
||||
uci_set "mihomo" "$subscription_section" "avaliable" "$(format_filesize $subscription_avaliable)"
|
||||
fi
|
||||
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"
|
||||
mv -f "$subscription_tmpfile" "$subscription_file"
|
||||
else
|
||||
log "Subscription update failed."
|
||||
# update subscription info
|
||||
uci_set "mihomo" "$subscription_section" "success" "0"
|
||||
# remove tmpfile
|
||||
rm -f "$subscription_header_tmpfile"
|
||||
rm -f "$subscription_tmpfile"
|
||||
fi
|
||||
uci_commit "mihomo"
|
||||
}
|
||||
|
||||
+6
-5
@@ -15,11 +15,12 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: 'yarn'
|
||||
cache-dependency-path: gui/yarn.lock
|
||||
shell: bash
|
||||
run: |
|
||||
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
|
||||
brew install node@20
|
||||
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
|
||||
run: |
|
||||
sudo apt-get update -y && sudo apt-get install -y gzip
|
||||
|
||||
+6
-5
@@ -15,11 +15,12 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: 'yarn'
|
||||
cache-dependency-path: gui/yarn.lock
|
||||
shell: bash
|
||||
run: |
|
||||
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
|
||||
brew install node@20
|
||||
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
|
||||
run: |
|
||||
sudo apt-get update -y && sudo apt-get install -y gzip
|
||||
|
||||
+8
-18
@@ -1,12 +1,10 @@
|
||||
name: Build & Release v2rayA
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
type: string
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
Build_v2rayA_Web:
|
||||
runs-on: ubuntu-22.04
|
||||
@@ -15,11 +13,12 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: 'yarn'
|
||||
cache-dependency-path: gui/yarn.lock
|
||||
shell: bash
|
||||
run: |
|
||||
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
|
||||
brew install node@20
|
||||
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
|
||||
run: |
|
||||
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
|
||||
Set-Location -Path ..
|
||||
}
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: nanoufo/action-upload-artifacts-and-release-assets@v2
|
||||
with:
|
||||
path: |
|
||||
v2raya_binaries/*
|
||||
|
||||
Build_Windows_Installers:
|
||||
runs-on: windows-latest
|
||||
needs: [Build_v2rayA_Binaries]
|
||||
@@ -399,7 +396,6 @@ jobs:
|
||||
--data '{"purge_everything":true}'
|
||||
Build_v2ray_Debian_Packages:
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- name: Install Tools
|
||||
run: |
|
||||
@@ -543,7 +539,6 @@ jobs:
|
||||
run: |
|
||||
echo "V2RAY_VERSION=$V2RAY_VERSION" >> ./v2ray_packages_version.txt
|
||||
echo "XRAY_VERSION=$XRAY_VERSION" >> ./xray_packages_version.txt
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: nanoufo/action-upload-artifacts-and-release-assets@v2
|
||||
with:
|
||||
@@ -555,7 +550,6 @@ jobs:
|
||||
Build_APT_Repository_and_AUR:
|
||||
runs-on: ubuntu-22.04
|
||||
needs: [Build_v2rayA_Binaries, Build_Linux_Packages, Build_v2ray_Debian_Packages]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -638,7 +632,6 @@ jobs:
|
||||
Release_to_Homebrew:
|
||||
runs-on: ubuntu-22.04
|
||||
needs: [Build_v2rayA_Binaries]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -693,7 +686,6 @@ jobs:
|
||||
Release_v2rayA_to_Docker:
|
||||
runs-on: ubuntu-22.04
|
||||
needs: [GitHub_Release]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -733,7 +725,7 @@ jobs:
|
||||
context: .
|
||||
builder: ${{ steps.buildx.outputs.name }}
|
||||
file: install/docker/Dockerfile.Action
|
||||
platforms: linux/arm,linux/arm64,linux/amd64
|
||||
platforms: linux/arm,linux/arm64,linux/amd64,linux/riscv64
|
||||
push: true
|
||||
tags: |
|
||||
${{ steps.prep.outputs.image }}:${{ steps.prep.outputs.tag }}
|
||||
@@ -745,7 +737,6 @@ jobs:
|
||||
Release_v2rayA_GUI_to_Docker:
|
||||
runs-on: ubuntu-22.04
|
||||
needs: [Build_v2rayA_Web]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -788,7 +779,7 @@ jobs:
|
||||
context: .
|
||||
builder: ${{ steps.buildx.outputs.name }}
|
||||
file: install/docker/Dockerfile.GUI.Action
|
||||
platforms: linux/arm,linux/arm64,linux/amd64
|
||||
platforms: linux/arm,linux/arm64,linux/amd64,linux/riscv64
|
||||
push: true
|
||||
tags: |
|
||||
mzz2017/v2raya-gui:latest
|
||||
@@ -798,7 +789,6 @@ jobs:
|
||||
Submit_to_Microsoft_winget:
|
||||
runs-on: windows-latest
|
||||
needs: [GitHub_Release]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
|
||||
+7
-5
@@ -4,6 +4,7 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- dependabot/*
|
||||
paths:
|
||||
- "**/*.go"
|
||||
- "go.mod"
|
||||
@@ -19,11 +20,12 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: 'yarn'
|
||||
cache-dependency-path: gui/yarn.lock
|
||||
shell: bash
|
||||
run: |
|
||||
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
|
||||
brew install node@20
|
||||
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
|
||||
run: |
|
||||
sudo apt-get update -y && sudo apt-get install -y gzip
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
FROM nginx:stable-alpine
|
||||
WORKDIR /build
|
||||
COPY ./web /usr/share/nginx/html
|
||||
FROM busybox:latest AS builder
|
||||
WORKDIR /
|
||||
COPY ./web /usr/share/v2raya-web
|
||||
ENTRYPOINT ["/bin/httpd", "-f", "-h", "/usr/share/v2raya-web", "-p", "80"]
|
||||
EXPOSE 80
|
||||
@@ -14,6 +14,10 @@ case "$(arch)" in
|
||||
v2ray_arch="arm64-v8a"
|
||||
v2raya_arch="arm64"
|
||||
;;
|
||||
riscv64)
|
||||
v2ray_arch="riscv64"
|
||||
v2raya_arch="riscv64"
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<Version>7.2.0</Version>
|
||||
<Version>7.2.1</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -12,8 +12,8 @@ android {
|
||||
applicationId = "com.v2ray.ang"
|
||||
minSdk = 21
|
||||
targetSdk = 35
|
||||
versionCode = 615
|
||||
versionName = "1.9.19"
|
||||
versionCode = 616
|
||||
versionName = "1.9.20"
|
||||
multiDexEnabled = true
|
||||
|
||||
splits {
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
|
||||
#### Important changes
|
||||
- **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
|
||||
- [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)
|
||||
|
||||
@@ -1294,6 +1294,7 @@ The available fields are:
|
||||
- `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_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_basename` (string): The basename of the webpage URL
|
||||
- `webpage_url_domain` (string): The domain of the webpage URL
|
||||
|
||||
@@ -238,6 +238,6 @@
|
||||
{
|
||||
"action": "add",
|
||||
"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)"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -52,7 +52,7 @@ default = [
|
||||
"pycryptodomex",
|
||||
"requests>=2.32.2,<3",
|
||||
"urllib3>=1.26.17,<3",
|
||||
"websockets>=13.0,<14",
|
||||
"websockets>=13.0",
|
||||
]
|
||||
curl-cffi = [
|
||||
"curl-cffi==0.5.10; os_name=='nt' and implementation_name=='cpython'",
|
||||
|
||||
@@ -216,7 +216,9 @@ class SocksWebSocketTestRequestHandler(SocksTestRequestHandler):
|
||||
protocol = websockets.ServerProtocol()
|
||||
connection = websockets.sync.server.ServerConnection(socket=self.request, protocol=protocol, close_timeout=0)
|
||||
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()
|
||||
|
||||
|
||||
|
||||
@@ -1947,6 +1947,7 @@ class YoutubeDL:
|
||||
'playlist_uploader_id': ie_result.get('uploader_id'),
|
||||
'playlist_channel': ie_result.get('channel'),
|
||||
'playlist_channel_id': ie_result.get('channel_id'),
|
||||
'playlist_webpage_url': ie_result.get('webpage_url'),
|
||||
**kwargs,
|
||||
}
|
||||
if strict:
|
||||
|
||||
@@ -261,6 +261,7 @@ class DailymotionIE(DailymotionBaseInfoExtractor):
|
||||
'tags': [],
|
||||
'view_count': int,
|
||||
'like_count': int,
|
||||
'thumbnail': r're:https://\w+.dmcdn.net/v/WnEY61cmvMxt2Fi6d/x1080',
|
||||
},
|
||||
}, {
|
||||
# https://geo.dailymotion.com/player/xf7zn.html?playlist=x7wdsj
|
||||
@@ -288,6 +289,25 @@ class DailymotionIE(DailymotionBaseInfoExtractor):
|
||||
'description': 'À bord du « véloto », l’alternative à la voiture pour la campagne',
|
||||
'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
|
||||
_COMMON_MEDIA_FIELDS = '''description
|
||||
@@ -302,7 +322,7 @@ class DailymotionIE(DailymotionBaseInfoExtractor):
|
||||
yield from super()._extract_embed_urls(url, webpage)
|
||||
for mobj in re.finditer(
|
||||
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(
|
||||
r'(?s)<script [^>]*\bsrc=(["\'])(?:https?:)?//[\w-]+\.dailymotion\.com/player/(?:(?!\1).)+\1[^>]*>', webpage):
|
||||
attrs = extract_attributes(mobj.group(0))
|
||||
|
||||
@@ -50,7 +50,7 @@ class FacebookIE(InfoExtractor):
|
||||
[^/]+/videos/(?:[^/]+/)?|
|
||||
[^/]+/posts/|
|
||||
events/(?:[^/]+/)?|
|
||||
groups/[^/]+/(?:permalink|posts)/|
|
||||
groups/[^/]+/(?:permalink|posts)/(?:[\da-f]+/)?|
|
||||
watchparty/
|
||||
)|
|
||||
facebook:
|
||||
@@ -410,6 +410,9 @@ class FacebookIE(InfoExtractor):
|
||||
'uploader': 'Comitato Liberi Pensatori',
|
||||
'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]+)'
|
||||
_api_config = {
|
||||
|
||||
@@ -28,24 +28,21 @@ class StripchatIE(InfoExtractor):
|
||||
def _real_extract(self, url):
|
||||
video_id = self._match_id(url)
|
||||
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(
|
||||
self._search_regex(
|
||||
r'<script\b[^>]*>\s*window\.__PRELOADED_STATE__\s*=(?P<value>.*?)<\/script>',
|
||||
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):
|
||||
if traverse_obj(data, ('viewCam', 'show', {dict})):
|
||||
raise ExtractorError('Model is in a private show', expected=True)
|
||||
if not traverse_obj(data, ('viewCam', 'model', 'isLive', {bool})):
|
||||
raise UserNotLive(video_id=video_id)
|
||||
|
||||
model_id = traverse_obj(data, ('viewCam', 'model', 'id'), expected_type=int)
|
||||
model_id = data['viewCam']['model']['id']
|
||||
|
||||
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'))):
|
||||
formats = self._extract_m3u8_formats(
|
||||
f'https://edge-hls.{host}/hls/{model_id}/master/{model_id}_auto.m3u8',
|
||||
@@ -53,7 +50,7 @@ class StripchatIE(InfoExtractor):
|
||||
if formats:
|
||||
break
|
||||
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 {
|
||||
'id': video_id,
|
||||
|
||||
@@ -4986,6 +4986,10 @@ class YoutubeTabBaseInfoExtractor(YoutubeBaseInfoExtractor):
|
||||
for item in grid_renderer['items']:
|
||||
if not isinstance(item, dict):
|
||||
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)
|
||||
if not isinstance(renderer, dict):
|
||||
continue
|
||||
@@ -5084,10 +5088,30 @@ class YoutubeTabBaseInfoExtractor(YoutubeBaseInfoExtractor):
|
||||
continue
|
||||
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):
|
||||
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(
|
||||
rich_grid_renderer,
|
||||
('content', ('videoRenderer', 'reelItemRenderer', 'playlistRenderer', 'shortsLockupViewModel', 'lockupViewModel'), any)) or {}
|
||||
('content', ('videoRenderer', 'reelItemRenderer', 'playlistRenderer', 'shortsLockupViewModel'), any)) or {}
|
||||
video_id = renderer.get('videoId')
|
||||
if video_id:
|
||||
yield self._extract_video(renderer)
|
||||
@@ -5114,18 +5138,6 @@ class YoutubeTabBaseInfoExtractor(YoutubeBaseInfoExtractor):
|
||||
})),
|
||||
thumbnails=self._extract_thumbnails(renderer, 'thumbnail', final_key='sources'))
|
||||
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):
|
||||
video_id = video_renderer.get('videoId')
|
||||
@@ -5794,7 +5806,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
|
||||
'info_dict': {
|
||||
'id': 'UCYO_jab_esuFRV4b17AJtAw',
|
||||
'title': '3Blue1Brown - Playlists',
|
||||
'description': 'md5:4d1da95432004b7ba840ebc895b6b4c9',
|
||||
'description': 'md5:602e3789e6a0cb7d9d352186b720e395',
|
||||
'channel_url': 'https://www.youtube.com/channel/UCYO_jab_esuFRV4b17AJtAw',
|
||||
'channel': '3Blue1Brown',
|
||||
'channel_id': 'UCYO_jab_esuFRV4b17AJtAw',
|
||||
|
||||
Reference in New Issue
Block a user