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

This commit is contained in:
github-action[bot]
2024-11-24 19:33:47 +01:00
parent 8002e47064
commit d1d2d4712c
64 changed files with 825 additions and 652 deletions
+1
View File
@@ -834,3 +834,4 @@ Update On Wed Nov 20 19:36:13 CET 2024
Update On Thu Nov 21 19:38:10 CET 2024
Update On 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
+8 -5
View File
@@ -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}\" ");
+8 -3
View File
@@ -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.
+207 -208
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -9,7 +9,7 @@ license = "GPL-3.0"
authors = ["zzzgydi", "keiko233"]
[workspace.dependencies]
thiserror = "1"
thiserror = "2"
tracing = "0.1"
boa_engine = { version = "0.19.1" }
+2 -2
View File
@@ -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);
+48 -44
View File
@@ -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",
+2 -2
View File
@@ -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"
}
+32 -25
View File
@@ -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)
+3 -1
View File
@@ -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
+3 -1
View File
@@ -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
@@ -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";
};
@@ -0,0 +1,39 @@
--- a/arch/arm64/boot/dts/rockchip/rk3568.dtsi
+++ b/arch/arm64/boot/dts/rockchip/rk3568.dtsi
@@ -225,6 +225,7 @@
assigned-clocks = <&pmucru CLK_PCIEPHY0_REF>;
assigned-clock-rates = <100000000>;
resets = <&cru SRST_PIPEPHY0>;
+ reset-names = "phy";
rockchip,pipe-grf = <&pipegrf>;
rockchip,pipe-phy-grf = <&pipe_phy_grf0>;
#phy-cells = <1>;
--- a/arch/arm64/boot/dts/rockchip/rk356x.dtsi
+++ b/arch/arm64/boot/dts/rockchip/rk356x.dtsi
@@ -1686,6 +1686,7 @@
assigned-clocks = <&pmucru CLK_PCIEPHY1_REF>;
assigned-clock-rates = <100000000>;
resets = <&cru SRST_PIPEPHY1>;
+ reset-names = "phy";
rockchip,pipe-grf = <&pipegrf>;
rockchip,pipe-phy-grf = <&pipe_phy_grf1>;
#phy-cells = <1>;
@@ -1702,6 +1703,7 @@
assigned-clocks = <&pmucru CLK_PCIEPHY2_REF>;
assigned-clock-rates = <100000000>;
resets = <&cru SRST_PIPEPHY2>;
+ reset-names = "phy";
rockchip,pipe-grf = <&pipegrf>;
rockchip,pipe-phy-grf = <&pipe_phy_grf2>;
#phy-cells = <1>;
--- a/drivers/phy/rockchip/phy-rockchip-naneng-combphy.c
+++ b/drivers/phy/rockchip/phy-rockchip-naneng-combphy.c
@@ -299,7 +299,7 @@ static int rockchip_combphy_parse_dt(str
priv->ext_refclk = device_property_present(dev, "rockchip,ext-refclk");
- priv->phy_rst = devm_reset_control_array_get_exclusive(dev);
+ priv->phy_rst = devm_reset_control_get(dev, "phy");
if (IS_ERR(priv->phy_rst))
return dev_err_probe(dev, PTR_ERR(priv->phy_rst), "failed to get phy reset\n");
@@ -0,0 +1,39 @@
--- a/arch/arm64/boot/dts/rockchip/rk3568.dtsi
+++ b/arch/arm64/boot/dts/rockchip/rk3568.dtsi
@@ -225,6 +225,7 @@
assigned-clocks = <&pmucru CLK_PCIEPHY0_REF>;
assigned-clock-rates = <100000000>;
resets = <&cru SRST_PIPEPHY0>;
+ reset-names = "phy";
rockchip,pipe-grf = <&pipegrf>;
rockchip,pipe-phy-grf = <&pipe_phy_grf0>;
#phy-cells = <1>;
--- a/arch/arm64/boot/dts/rockchip/rk356x.dtsi
+++ b/arch/arm64/boot/dts/rockchip/rk356x.dtsi
@@ -1719,6 +1719,7 @@
assigned-clocks = <&pmucru CLK_PCIEPHY1_REF>;
assigned-clock-rates = <100000000>;
resets = <&cru SRST_PIPEPHY1>;
+ reset-names = "phy";
rockchip,pipe-grf = <&pipegrf>;
rockchip,pipe-phy-grf = <&pipe_phy_grf1>;
#phy-cells = <1>;
@@ -1735,6 +1736,7 @@
assigned-clocks = <&pmucru CLK_PCIEPHY2_REF>;
assigned-clock-rates = <100000000>;
resets = <&cru SRST_PIPEPHY2>;
+ reset-names = "phy";
rockchip,pipe-grf = <&pipegrf>;
rockchip,pipe-phy-grf = <&pipe_phy_grf2>;
#phy-cells = <1>;
--- a/drivers/phy/rockchip/phy-rockchip-naneng-combphy.c
+++ b/drivers/phy/rockchip/phy-rockchip-naneng-combphy.c
@@ -324,7 +324,7 @@ static int rockchip_combphy_parse_dt(str
priv->ext_refclk = device_property_present(dev, "rockchip,ext-refclk");
- priv->phy_rst = devm_reset_control_array_get_exclusive(dev);
+ priv->phy_rst = devm_reset_control_get(dev, "phy");
if (IS_ERR(priv->phy_rst))
return dev_err_probe(dev, PTR_ERR(priv->phy_rst), "failed to get phy reset\n");
@@ -98,9 +98,50 @@ if not fs.access(CACHE_PATH) then
fs.mkdir(CACHE_PATH)
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
+6 -4
View File
@@ -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
-157
View File
@@ -1,157 +0,0 @@
package outbound
import (
"context"
"net"
"net/netip"
"os"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/dialer"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
"github.com/sagernet/sing/common/canceler"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
func NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata adapter.InboundContext) error {
defer conn.Close()
ctx = adapter.WithContext(ctx, &metadata)
var outConn net.Conn
var err error
if len(metadata.DestinationAddresses) > 0 {
outConn, err = dialer.DialSerialNetwork(ctx, this, N.NetworkTCP, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.NetworkType, metadata.FallbackNetworkType, metadata.FallbackDelay)
} else {
outConn, err = this.DialContext(ctx, N.NetworkTCP, metadata.Destination)
}
if err != nil {
return N.ReportHandshakeFailure(conn, err)
}
err = N.ReportConnHandshakeSuccess(conn, outConn)
if err != nil {
outConn.Close()
return err
}
return CopyEarlyConn(ctx, conn, outConn)
}
func NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, metadata adapter.InboundContext) error {
defer conn.Close()
ctx = adapter.WithContext(ctx, &metadata)
var (
outPacketConn net.PacketConn
outConn net.Conn
destinationAddress netip.Addr
err error
)
if metadata.UDPConnect {
if len(metadata.DestinationAddresses) > 0 {
if parallelDialer, isParallelDialer := this.(dialer.ParallelInterfaceDialer); isParallelDialer {
outConn, err = dialer.DialSerialNetwork(ctx, parallelDialer, N.NetworkUDP, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.NetworkType, metadata.FallbackNetworkType, metadata.FallbackDelay)
} else {
outConn, err = N.DialSerial(ctx, this, N.NetworkUDP, metadata.Destination, metadata.DestinationAddresses)
}
} else {
outConn, err = this.DialContext(ctx, N.NetworkUDP, metadata.Destination)
}
if err != nil {
return N.ReportHandshakeFailure(conn, err)
}
outPacketConn = bufio.NewUnbindPacketConn(outConn)
connRemoteAddr := M.AddrFromNet(outConn.RemoteAddr())
if connRemoteAddr != metadata.Destination.Addr {
destinationAddress = connRemoteAddr
}
} else {
if len(metadata.DestinationAddresses) > 0 {
outPacketConn, destinationAddress, err = dialer.ListenSerialNetworkPacket(ctx, this, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.NetworkType, metadata.FallbackNetworkType, metadata.FallbackDelay)
} else {
outPacketConn, err = this.ListenPacket(ctx, metadata.Destination)
}
if err != nil {
return N.ReportHandshakeFailure(conn, err)
}
}
err = N.ReportPacketConnHandshakeSuccess(conn, outPacketConn)
if err != nil {
outPacketConn.Close()
return err
}
if destinationAddress.IsValid() {
var originDestination M.Socksaddr
if metadata.RouteOriginalDestination.IsValid() {
originDestination = metadata.RouteOriginalDestination
} else {
originDestination = metadata.Destination
}
if metadata.Destination != M.SocksaddrFrom(destinationAddress, metadata.Destination.Port) {
if metadata.UDPDisableDomainUnmapping {
outPacketConn = bufio.NewUnidirectionalNATPacketConn(bufio.NewPacketConn(outPacketConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), originDestination)
} else {
outPacketConn = bufio.NewNATPacketConn(bufio.NewPacketConn(outPacketConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), originDestination)
}
}
if natConn, loaded := common.Cast[bufio.NATPacketConn](conn); loaded {
natConn.UpdateDestination(destinationAddress)
}
}
switch metadata.Protocol {
case C.ProtocolSTUN:
ctx, conn = canceler.NewPacketConn(ctx, conn, C.STUNTimeout)
case C.ProtocolQUIC:
ctx, conn = canceler.NewPacketConn(ctx, conn, C.QUICTimeout)
case C.ProtocolDNS:
ctx, conn = canceler.NewPacketConn(ctx, conn, C.DNSTimeout)
}
return bufio.CopyPacketConn(ctx, conn, bufio.NewPacketConn(outPacketConn))
}
func CopyEarlyConn(ctx context.Context, conn net.Conn, serverConn net.Conn) error {
if cachedReader, isCached := conn.(N.CachedReader); isCached {
payload := cachedReader.ReadCached()
if payload != nil && !payload.IsEmpty() {
_, err := serverConn.Write(payload.Bytes())
payload.Release()
if err != nil {
serverConn.Close()
return err
}
return bufio.CopyConn(ctx, conn, serverConn)
}
}
if earlyConn, isEarlyConn := common.Cast[N.EarlyConn](serverConn); isEarlyConn && earlyConn.NeedHandshake() {
payload := buf.NewPacket()
err := conn.SetReadDeadline(time.Now().Add(C.ReadPayloadTimeout))
if err != os.ErrInvalid {
if err != nil {
payload.Release()
serverConn.Close()
return err
}
_, err = payload.ReadOnceFrom(conn)
if err != nil && !E.IsTimeout(err) {
payload.Release()
serverConn.Close()
return E.Cause(err, "read payload")
}
err = conn.SetReadDeadline(time.Time{})
if err != nil {
payload.Release()
serverConn.Close()
return err
}
}
_, err = serverConn.Write(payload.Bytes())
payload.Release()
if err != nil {
serverConn.Close()
return N.ReportHandshakeFailure(conn, err)
}
}
return bufio.CopyConn(ctx, conn, serverConn)
}
+1
View File
@@ -10,6 +10,7 @@ const (
ProtocolDTLS = "dtls"
ProtocolSSH = "ssh"
ProtocolRDP = "rdp"
ProtocolNTP = "ntp"
)
const (
+15 -2
View File
@@ -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,
}
+13
View File
@@ -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
View File
@@ -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
View File
@@ -110,8 +110,8 @@ github.com/sagernet/quic-go v0.48.1-beta.1/go.mod h1:1WgdDIVD1Gybp40JTWketeSfKA/
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc=
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/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=
+3 -2
View File
@@ -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
+1 -1
View File
@@ -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
}
+32 -27
View File
@@ -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)
}
}
+10 -12
View File
@@ -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
View File
@@ -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
View File
@@ -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:
+2
View File
@@ -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
View File
@@ -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
+2 -2
View File
@@ -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
+5 -5
View File
@@ -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'
+19 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+4 -3
View File
@@ -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
+4
View File
@@ -14,6 +14,10 @@ case "$(arch)" in
v2ray_arch="arm64-v8a"
v2raya_arch="arm64"
;;
riscv64)
v2ray_arch="riscv64"
v2raya_arch="riscv64"
;;
*)
;;
esac
+1 -1
View File
@@ -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>
+2 -2
View File
@@ -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
View File
@@ -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)
+1
View File
@@ -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
+1 -1
View File
@@ -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)"
}
]
+1 -1
View File
@@ -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'",
+3 -1
View File
@@ -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()
+1
View File
@@ -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:
+21 -1
View File
@@ -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 », lalternative à 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))
+4 -1
View File
@@ -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 = {
+11 -14
View File
@@ -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,
+26 -14
View File
@@ -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',