diff --git a/.github/update.log b/.github/update.log index 90c28fae14..74fc8b5915 100644 --- a/.github/update.log +++ b/.github/update.log @@ -720,3 +720,4 @@ Update On Sun Jul 28 20:32:24 CEST 2024 Update On Mon Jul 29 20:33:10 CEST 2024 Update On Tue Jul 30 20:33:50 CEST 2024 Update On Wed Jul 31 20:30:50 CEST 2024 +Update On Thu Aug 1 20:31:07 CEST 2024 diff --git a/clash-nyanpasu/backend/Cargo.lock b/clash-nyanpasu/backend/Cargo.lock index 375a159641..7978791f7c 100644 --- a/clash-nyanpasu/backend/Cargo.lock +++ b/clash-nyanpasu/backend/Cargo.lock @@ -744,7 +744,7 @@ dependencies = [ "bitflags 2.6.0", "boa_interner", "boa_macros", - "indexmap 2.2.6", + "indexmap 2.3.0", "num-bigint", "rustc-hash 2.0.0", ] @@ -770,7 +770,7 @@ dependencies = [ "fast-float", "hashbrown 0.14.5", "icu_normalizer", - "indexmap 2.2.6", + "indexmap 2.3.0", "intrusive-collections", "itertools 0.13.0", "num-bigint", @@ -816,7 +816,7 @@ dependencies = [ "boa_gc", "boa_macros", "hashbrown 0.14.5", - "indexmap 2.2.6", + "indexmap 2.3.0", "once_cell", "phf 0.11.2", "rustc-hash 2.0.0", @@ -1145,9 +1145,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.11" +version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35723e6a11662c2afb578bcf0b88bf6ea8e21282a953428f240574fcc3a2b5b3" +checksum = "0fbb260a053428790f3de475e304ff84cdbc4face759ea7a3e64c1edd938a7fc" dependencies = [ "clap_builder", "clap_derive", @@ -1155,9 +1155,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.11" +version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49eb96cbfa7cfa35017b7cd548c75b14c3118c98b423041d70562665e07fb0fa" +checksum = "64b17d7ea74e9f833c7dbf2cbe4fb12ff26783eda4782a8975b72f895c9b4d99" dependencies = [ "anstream", "anstyle", @@ -1167,9 +1167,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.11" +version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d029b67f89d30bbb547c89fd5161293c0aec155fc691d7924b64550662db93e" +checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -1219,7 +1219,7 @@ dependencies = [ "hex", "humansize", "image 0.25.2", - "indexmap 2.2.6", + "indexmap 2.3.0", "log", "md-5", "mlua", @@ -1277,7 +1277,7 @@ dependencies = [ "windows-sys 0.59.0", "winreg 0.52.0", "wry", - "zip 2.1.5", + "zip 2.1.6", "zip-extensions", ] @@ -2844,7 +2844,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.2.6", + "indexmap 2.3.0", "slab", "tokio", "tokio-util", @@ -2863,7 +2863,7 @@ dependencies = [ "futures-core", "futures-sink", "http 1.1.0", - "indexmap 2.2.6", + "indexmap 2.3.0", "slab", "tokio", "tokio-util", @@ -3413,9 +3413,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.6" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "de3fc2e30ba82dd1b3911c8de1ffc143c74a914a14e99514d7637e3099df5ea0" dependencies = [ "equivalent", "hashbrown 0.14.5", @@ -3891,7 +3891,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "windows-targets 0.48.5", ] [[package]] @@ -5062,7 +5062,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.2.6", + "indexmap 2.3.0", ] [[package]] @@ -5265,7 +5265,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016" dependencies = [ "base64 0.22.1", - "indexmap 2.2.6", + "indexmap 2.3.0", "quick-xml 0.32.0", "serde", "time", @@ -6220,7 +6220,7 @@ version = "1.0.121" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ab380d7d9f22ef3f21ad3e6c1ebe8e4fc7a2000ccba2e4d71fc96f15b2cb609" dependencies = [ - "indexmap 2.2.6", + "indexmap 2.3.0", "itoa 1.0.11", "memchr", "ryu", @@ -6279,7 +6279,7 @@ dependencies = [ "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.2.6", + "indexmap 2.3.0", "serde", "serde_derive", "serde_json", @@ -6317,7 +6317,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.2.6", + "indexmap 2.3.0", "itoa 1.0.11", "ryu", "serde", @@ -7527,7 +7527,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.2.6", + "indexmap 2.3.0", "serde", "serde_spanned", "toml_datetime", @@ -7540,7 +7540,7 @@ version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" dependencies = [ - "indexmap 2.2.6", + "indexmap 2.3.0", "toml_datetime", "winnow 0.5.40", ] @@ -7551,7 +7551,7 @@ version = "0.22.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d9f8729f5aea9562aac1cc0441f5d6de3cff1ee0c5d67293eeca5eb36ee7c16" dependencies = [ - "indexmap 2.2.6", + "indexmap 2.3.0", "serde", "serde_spanned", "toml_datetime", @@ -9306,9 +9306,9 @@ dependencies = [ [[package]] name = "zip" -version = "2.1.5" +version = "2.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b895748a3ebcb69b9d38dcfdf21760859a4b0d0b0015277640c2ef4c69640e6f" +checksum = "40dd8c92efc296286ce1fbd16657c5dbefff44f1b4ca01cc5f517d8b7b3d3e2e" dependencies = [ "aes", "arbitrary", @@ -9320,7 +9320,7 @@ dependencies = [ "displaydoc", "flate2", "hmac", - "indexmap 2.2.6", + "indexmap 2.3.0", "lzma-rs", "memchr", "pbkdf2", @@ -9339,7 +9339,7 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "386508a00aae1d8218b9252a41f59bba739ccee3f8e420bb90bcb1c30d960d4a" dependencies = [ - "zip 2.1.5", + "zip 2.1.6", ] [[package]] diff --git a/clash-nyanpasu/backend/tauri/src/config/nyanpasu/mod.rs b/clash-nyanpasu/backend/tauri/src/config/nyanpasu/mod.rs index 3ac691cb21..ee8744bdb1 100644 --- a/clash-nyanpasu/backend/tauri/src/config/nyanpasu/mod.rs +++ b/clash-nyanpasu/backend/tauri/src/config/nyanpasu/mod.rs @@ -10,7 +10,7 @@ pub use self::clash_strategy::{ClashStrategy, ExternalControllerPortStrategy}; pub use logging::LoggingLevel; // TODO: when support sing-box, remove this struct -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)] pub enum ClashCore { #[serde(rename = "clash", alias = "clash-premium")] ClashPremium, @@ -88,6 +88,15 @@ impl TryFrom<&nyanpasu_utils::core::CoreType> for ClashCore { } } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, Default)] +#[serde(rename_all = "snake_case")] +pub enum ProxiesSelectorMode { + Hidden, + #[default] + Normal, + Submenu, +} + /// ### `verge.yaml` schema #[derive(Default, Debug, Clone, Deserialize, Serialize)] pub struct IVerge { @@ -198,7 +207,7 @@ pub struct IVerge { pub clash_strategy: Option, /// 是否启用代理托盘选择 - pub clash_tray_selector: Option, + pub clash_tray_selector: Option, } #[derive(Default, Debug, Clone, Deserialize, Serialize)] @@ -292,7 +301,7 @@ impl IVerge { // auto_log_clean: Some(60 * 24 * 7), // 7 days 自动清理日记 max_log_files: Some(7), // 7 days enable_auto_check_update: Some(true), - clash_tray_selector: Some(true), + clash_tray_selector: Some(ProxiesSelectorMode::default()), enable_service_mode: Some(false), ..Self::default() } diff --git a/clash-nyanpasu/backend/tauri/src/config/profile/item.rs b/clash-nyanpasu/backend/tauri/src/config/profile/item.rs index 0be104d5ef..824a85c097 100644 --- a/clash-nyanpasu/backend/tauri/src/config/profile/item.rs +++ b/clash-nyanpasu/backend/tauri/src/config/profile/item.rs @@ -1,7 +1,7 @@ use crate::{ config::Config, enhance::ScriptType, - utils::{dirs, help, tmpl}, + utils::{dirs, help}, }; use anyhow::{bail, Context, Result}; use reqwest::StatusCode; @@ -150,12 +150,12 @@ impl ProfileItem { Some(ProfileItemType::Merge) => { let name = item.name.unwrap_or("Merge".into()); let desc = item.desc.unwrap_or("".into()); - ProfileItem::from_merge(name, desc) + ProfileItem::from_merge(name, desc, file_data) } Some(ProfileItemType::Script(script_type)) => { let name = item.name.unwrap_or("Script".into()); let desc = item.desc.unwrap_or("".into()); - ProfileItem::from_script(name, desc, script_type) + ProfileItem::from_script(name, desc, script_type, file_data) } None => bail!("could not find the item type"), } @@ -178,7 +178,7 @@ impl ProfileItem { desc: Some(desc), file: Some(file), updated: Some(chrono::Local::now().timestamp() as usize), - file_data: Some(file_data.unwrap_or(tmpl::ITEM_LOCAL.into())), + file_data, ..Default::default() }) } @@ -347,7 +347,11 @@ impl ProfileItem { /// ## Merge type (enhance) /// create the enhanced item by using `merge` rule - pub fn from_merge(name: String, desc: String) -> Result { + pub fn from_merge( + name: String, + desc: String, + file_data: Option, + ) -> Result { let uid = help::get_uid("m"); let file = format!("{uid}.yaml"); @@ -358,14 +362,19 @@ impl ProfileItem { desc: Some(desc), file: Some(file), updated: Some(chrono::Local::now().timestamp() as usize), - file_data: Some(tmpl::ITEM_MERGE.into()), + file_data, ..Default::default() }) } /// ## Script type (enhance) /// create the enhanced item by using javascript quick.js - pub fn from_script(name: String, desc: String, script_type: ScriptType) -> Result { + pub fn from_script( + name: String, + desc: String, + script_type: ScriptType, + file_data: Option, + ) -> Result { let uid = help::get_uid("s"); let file = match script_type { ScriptType::JavaScript => format!("{uid}.js"), // js ext @@ -379,7 +388,7 @@ impl ProfileItem { desc: Some(desc), file: Some(file), updated: Some(chrono::Local::now().timestamp() as usize), - file_data: Some(tmpl::ITEM_SCRIPT.into()), + file_data, ..Default::default() }) } diff --git a/clash-nyanpasu/backend/tauri/src/config/runtime.rs b/clash-nyanpasu/backend/tauri/src/config/runtime.rs index 48f981e175..b0c7c50acb 100644 --- a/clash-nyanpasu/backend/tauri/src/config/runtime.rs +++ b/clash-nyanpasu/backend/tauri/src/config/runtime.rs @@ -1,6 +1,6 @@ +use indexmap::IndexMap; use serde::{Deserialize, Serialize}; use serde_yaml::Mapping; -use std::collections::HashMap; use crate::enhance::Logs; @@ -10,7 +10,7 @@ pub struct IRuntime { // 记录在配置中(包括merge和script生成的)出现过的keys // 这些keys不一定都生效 pub exists_keys: Vec, - pub chain_logs: HashMap, + pub chain_logs: IndexMap, } impl IRuntime { diff --git a/clash-nyanpasu/backend/tauri/src/core/migration/mod.rs b/clash-nyanpasu/backend/tauri/src/core/migration/mod.rs index 075df6d40b..1a64feb69d 100644 --- a/clash-nyanpasu/backend/tauri/src/core/migration/mod.rs +++ b/clash-nyanpasu/backend/tauri/src/core/migration/mod.rs @@ -103,6 +103,10 @@ pub trait Migration<'a>: DynClone { fn migrate(&self) -> std::io::Result<()> { unimplemented!() } + + fn discard(&self) -> std::io::Result<()> { + Ok(()) + } } clone_trait_object!(Migration<'_>); @@ -268,7 +272,18 @@ impl Runner<'_> { Ok(()) } Err(e) => { - eprintln!("Migration {} failed: {}", name, e); + eprintln!( + "Migration {} failed: {}; trying to discard changes", + name, e + ); + match migration.discard() { + Ok(_) => { + eprintln!("Migration {} discarded.", name); + } + Err(e) => { + eprintln!("Migration {} discard failed: {}", name, e); + } + } store.set_state(Cow::Owned(name.to_string()), MigrationState::Failed); Err(e) } diff --git a/clash-nyanpasu/backend/tauri/src/core/migration/units/unit_160.rs b/clash-nyanpasu/backend/tauri/src/core/migration/units/unit_160.rs index 4ba3796410..be0c2cec12 100644 --- a/clash-nyanpasu/backend/tauri/src/core/migration/units/unit_160.rs +++ b/clash-nyanpasu/backend/tauri/src/core/migration/units/unit_160.rs @@ -1,11 +1,15 @@ +use std::borrow::Cow; + use once_cell::sync::Lazy; +use serde_yaml::Mapping; use crate::{ config::RUNTIME_CONFIG, core::migration::{DynMigration, Migration}, }; -pub static UNITS: Lazy> = Lazy::new(|| vec![MigrateAppHomeDir.into()]); +pub static UNITS: Lazy> = + Lazy::new(|| vec![MigrateAppHomeDir.into(), MigrateProxiesSelectorMode.into()]); pub static VERSION: Lazy = Lazy::new(|| semver::Version::parse("1.6.0").unwrap()); @@ -109,4 +113,142 @@ impl<'a> Migration<'a> for MigrateAppHomeDir { println!("Migration completed"); Ok(()) } + + #[allow(deprecated)] + fn discard(&self) -> std::io::Result<()> { + let home_dir = crate::utils::dirs::app_home_dir().unwrap(); + let app_config_dir = crate::utils::dirs::app_config_dir().unwrap(); + let app_data_dir = crate::utils::dirs::app_data_dir().unwrap(); + if !home_dir.exists() { + std::fs::create_dir_all(&home_dir) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?; + } + let file_opts = fs_extra::file::CopyOptions::default().skip_exist(true); + let dir_opts = fs_extra::dir::CopyOptions::default() + .skip_exist(true) + .content_only(true); + if home_dir != app_config_dir { + // move profiles.yaml + let path = app_config_dir.join("profiles.yaml"); + if path.exists() { + println!("Moving profiles.yaml to home dir"); + fs_extra::file::move_file(path, home_dir.join("profiles.yaml"), &file_opts) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?; + } + // move profiles dir + let path = crate::utils::dirs::app_profiles_dir().unwrap(); + if path.exists() { + println!("Moving profiles dir to home dir"); + fs_extra::dir::move_dir(path, home_dir.join("profiles"), &dir_opts) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?; + } + // move other files and dirs to home dir + println!("Moving other files and dirs to home dir"); + fs_extra::dir::move_dir(app_data_dir, &home_dir, &dir_opts) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?; + } + // move nyanpasu config + let path = app_config_dir.join(crate::utils::dirs::NYANPASU_CONFIG); + if path.exists() { + println!("Moving verge.yaml to home dir"); + fs_extra::file::move_file(path, home_dir.join("verge.yaml"), &file_opts) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?; + } + // move clash guard overrides + let path = crate::utils::dirs::clash_guard_overrides_path().unwrap(); + if path.exists() { + println!("Moving config.yaml to home dir"); + fs_extra::file::move_file(path, home_dir.join("config.yaml"), &file_opts) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?; + } + // move clash runtime config + let path = app_config_dir.join(RUNTIME_CONFIG); + if path.exists() { + println!("Moving clash-verge.yaml to home dir"); + fs_extra::file::move_file(path, home_dir.join("clash-verge.yaml"), &file_opts) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?; + } + println!("Migration discarded"); + Ok(()) + } +} + +#[derive(Debug, Clone)] +pub struct MigrateProxiesSelectorMode; +impl<'a> Migration<'a> for MigrateProxiesSelectorMode { + fn version(&self) -> &'a semver::Version { + &VERSION + } + + fn name(&self) -> std::borrow::Cow<'a, str> { + Cow::Borrowed("Migrate Proxies Selector Mode") + } + + fn migrate(&self) -> std::io::Result<()> { + let config_path = crate::utils::dirs::nyanpasu_config_path().unwrap(); + if !config_path.exists() { + println!("Config file not found, skipping migration"); + return Ok(()); + } + println!("parse config file..."); + let config = std::fs::read_to_string(&config_path) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?; + let mut config: Mapping = serde_yaml::from_str(&config) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?; + let mode = config.get_mut("clash_tray_selector"); + match mode { + None => { + println!("clash_tray_selector not found, skipping migration"); + return Ok(()); + } + Some(mode) => { + if mode.is_bool() { + println!("detected old mode, migrating..."); + let value = mode.as_bool().unwrap(); + let value = if value { "normal" } else { "hidden" }; + *mode = serde_yaml::Value::from(value); + println!("write config file..."); + let config = serde_yaml::to_string(&config) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?; + std::fs::write(&config_path, config)?; + } + println!("Migration completed"); + } + } + Ok(()) + } + + fn discard(&self) -> std::io::Result<()> { + let config_path = crate::utils::dirs::nyanpasu_config_path().unwrap(); + if !config_path.exists() { + println!("Config file not found, skipping migration"); + return Ok(()); + } + println!("parse config file..."); + let config = std::fs::read_to_string(&config_path) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?; + let mut config: Mapping = serde_yaml::from_str(&config) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?; + let mode = config.get_mut("clash_tray_selector"); + match mode { + None => { + println!("clash_tray_selector not found, skipping migration"); + return Ok(()); + } + Some(mode) => { + if mode.is_string() { + println!("detected new mode, migrating..."); + let value = mode.as_str().unwrap(); + let value = value == "normal"; + *mode = serde_yaml::Value::from(value); + println!("write config file..."); + let config = serde_yaml::to_string(&config) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?; + std::fs::write(&config_path, config)?; + } + println!("Migration discarded"); + } + } + Ok(()) + } } diff --git a/clash-nyanpasu/backend/tauri/src/core/tray/proxies.rs b/clash-nyanpasu/backend/tauri/src/core/tray/proxies.rs index 359099248f..3b42c1bae2 100644 --- a/clash-nyanpasu/backend/tauri/src/core/tray/proxies.rs +++ b/clash-nyanpasu/backend/tauri/src/core/tray/proxies.rs @@ -1,5 +1,5 @@ use crate::{ - config::Config, + config::{nyanpasu::ProxiesSelectorMode, Config}, core::{ clash::proxies::{Proxies, ProxiesGuard, ProxiesGuardExt}, handle::Handle, @@ -162,8 +162,11 @@ pub async fn proxies_updated_receiver() { } Handle::mutate_proxies(); { - let is_tray_selector_enabled = - Config::verge().latest().clash_tray_selector.unwrap_or(true); + let is_tray_selector_enabled = Config::verge() + .latest() + .clash_tray_selector + .unwrap_or_default() + != ProxiesSelectorMode::Hidden; if !is_tray_selector_enabled { continue; } @@ -209,8 +212,12 @@ pub fn setup_proxies() { mod platform_impl { use super::{ProxySelectAction, TrayProxyItem}; - use crate::core::{clash::proxies::ProxiesGuard, handle::Handle}; + use crate::{ + config::nyanpasu::ProxiesSelectorMode, + core::{clash::proxies::ProxiesGuard, handle::Handle}, + }; use base64::{engine::general_purpose::STANDARD as base64_standard, Engine as _}; + use rust_i18n::t; use tauri::{CustomMenuItem, SystemTrayMenu, SystemTrayMenuItem, SystemTraySubmenu}; use tracing::warn; @@ -256,20 +263,30 @@ mod platform_impl { } pub fn setup_tray(menu: &mut SystemTrayMenu) -> SystemTrayMenu { - let mut menu = menu.to_owned(); - let is_tray_selector_enabled = crate::config::Config::verge() + let mut parent_menu = menu.to_owned(); + let selector_mode = crate::config::Config::verge() .latest() .clash_tray_selector - .unwrap_or(true); - if !is_tray_selector_enabled { - return menu; - } - // TODO: support submenu - menu = menu.add_native_item(SystemTrayMenuItem::Separator); + .unwrap_or_default(); + let mut menu = match selector_mode { + ProxiesSelectorMode::Hidden => return parent_menu, + ProxiesSelectorMode::Normal => { + parent_menu = parent_menu.add_native_item(SystemTrayMenuItem::Separator); + parent_menu.clone() + } + ProxiesSelectorMode::Submenu => SystemTrayMenu::new(), + }; let proxies = ProxiesGuard::global().read().inner().to_owned(); let mode = crate::utils::config::get_current_clash_mode(); let tray_proxies = super::to_tray_proxies(mode.as_str(), &proxies); - generate_selectors(&menu, &tray_proxies) + menu = generate_selectors(&menu, &tray_proxies); + if selector_mode == ProxiesSelectorMode::Submenu { + parent_menu = + parent_menu.add_submenu(SystemTraySubmenu::new(t!("tray.select_proxies"), menu)); + parent_menu + } else { + menu + } } pub fn update_selected_proxies(actions: &[ProxySelectAction]) { diff --git a/clash-nyanpasu/backend/tauri/src/enhance/mod.rs b/clash-nyanpasu/backend/tauri/src/enhance/mod.rs index 7a4e8b0d57..f3aec16a5e 100644 --- a/clash-nyanpasu/backend/tauri/src/enhance/mod.rs +++ b/clash-nyanpasu/backend/tauri/src/enhance/mod.rs @@ -8,13 +8,14 @@ mod utils; pub use self::chain::ScriptType; use self::{chain::*, field::*, merge::*, script::*, tun::*}; use crate::config::Config; +use indexmap::IndexMap; use serde_yaml::Mapping; -use std::collections::{HashMap, HashSet}; +use std::collections::HashSet; pub use utils::{Logs, LogsExt}; /// Enhance mode /// 返回最终配置、该配置包含的键、和script执行的结果 -pub async fn enhance() -> (Mapping, Vec, HashMap) { +pub async fn enhance() -> (Mapping, Vec, IndexMap) { // config.yaml 的配置 let clash_config = { Config::clash().latest().0.clone() }; @@ -61,7 +62,7 @@ pub async fn enhance() -> (Mapping, Vec, HashMap) { (current_mapping, profile_spec_chains, valid) }; - let mut result_map = HashMap::new(); // 保存脚本日志 + let mut result_map = IndexMap::new(); // 保存脚本日志 let mut exists_keys = use_keys(&config); // 保存出现过的keys let valid = use_valid_fields(valid); diff --git a/clash-nyanpasu/backend/tauri/src/enhance/tun.rs b/clash-nyanpasu/backend/tauri/src/enhance/tun.rs index b72823ac92..5bfd17f6ef 100644 --- a/clash-nyanpasu/backend/tauri/src/enhance/tun.rs +++ b/clash-nyanpasu/backend/tauri/src/enhance/tun.rs @@ -1,5 +1,7 @@ use serde_yaml::{Mapping, Value}; +use crate::config::{nyanpasu::ClashCore, Config}; + macro_rules! revise { ($map: expr, $key: expr, $val: expr) => { let ret_key = Value::String($key.into()); @@ -31,10 +33,21 @@ pub fn use_tun(mut config: Mapping, enable: bool) -> Mapping { revise!(tun_val, "enable", enable); if enable { - append!(tun_val, "stack", "gvisor"); - append!(tun_val, "dns-hijack", vec!["any:53"]); - append!(tun_val, "auto-route", true); - append!(tun_val, "auto-detect-interface", true); + let core = { + *Config::verge() + .latest() + .clash_core + .as_ref() + .unwrap_or(&ClashCore::default()) + }; + if core == ClashCore::ClashRs { + append!(tun_val, "device-id", "dev://utun1989"); + } else { + append!(tun_val, "stack", "gvisor"); + append!(tun_val, "dns-hijack", vec!["any:53"]); + append!(tun_val, "auto-route", true); + append!(tun_val, "auto-detect-interface", true); + } } revise!(config, "tun", tun_val); diff --git a/clash-nyanpasu/backend/tauri/src/ipc.rs b/clash-nyanpasu/backend/tauri/src/ipc.rs index 9e1ca8b4ea..0dcc3b9165 100644 --- a/clash-nyanpasu/backend/tauri/src/ipc.rs +++ b/clash-nyanpasu/backend/tauri/src/ipc.rs @@ -13,9 +13,10 @@ use crate::{ }; use anyhow::{Context, Result}; use chrono::Local; +use indexmap::IndexMap; use log::debug; use serde_yaml::Mapping; -use std::collections::{HashMap, VecDeque}; +use std::collections::VecDeque; use sysproxy::Sysproxy; use tauri::api::dialog::FileDialogBuilder; @@ -173,7 +174,7 @@ pub fn get_runtime_exists() -> CmdResult> { } #[tauri::command] -pub fn get_runtime_logs() -> CmdResult> { +pub fn get_runtime_logs() -> CmdResult> { Ok(Config::runtime().latest().chain_logs.clone()) } diff --git a/clash-nyanpasu/backend/tauri/src/utils/mod.rs b/clash-nyanpasu/backend/tauri/src/utils/mod.rs index b88411187c..5cfbecbd2e 100644 --- a/clash-nyanpasu/backend/tauri/src/utils/mod.rs +++ b/clash-nyanpasu/backend/tauri/src/utils/mod.rs @@ -5,7 +5,6 @@ pub mod dirs; pub mod help; pub mod init; pub mod resolve; -pub mod tmpl; // mod winhelp; pub mod downloader; #[cfg(windows)] diff --git a/clash-nyanpasu/backend/tauri/src/utils/tmpl.rs b/clash-nyanpasu/backend/tauri/src/utils/tmpl.rs deleted file mode 100644 index 9d2060c512..0000000000 --- a/clash-nyanpasu/backend/tauri/src/utils/tmpl.rs +++ /dev/null @@ -1,35 +0,0 @@ -//! Some config file template -/// template for new a profile item -pub const ITEM_LOCAL: &str = "# Profile Template for clash verge - -proxies: - -proxy-groups: - -rules: -"; - -/// enhanced profile -pub const ITEM_MERGE: &str = "# Merge Template for clash verge -# The `Merge` format used to enhance profile - -prepend-rules: - -prepend-proxies: - -prepend-proxy-groups: - -append-rules: - -append-proxies: - -append-proxy-groups: -"; - -/// enhanced profile -pub const ITEM_SCRIPT: &str = "// Define the `main` function - -function main(params) { - return params; -} -"; diff --git a/clash-nyanpasu/frontend/interface/service/types.ts b/clash-nyanpasu/frontend/interface/service/types.ts index 906433f920..d35e6183ae 100644 --- a/clash-nyanpasu/frontend/interface/service/types.ts +++ b/clash-nyanpasu/frontend/interface/service/types.ts @@ -41,7 +41,7 @@ export interface VergeConfig { enable_clash_fields?: boolean; enable_builtin_enhanced?: boolean; proxy_layout_column?: number; - clash_tray_selector?: boolean; + clash_tray_selector?: "normal" | "hidden" | "submenu"; clash_strategy?: { external_controller_port_strategy: "fixed" | "random" | "allow_fallback"; }; @@ -75,11 +75,53 @@ export namespace Profile { items?: Item[]; } - export type ScriptType = "javascript" | "lua"; + export const Template = { + merge: `# Clash Nyanpasu Merge Template (YAML) +# Documentation on https://nyanpasu.elaina.moe/ +# Set the default merge strategy to recursive merge. +# Enable the old mode with the override__ prefix. +# Use the filter__ prefix to filter lists (removing unwanted content). +# All prefixes should support accessing maps or lists with a.b.c syntax. +`, + javascript: `// Clash Nyanpasu JavaScript Template +// Documentation on https://nyanpasu.elaina.moe/ + +/** @type {config} */ +export default function (profile) { + return profile; +} +`, + luascript: `-- Clash Nyanpasu Lua Script Template +-- Documentation on https://nyanpasu.elaina.moe/ + +return config; +`, + profile: `# Clash Nyanpasu Profile Template +# Documentation on https://nyanpasu.elaina.moe/ + +proxies: + +proxy-groups: + +rules: +`, + }; + + export const Type = { + Local: "local", + Remote: "remote", + Merge: "merge", + JavaScript: { + script: "javascript", + }, + LuaScript: { + script: "lua", + }, + } as const; export interface Item { uid: string; - type?: "local" | "remote" | "merge" | { script: ScriptType }; + type?: (typeof Type)[keyof typeof Type]; name?: string; desc?: string; file?: string; diff --git a/clash-nyanpasu/frontend/nyanpasu/package.json b/clash-nyanpasu/frontend/nyanpasu/package.json index 8ec8fd8c1f..10747697c9 100644 --- a/clash-nyanpasu/frontend/nyanpasu/package.json +++ b/clash-nyanpasu/frontend/nyanpasu/package.json @@ -56,7 +56,7 @@ "@vitejs/plugin-react-swc": "3.7.0", "clsx": "2.1.1", "sass": "1.77.8", - "shiki": "1.12.0", + "shiki": "1.12.1", "tailwindcss-textshadow": "2.1.3", "vite": "5.3.5", "vite-plugin-monaco-editor": "1.1.3", diff --git a/clash-nyanpasu/frontend/nyanpasu/src/components/profiles/modules/chain-item.tsx b/clash-nyanpasu/frontend/nyanpasu/src/components/profiles/modules/chain-item.tsx new file mode 100644 index 0000000000..cd709faf9f --- /dev/null +++ b/clash-nyanpasu/frontend/nyanpasu/src/components/profiles/modules/chain-item.tsx @@ -0,0 +1,105 @@ +import { memo, useState, useTransition } from "react"; +import { useTranslation } from "react-i18next"; +import { Menu as MenuIcon } from "@mui/icons-material"; +import { LoadingButton } from "@mui/lab"; +import { alpha, ListItemButton, Menu, MenuItem, useTheme } from "@mui/material"; +import { Profile, useClash } from "@nyanpasu/interface"; +import { cleanDeepClickEvent } from "@nyanpasu/ui"; + +export const ChainItem = memo(function ChainItem({ + item, + selected, + onClick, + onChainEdit, +}: { + item: Profile.Item; + selected?: boolean; + onClick: () => Promise; + onChainEdit: () => void; +}) { + const { t } = useTranslation(); + + const { palette } = useTheme(); + + const { deleteProfile } = useClash(); + + const [isPending, startTransition] = useTransition(); + + const handleClick = () => { + startTransition(onClick); + }; + + const [anchorEl, setAnchorEl] = useState(null); + + const menuMapping = { + Apply: () => handleClick(), + "Edit Info": () => onChainEdit(), + Delete: () => deleteProfile(item.uid), + }; + + const handleMenuClick = (func: () => void) => { + setAnchorEl(null); + func(); + }; + + return ( + <> + +
+ {item.name} +
+ + { + cleanDeepClickEvent(e); + setAnchorEl(e.currentTarget); + }} + loading={isPending} + > + + +
+ + setAnchorEl(null)} + > + {Object.entries(menuMapping).map(([key, func], index) => { + return ( + { + cleanDeepClickEvent(e); + handleMenuClick(func); + }} + > + {t(key)} + + ); + })} + + + ); +}); + +export default ChainItem; diff --git a/clash-nyanpasu/frontend/nyanpasu/src/components/profiles/modules/language-chip.tsx b/clash-nyanpasu/frontend/nyanpasu/src/components/profiles/modules/language-chip.tsx new file mode 100644 index 0000000000..d6519e0338 --- /dev/null +++ b/clash-nyanpasu/frontend/nyanpasu/src/components/profiles/modules/language-chip.tsx @@ -0,0 +1,25 @@ +import { alpha, useTheme } from "@mui/material"; +import { Profile } from "@nyanpasu/interface"; +import { getLanguage } from "../utils"; + +export const LanguageChip = ({ type }: { type: Profile.Item["type"] }) => { + const { palette } = useTheme(); + + const lang = getLanguage(type, true); + + return ( + lang && ( +
+ {lang} +
+ ) + ); +}; + +export default LanguageChip; diff --git a/clash-nyanpasu/frontend/nyanpasu/src/components/profiles/modules/side-chain.tsx b/clash-nyanpasu/frontend/nyanpasu/src/components/profiles/modules/side-chain.tsx index 2a36fa68c5..324c2df9e9 100644 --- a/clash-nyanpasu/frontend/nyanpasu/src/components/profiles/modules/side-chain.tsx +++ b/clash-nyanpasu/frontend/nyanpasu/src/components/profiles/modules/side-chain.tsx @@ -1,127 +1,79 @@ import { useLockFn } from "ahooks"; -import { memo } from "react"; -import { Add, Edit } from "@mui/icons-material"; -import { - alpha, - IconButton, - ListItemButton, - ListItemIcon, - ListItemText, - useTheme, -} from "@mui/material"; +import { useAtomValue } from "jotai"; +import { Add } from "@mui/icons-material"; +import { alpha, ListItemButton, useTheme } from "@mui/material"; import { Profile, useClash } from "@nyanpasu/interface"; import { filterProfiles } from "../utils"; - -const ChainItem = memo(function ChainItem({ - name, - desc, - selected, - onClick, - onChainEdit, -}: { - name?: string; - desc?: string; - selected?: boolean; - onClick: () => void; - onChainEdit: () => void; -}) { - const { palette } = useTheme(); - - return ( - - - - { - e.preventDefault(); - e.stopPropagation(); - onChainEdit(); - }} - > - - - - ); -}); +import ChainItem from "./chain-item"; +import { atomChainsSelected, atomGlobalChainCurrent } from "./store"; export interface SideChainProps { - global?: boolean; - profile?: Profile.Item; onChainEdit: (item?: Profile.Item) => void | Promise; } -export const SideChain = ({ global, profile, onChainEdit }: SideChainProps) => { +export const SideChain = ({ onChainEdit }: SideChainProps) => { const { palette } = useTheme(); - const { getProfiles, setProfilesConfig, setProfiles } = useClash(); + const isGlobalChainCurrent = useAtomValue(atomGlobalChainCurrent); + + const currnetProfile = useAtomValue(atomChainsSelected); + + const { + getProfiles, + setProfilesConfig, + setProfiles, + getRuntimeLogs: { mutate: mutateRuntimeLogs }, + } = useClash(); const { scripts } = filterProfiles(getProfiles.data?.items); const handleChainClick = useLockFn(async (uid: string) => { - const chains = global + const chains = isGlobalChainCurrent ? (getProfiles.data?.chain ?? []) - : (profile?.chains ?? []); + : (currnetProfile?.chains ?? []); const updatedChains = chains.includes(uid) ? chains.filter((chain) => chain !== uid) : [...chains, uid]; - if (global) { + if (isGlobalChainCurrent) { await setProfilesConfig({ chain: updatedChains }); } else { await setProfiles(uid, { chains: updatedChains }); } + + mutateRuntimeLogs(); }); return (
{scripts?.map((item, index) => { - const selected = global + const selected = isGlobalChainCurrent ? getProfiles.data?.chain?.includes(item.uid) - : profile?.chains?.includes(item.uid); + : currnetProfile?.chains?.includes(item.uid); return ( handleChainClick(item.uid)} + onClick={async () => await handleChainClick(item.uid)} onChainEdit={() => onChainEdit(item)} /> ); })} onChainEdit()} > - - - + - +
New Chain
); diff --git a/clash-nyanpasu/frontend/nyanpasu/src/components/profiles/modules/store.ts b/clash-nyanpasu/frontend/nyanpasu/src/components/profiles/modules/store.ts new file mode 100644 index 0000000000..77ac08996b --- /dev/null +++ b/clash-nyanpasu/frontend/nyanpasu/src/components/profiles/modules/store.ts @@ -0,0 +1,6 @@ +import { atom } from "jotai"; +import type { Profile } from "@nyanpasu/interface"; + +export const atomGlobalChainCurrent = atom(false); + +export const atomChainsSelected = atom(); diff --git a/clash-nyanpasu/frontend/nyanpasu/src/components/profiles/profile-item.tsx b/clash-nyanpasu/frontend/nyanpasu/src/components/profiles/profile-item.tsx index a780f5487d..69f9661755 100644 --- a/clash-nyanpasu/frontend/nyanpasu/src/components/profiles/profile-item.tsx +++ b/clash-nyanpasu/frontend/nyanpasu/src/components/profiles/profile-item.tsx @@ -107,10 +107,18 @@ export const ProfileItem = memo(function ProfileItem({ await deleteConnections(); } catch (err) { - useMessage(`Error setting profile: \n ${JSON.stringify(err)}`, { - title: t("Error"), - type: "error", - }); + const is_fetch_error = err instanceof Error && err.name === "FetchError"; + useMessage( + is_fetch_error + ? t("FetchError", { + content: t("Subscription"), + }) + : `Error setting profile: \n ${err instanceof Error ? err.message : String(err)}`, + { + title: t("Error"), + type: "error", + }, + ); } finally { setLoading({ card: false }); } diff --git a/clash-nyanpasu/frontend/nyanpasu/src/components/profiles/profile-monaco-view.tsx b/clash-nyanpasu/frontend/nyanpasu/src/components/profiles/profile-monaco-view.tsx index e77add606b..0d93b69721 100644 --- a/clash-nyanpasu/frontend/nyanpasu/src/components/profiles/profile-monaco-view.tsx +++ b/clash-nyanpasu/frontend/nyanpasu/src/components/profiles/profile-monaco-view.tsx @@ -1,4 +1,4 @@ -import { useAsyncEffect, useLockFn, useUpdateEffect } from "ahooks"; +import { useAsyncEffect, useUpdateEffect } from "ahooks"; import { useAtomValue } from "jotai"; import { forwardRef, useImperativeHandle, useRef } from "react"; import { monaco } from "@/services/monaco"; @@ -23,12 +23,16 @@ export const ProfileMonacoView = forwardRef(function ProfileMonacoView( const monacoRef = useRef(null); + const monacoeditorRef = useRef(null); + const instanceRef = useRef(null); useAsyncEffect(async () => { if (open) { const { monaco } = await import("@/services/monaco"); + monacoeditorRef.current = monaco; + if (!monacoRef.current) { return; } @@ -49,21 +53,25 @@ export const ProfileMonacoView = forwardRef(function ProfileMonacoView( getValue: () => instanceRef.current?.getValue(), })); - const changeLanguage = useLockFn(async () => { - const { monaco } = await import("@/services/monaco"); + useUpdateEffect(() => { + const model = instanceRef.current?.getModel(); - const text = instanceRef.current?.getModel(); - - if (!text || !language) { + if (!model || !language) { return; } - monaco.editor.setModelLanguage(text, language); - }); + monacoeditorRef.current?.editor.setModelLanguage(model, language); + }, [language]); useUpdateEffect(() => { - changeLanguage(); - }, [language]); + const model = instanceRef.current?.getModel(); + + if (!model || !value) { + return; + } + + model.setValue(value); + }, [value]); return
; }); diff --git a/clash-nyanpasu/frontend/nyanpasu/src/components/profiles/profile-side.tsx b/clash-nyanpasu/frontend/nyanpasu/src/components/profiles/profile-side.tsx index c92264ac28..76c2b40a67 100644 --- a/clash-nyanpasu/frontend/nyanpasu/src/components/profiles/profile-side.tsx +++ b/clash-nyanpasu/frontend/nyanpasu/src/components/profiles/profile-side.tsx @@ -1,5 +1,6 @@ import { Allotment } from "allotment"; import "allotment/dist/style.css"; +import { useAtomValue } from "jotai"; import { useState } from "react"; import { useTranslation } from "react-i18next"; import { Close } from "@mui/icons-material"; @@ -7,23 +8,26 @@ import { IconButton } from "@mui/material"; import { Profile } from "@nyanpasu/interface"; import { SideChain } from "./modules/side-chain"; import { SideLog } from "./modules/side-log"; +import { atomChainsSelected, atomGlobalChainCurrent } from "./modules/store"; import { ScriptDialog } from "./script-dialog"; export interface ProfileSideProps { - profile?: Profile.Item; - global?: boolean; onClose: () => void; } -export const ProfileSide = ({ profile, global, onClose }: ProfileSideProps) => { +export const ProfileSide = ({ onClose }: ProfileSideProps) => { const { t } = useTranslation(); const [open, setOpen] = useState(false); const [item, setItem] = useState(); - const handleEditChain = async (item?: Profile.Item) => { - setItem(item); + const isGlobalChainCurrent = useAtomValue(atomGlobalChainCurrent); + + const currentProfile = useAtomValue(atomChainsSelected); + + const handleEditChain = async (_item?: Profile.Item) => { + setItem(_item); setOpen(true); }; @@ -34,7 +38,9 @@ export const ProfileSide = ({ profile, global, onClose }: ProfileSideProps) => {
{t("Proxy Chains")}
- {global ? t("Global Proxy Chains") : profile?.name} + {isGlobalChainCurrent + ? t("Global Proxy Chains") + : currentProfile?.name}
@@ -46,11 +52,7 @@ export const ProfileSide = ({ profile, global, onClose }: ProfileSideProps) => {
- + @@ -61,7 +63,7 @@ export const ProfileSide = ({ profile, global, onClose }: ProfileSideProps) => { { setOpen(false); setItem(undefined); diff --git a/clash-nyanpasu/frontend/nyanpasu/src/components/profiles/script-dialog.tsx b/clash-nyanpasu/frontend/nyanpasu/src/components/profiles/script-dialog.tsx index 117f7f4fc1..8862ac9e36 100644 --- a/clash-nyanpasu/frontend/nyanpasu/src/components/profiles/script-dialog.tsx +++ b/clash-nyanpasu/frontend/nyanpasu/src/components/profiles/script-dialog.tsx @@ -1,22 +1,60 @@ import { useAsyncEffect, useReactive } from "ahooks"; -import { isEqual } from "lodash-es"; -import { useRef } from "react"; +import { useEffect, useRef, useState } from "react"; import { SelectElement, TextFieldElement, useForm } from "react-hook-form-mui"; import { useTranslation } from "react-i18next"; import { Divider } from "@mui/material"; import { Profile, useClash } from "@nyanpasu/interface"; import { BaseDialog, BaseDialogProps } from "@nyanpasu/ui"; +import LanguageChip from "./modules/language-chip"; import { ProfileMonacoView, ProfileMonacoViewRef } from "./profile-monaco-view"; +import { getLanguage } from "./utils"; + +const formCommonProps = { + autoComplete: "off", + autoCorrect: "off", + fullWidth: true, +}; + +const optionTypeMapping = [ + { + id: "js", + value: Profile.Type.JavaScript, + language: "javascript", + label: "JavaScript", + }, + { + id: "lua", + value: Profile.Type.LuaScript, + language: "lua", + label: "LuaScript", + }, + { + id: "merge", + value: Profile.Type.Merge, + language: "yaml", + label: "Merge", + }, +]; + +const convertTypeMapping = (data: Profile.Item) => { + optionTypeMapping.forEach((option) => { + if (option.id === data.type) { + data.type = option.value; + } + }); + + return data; +}; export interface ScriptDialogProps extends Omit { open: boolean; onClose: () => void; - item?: Profile.Item; + profile?: Profile.Item; } export const ScriptDialog = ({ open, - item, + profile, onClose, ...props }: ScriptDialogProps) => { @@ -25,121 +63,120 @@ export const ScriptDialog = ({ const { getProfileFile, setProfileFile, createProfile, setProfiles } = useClash(); - const optionTypeMapping = [ - { - id: "js", - value: { script: "javascript" }, - language: "javascript", - label: t("JavaScript"), - }, - { - id: "lua", - value: { script: "lua" }, - language: "lua", - label: t("LuaScript"), - }, - { - id: "merge", - value: "merge", - language: "yaml", - label: t("Merge"), - }, - ]; + const form = useForm(); - const preprocessing = () => { - const result = optionTypeMapping.find((option) => - isEqual(option.value, item?.type), - ); + const isEdit = Boolean(profile); - return { ...item, type: result?.id } as Profile.Item; - }; + useEffect(() => { + if (isEdit) { + form.reset(profile); + } else { + form.reset({ + type: "merge", + chains: [], + name: "New Script", + desc: "", + }); + } + }, [isEdit]); - const { control, watch, handleSubmit, reset } = useForm({ - defaultValues: item - ? preprocessing() - : { - type: "merge", - chains: [], - name: "New Script", - desc: "", - }, - }); + const [openMonaco, setOpenMonaco] = useState(false); const profileMonacoViewRef = useRef(null); - const editor = useReactive({ - value: "", - language: "javascript", + const editor = useReactive<{ + value: string; + language: string; + rawType: Profile.Item["type"]; + }>({ + value: Profile.Template.merge, + language: "yaml", + rawType: "merge", }); - const handleTypeChange = () => { - const language = optionTypeMapping.find((option) => - isEqual(option.id, watch("type")), - )?.language; + const onSubmit = form.handleSubmit(async (data) => { + convertTypeMapping(data); - if (language) { - editor.language = language; + const editorValue = profileMonacoViewRef.current?.getValue(); + + if (!editorValue) { + return; } - }; - - const isEdit = Boolean(item); - - const commonProps = { - autoComplete: "off", - autoCorrect: "off", - fullWidth: true, - }; - - const onSubmit = handleSubmit(async (form) => { - const value = profileMonacoViewRef.current?.getValue() || ""; - - const type = optionTypeMapping.find((option) => - isEqual(option.id, form.type), - )?.value; - - const data = { - ...form, - type, - } as Profile.Item; try { if (isEdit) { await setProfiles(data.uid, data); - await setProfileFile(data.uid, value); + await setProfileFile(data.uid, editorValue); } else { - await createProfile(data, value); + await createProfile(data, editorValue); } - - setTimeout(() => reset(), 300); - - onClose(); } finally { + onClose(); } }); useAsyncEffect(async () => { - editor.value = await getProfileFile(item?.uid); - - if (item) { - reset(item); + if (isEdit) { + editor.value = await getProfileFile(profile?.uid); + editor.language = getLanguage(profile?.type)!; } else { - reset(); + editor.value = Profile.Template.merge; + editor.language = "yaml"; } + + setOpenMonaco(open); }, [open]); + const handleTypeChange = () => { + const data = form.getValues(); + + editor.rawType = convertTypeMapping(data).type; + + const lang = getLanguage(editor.rawType); + + if (!lang) { + return; + } + + editor.language = lang; + + switch (lang) { + case "yaml": { + editor.value = Profile.Template.merge; + break; + } + + case "lua": { + editor.value = Profile.Template.luascript; + break; + } + + case "javascript": { + editor.value = Profile.Template.javascript; + break; + } + } + }; + return ( + {isEdit ? "Edit Script" : "New Script"} + + +
+ } open={open} onClose={() => onClose()} onOk={onSubmit} divider contentStyle={{ - height: "80vh", - width: "90vw", + overflow: "hidden", padding: 0, }} + full {...props} >
@@ -149,8 +186,8 @@ export const ScriptDialog = ({ @@ -170,8 +207,8 @@ export const ScriptDialog = ({ @@ -183,7 +220,7 @@ export const ScriptDialog = ({ diff --git a/clash-nyanpasu/frontend/nyanpasu/src/components/profiles/utils.ts b/clash-nyanpasu/frontend/nyanpasu/src/components/profiles/utils.ts index bd9108add0..ba834ffca2 100644 --- a/clash-nyanpasu/frontend/nyanpasu/src/components/profiles/utils.ts +++ b/clash-nyanpasu/frontend/nyanpasu/src/components/profiles/utils.ts @@ -1,3 +1,4 @@ +import { isEqual } from "lodash-es"; import { Profile } from "@nyanpasu/interface"; export const filterProfiles = (items?: Profile.Item[]) => { @@ -21,12 +22,12 @@ export const filterProfiles = (items?: Profile.Item[]) => { }); }; - const profiles = getItems(["local", "remote"]); + const profiles = getItems([Profile.Type.Local, Profile.Type.Remote]); const scripts = getItems([ - "merge", - { script: "javascript" }, - { script: "lua" }, + Profile.Type.Merge, + Profile.Type.JavaScript, + Profile.Type.LuaScript, ]); return { @@ -34,3 +35,25 @@ export const filterProfiles = (items?: Profile.Item[]) => { scripts, }; }; + +export const getLanguage = (type: Profile.Item["type"], snake?: boolean) => { + switch (true) { + case isEqual(type, Profile.Type.JavaScript): + case isEqual(type, Profile.Type.JavaScript.script): { + return snake ? "JavaScript" : "javascript"; + } + + case isEqual(type, Profile.Type.LuaScript): + case isEqual(type, Profile.Type.LuaScript.script): { + return snake ? "Lua" : "lua"; + } + + case isEqual(type, Profile.Type.Merge): { + return snake ? "YAML" : "yaml"; + } + + default: { + return; + } + } +}; diff --git a/clash-nyanpasu/frontend/nyanpasu/src/components/setting/setting-nyanpasu-misc.tsx b/clash-nyanpasu/frontend/nyanpasu/src/components/setting/setting-nyanpasu-misc.tsx index 79e678b63e..1972bfb12c 100644 --- a/clash-nyanpasu/frontend/nyanpasu/src/components/setting/setting-nyanpasu-misc.tsx +++ b/clash-nyanpasu/frontend/nyanpasu/src/components/setting/setting-nyanpasu-misc.tsx @@ -20,6 +20,12 @@ export const SettingNyanpasuMisc = () => { silent: "Silent", }; + const trayProxiesSelectorMode = { + normal: t("Normal"), + hidden: t("Hidden"), + submenu: t("Submenu"), + }; + return ( @@ -32,6 +38,17 @@ export const SettingNyanpasuMisc = () => { } /> + + setNyanpasuConfig({ + clash_tray_selector: value as "normal" | "hidden" | "submenu", + }) + } + /> + { {...createBooleanProps("enable_builtin_enhanced")} /> - - { const { profiles } = filterProfiles(getProfiles.data?.items); - const [globalChain, setGlobalChain] = useState(false); + const [globalChain, setGlobalChain] = useAtom(atomGlobalChainCurrent); + + const [chainsSelected, setChainsSelected] = useAtom(atomChainsSelected); const handleGlobalChainClick = () => { setChainsSelected(undefined); setGlobalChain(!globalChain); }; - const [chainsSelected, setChainsSelected] = useState(); - const onClickChains = (profile: Profile.Item) => { setGlobalChain(false); @@ -61,11 +65,7 @@ export const ProfilePage = () => { sideClassName="!overflow-visible" side={ (globalChain || chainsSelected) && ( - + ) } > diff --git a/clash-nyanpasu/frontend/nyanpasu/src/utils/index.ts b/clash-nyanpasu/frontend/nyanpasu/src/utils/index.ts index f4c8b9d37d..105b45c784 100644 --- a/clash-nyanpasu/frontend/nyanpasu/src/utils/index.ts +++ b/clash-nyanpasu/frontend/nyanpasu/src/utils/index.ts @@ -29,6 +29,10 @@ export const containsSearchTerm = (obj: any, term: string): boolean => { return false; }; +export function formatError(err: unknown): string { + return `Error: ${err instanceof Error ? err.message : String(err)}`; +} + export function formatEnvInfos(envs: EnvInfos) { let result = "----------- System -----------\n"; result += `OS: ${envs.os}\n`; diff --git a/clash-nyanpasu/locales/en.json b/clash-nyanpasu/locales/en.json index 4ec384f155..160db0c19a 100644 --- a/clash-nyanpasu/locales/en.json +++ b/clash-nyanpasu/locales/en.json @@ -6,6 +6,7 @@ "ps": "Copy Env (PS)", "sh": "Copy Env (sh)" }, + "select_proxies": "Select Proxies", "dashboard": "Dashboard", "direct_mode": "Direct Mode", "global_mode": "Global Mode", diff --git a/clash-nyanpasu/locales/zh.json b/clash-nyanpasu/locales/zh.json index a2893372bc..def2f75c7c 100644 --- a/clash-nyanpasu/locales/zh.json +++ b/clash-nyanpasu/locales/zh.json @@ -6,6 +6,7 @@ "ps": "复制环境变量(PS)", "sh": "复制环境变量(sh)" }, + "select_proxies": "选择代理", "dashboard": "打开面板", "direct_mode": "直连模式", "global_mode": "全局模式", diff --git a/clash-nyanpasu/manifest/version.json b/clash-nyanpasu/manifest/version.json index dd1a05789e..206875b633 100644 --- a/clash-nyanpasu/manifest/version.json +++ b/clash-nyanpasu/manifest/version.json @@ -2,7 +2,7 @@ "manifest_version": 1, "latest": { "mihomo": "v1.18.7", - "mihomo_alpha": "alpha-117cdd8", + "mihomo_alpha": "alpha-e7e1400", "clash_rs": "v0.1.20", "clash_premium": "2023-09-05-gdcc8d87" }, @@ -36,5 +36,5 @@ "darwin-x64": "clash-darwin-amd64-n{}.gz" } }, - "updated_at": "2024-07-29T22:20:59.937Z" + "updated_at": "2024-07-31T22:20:11.102Z" } diff --git a/clash-nyanpasu/package.json b/clash-nyanpasu/package.json index 4b66b4bfa4..5d216786b7 100644 --- a/clash-nyanpasu/package.json +++ b/clash-nyanpasu/package.json @@ -88,7 +88,7 @@ "stylelint-order": "6.0.4", "stylelint-scss": "6.4.1", "tailwindcss": "3.4.7", - "tsx": "4.16.2", + "tsx": "4.16.5", "typescript": "5.5.4" }, "packageManager": "pnpm@9.6.0", diff --git a/clash-nyanpasu/pnpm-lock.yaml b/clash-nyanpasu/pnpm-lock.yaml index cdb8325d6a..4a38330812 100644 --- a/clash-nyanpasu/pnpm-lock.yaml +++ b/clash-nyanpasu/pnpm-lock.yaml @@ -140,8 +140,8 @@ importers: specifier: 3.4.7 version: 3.4.7 tsx: - specifier: 4.16.2 - version: 4.16.2 + specifier: 4.16.5 + version: 4.16.5 typescript: specifier: 5.5.4 version: 5.5.4 @@ -307,8 +307,8 @@ importers: specifier: 1.77.8 version: 1.77.8 shiki: - specifier: 1.12.0 - version: 1.12.0 + specifier: 1.12.1 + version: 1.12.1 tailwindcss-textshadow: specifier: 2.1.3 version: 2.1.3 @@ -441,8 +441,8 @@ importers: specifier: 2.23.2 version: 2.23.2 undici: - specifier: 6.19.4 - version: 6.19.4 + specifier: 6.19.5 + version: 6.19.5 packages: @@ -1669,8 +1669,8 @@ packages: cpu: [x64] os: [win32] - '@shikijs/core@1.12.0': - resolution: {integrity: sha512-mc1cLbm6UQ8RxLc0dZES7v5rkH+99LxQp/ZvTqV3NLyYsO/fD6JhEflP1H5b2SDq9gI0+0G36AVZWxvounfR9w==} + '@shikijs/core@1.12.1': + resolution: {integrity: sha512-biCz/mnkMktImI6hMfMX3H9kOeqsInxWEyCHbSlL8C/2TR1FqfmGxTLRNwYCKsyCyxWLbB8rEqXRVZuyxuLFmA==} '@sindresorhus/is@4.6.0': resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} @@ -5289,8 +5289,8 @@ packages: shell-quote@1.8.1: resolution: {integrity: sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==} - shiki@1.12.0: - resolution: {integrity: sha512-BuAxWOm5JhRcbSOl7XCei8wGjgJJonnV0oipUupPY58iULxUGyHhW5CF+9FRMuM1pcJ5cGEJGll1LusX6FwpPA==} + shiki@1.12.1: + resolution: {integrity: sha512-nwmjbHKnOYYAe1aaQyEBHvQymJgfm86ZSS7fT8OaPRr4sbAcBNz7PbfAikMEFSDQ6se2j2zobkXvVKcBOm0ysg==} side-channel@1.0.6: resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} @@ -5644,8 +5644,8 @@ packages: tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} - tsx@4.16.2: - resolution: {integrity: sha512-C1uWweJDgdtX2x600HjaFaucXTilT7tgUZHbOE4+ypskZ1OP8CRCSDkCxG6Vya9EwaFIVagWwpaVAn5wzypaqQ==} + tsx@4.16.5: + resolution: {integrity: sha512-ArsiAQHEW2iGaqZ8fTA1nX0a+lN5mNTyuGRRO6OW3H/Yno1y9/t1f9YOI1Cfoqz63VAthn++ZYcbDP7jPflc+A==} engines: {node: '>=18.0.0'} hasBin: true @@ -5716,8 +5716,8 @@ packages: resolution: {integrity: sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==} engines: {node: '>=14.0'} - undici@6.19.4: - resolution: {integrity: sha512-i3uaEUwNdkRq2qtTRRJb13moW5HWqviu7Vl7oYRYz++uPtGHJj+x7TGjcEuwS5Mt2P4nA0U9dhIX3DdB6JGY0g==} + undici@6.19.5: + resolution: {integrity: sha512-LryC15SWzqQsREHIOUybavaIHF5IoL0dJ9aWWxL/PgT1KfqAW5225FZpDUFlt9xiDMS2/S7DOKhFWA7RLksWdg==} engines: {node: '>=18.17'} unicorn-magic@0.1.0: @@ -6892,8 +6892,8 @@ snapshots: '@babel/runtime': 7.24.8 '@mui/base': 5.0.0-beta.40(react-dom@19.0.0-rc-df783f9ea1-20240708(react@19.0.0-rc-df783f9ea1-20240708))(react@19.0.0-rc-df783f9ea1-20240708)(types-react@19.0.0-rc.1) '@mui/material': 5.16.6(@emotion/react@11.13.0(react@19.0.0-rc-df783f9ea1-20240708)(types-react@19.0.0-rc.1))(@emotion/styled@11.13.0(@emotion/react@11.13.0(react@19.0.0-rc-df783f9ea1-20240708)(types-react@19.0.0-rc.1))(react@19.0.0-rc-df783f9ea1-20240708)(types-react@19.0.0-rc.1))(react-dom@19.0.0-rc-df783f9ea1-20240708(react@19.0.0-rc-df783f9ea1-20240708))(react@19.0.0-rc-df783f9ea1-20240708)(types-react@19.0.0-rc.1) - '@mui/system': 5.16.5(@emotion/react@11.13.0(react@19.0.0-rc-df783f9ea1-20240708)(types-react@19.0.0-rc.1))(@emotion/styled@11.13.0(@emotion/react@11.13.0(react@19.0.0-rc-df783f9ea1-20240708)(types-react@19.0.0-rc.1))(react@19.0.0-rc-df783f9ea1-20240708)(types-react@19.0.0-rc.1))(react@19.0.0-rc-df783f9ea1-20240708)(types-react@19.0.0-rc.1) - '@mui/utils': 5.16.5(react@19.0.0-rc-df783f9ea1-20240708)(types-react@19.0.0-rc.1) + '@mui/system': 5.16.6(@emotion/react@11.13.0(react@19.0.0-rc-df783f9ea1-20240708)(types-react@19.0.0-rc.1))(@emotion/styled@11.13.0(@emotion/react@11.13.0(react@19.0.0-rc-df783f9ea1-20240708)(types-react@19.0.0-rc.1))(react@19.0.0-rc-df783f9ea1-20240708)(types-react@19.0.0-rc.1))(react@19.0.0-rc-df783f9ea1-20240708)(types-react@19.0.0-rc.1) + '@mui/utils': 5.16.6(react@19.0.0-rc-df783f9ea1-20240708)(types-react@19.0.0-rc.1) '@types/react-transition-group': 4.4.10 clsx: 2.1.1 prop-types: 15.8.1 @@ -7217,7 +7217,7 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.17.2': optional: true - '@shikijs/core@1.12.0': + '@shikijs/core@1.12.1': dependencies: '@types/hast': 3.0.4 @@ -11138,9 +11138,9 @@ snapshots: shell-quote@1.8.1: {} - shiki@1.12.0: + shiki@1.12.1: dependencies: - '@shikijs/core': 1.12.0 + '@shikijs/core': 1.12.1 '@types/hast': 3.0.4 side-channel@1.0.6: @@ -11604,7 +11604,7 @@ snapshots: tslib@2.6.2: {} - tsx@4.16.2: + tsx@4.16.5: dependencies: esbuild: 0.21.5 get-tsconfig: 4.7.5 @@ -11708,7 +11708,7 @@ snapshots: dependencies: '@fastify/busboy': 2.1.1 - undici@6.19.4: {} + undici@6.19.5: {} unicorn-magic@0.1.0: {} diff --git a/clash-nyanpasu/scripts/package.json b/clash-nyanpasu/scripts/package.json index a679402c65..a856b5e514 100644 --- a/clash-nyanpasu/scripts/package.json +++ b/clash-nyanpasu/scripts/package.json @@ -20,6 +20,6 @@ "picocolors": "1.0.1", "tar": "7.4.3", "telegram": "2.23.2", - "undici": "6.19.4" + "undici": "6.19.5" } } diff --git a/gost/.github/workflows/buildx.yaml b/gost/.github/workflows/buildx.yaml index 0ea67d2c6f..6207e7f888 100644 --- a/gost/.github/workflows/buildx.yaml +++ b/gost/.github/workflows/buildx.yaml @@ -19,7 +19,7 @@ jobs: echo ::set-output name=version::snapshot fi - echo ::set-output name=docker_platforms::linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8,linux/386,linux/s390x + echo ::set-output name=docker_platforms::linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8,linux/386,linux/s390x,linux/riscv64 echo ::set-output name=docker_image::${{ secrets.DOCKER_USERNAME }}/${{ github.event.repository.name }} # https://github.com/crazy-max/ghaction-docker-buildx diff --git a/gost/cmd/gost/cfg.go b/gost/cmd/gost/cfg.go index dddbdf9ff6..83a836501d 100644 --- a/gost/cmd/gost/cfg.go +++ b/gost/cmd/gost/cfg.go @@ -106,6 +106,7 @@ func parseUsers(authFile string) (users []*url.Userinfo, err error) { if err != nil { return } + defer file.Close() scanner := bufio.NewScanner(file) for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) @@ -175,7 +176,7 @@ func parseIP(s string, port string) (ips []string) { } return } - + defer file.Close() scanner := bufio.NewScanner(file) for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) diff --git a/lede/package/kernel/linux/modules/netdevices.mk b/lede/package/kernel/linux/modules/netdevices.mk index 3f681e24b9..bc98e85d1d 100644 --- a/lede/package/kernel/linux/modules/netdevices.mk +++ b/lede/package/kernel/linux/modules/netdevices.mk @@ -382,8 +382,8 @@ define KernelPackage/phy-aquantia TITLE:=Aquantia Ethernet PHYs DEPENDS:=+kmod-libphy +kmod-hwmon-core +kmod-lib-crc-ccitt KCONFIG:=CONFIG_AQUANTIA_PHY - FILES:=$(LINUX_DIR)/drivers/net/phy/aquantia.ko@lt6.6 \ - $(LINUX_DIR)/drivers/net/phy/aquantia/aquantia.ko@ge6.6 + FILES:=$(LINUX_DIR)/drivers/net/phy/aquantia.ko@lt6.1 \ + $(LINUX_DIR)/drivers/net/phy/aquantia/aquantia.ko@ge6.1 AUTOLOAD:=$(call AutoLoad,18,aquantia,1) endef @@ -731,8 +731,7 @@ define KernelPackage/r8169 DEPENDS:=@PCI_SUPPORT +kmod-mii +r8169-firmware +kmod-phy-realtek +!LINUX_5_4:kmod-mdio-devres KCONFIG:= \ CONFIG_R8169 \ - CONFIG_R8169_NAPI=y \ - CONFIG_R8169_VLAN=n + CONFIG_R8169_LEDS=y@ge6.6 FILES:=$(LINUX_DIR)/drivers/net/ethernet/realtek/r8169.ko AUTOLOAD:=$(call AutoProbe,r8169) endef diff --git a/lede/package/lean/leigod-acc/Makefile b/lede/package/lean/leigod-acc/Makefile new file mode 100644 index 0000000000..65b57ce24f --- /dev/null +++ b/lede/package/lean/leigod-acc/Makefile @@ -0,0 +1,58 @@ +# +# Copyright (C) 2015-2016 OpenWrt.org +# +# This is free software, licensed under the GNU General Public License v3. +# + +include $(TOPDIR)/rules.mk + +PKG_NAME:=leigod-acc +PKG_VERSION:=1.3.0.30 +PKG_RELEASE:=1 + +include $(INCLUDE_DIR)/package.mk + +define Package/$(PKG_NAME) + SECTION:=net + CATEGORY:=Network + TITLE:=Powerful Leigod Game Accelerater + DEPENDS:=@(x86_64||arm||mipsel||aarch64) + URL:=https://www.leigod.com/ +endef + +define Package/$(PKG_NAME)/description +leigod-acc is a powerful game accelerater +endef + +ifeq ($(ARCH),x86_64) + acc-arch:=amd64 +endif +ifeq ($(ARCH),mipsel) + acc-arch:=mipsle +endif +ifeq ($(ARCH),arm) + acc-arch:=arm +endif +ifeq ($(ARCH),aarch64) + acc-arch:=arm64 +endif + +define Build/Prepare +endef + +define Build/Configure +endef + +define Build/Compile +endef + +define Package/$(PKG_NAME)/install + $(INSTALL_DIR) $(1)/usr/sbin/leigod + $(INSTALL_BIN) ./files/acc-gw.linux.$(acc-arch) $(1)/usr/sbin/leigod/acc-gw.linux.$(acc-arch) + $(INSTALL_BIN) ./files/leigod_uninstall.sh $(1)/usr/sbin/leigod/leigod_uninstall.sh + $(INSTALL_BIN) ./files/plugin_common.sh $(1)/usr/sbin/leigod/plugin_common.sh + $(INSTALL_BIN) ./files/update_plugin $(1)/usr/sbin/leigod/update_plugin + $(INSTALL_DATA) ./files/version $(1)/usr/sbin/leigod/version +endef + +$(eval $(call BuildPackage,$(PKG_NAME))) diff --git a/lede/package/lean/leigod-acc/files/acc-gw.linux.amd64 b/lede/package/lean/leigod-acc/files/acc-gw.linux.amd64 new file mode 100644 index 0000000000..d4ddab9020 Binary files /dev/null and b/lede/package/lean/leigod-acc/files/acc-gw.linux.amd64 differ diff --git a/lede/package/lean/leigod-acc/files/acc-gw.linux.arm b/lede/package/lean/leigod-acc/files/acc-gw.linux.arm new file mode 100644 index 0000000000..7b72e853dc Binary files /dev/null and b/lede/package/lean/leigod-acc/files/acc-gw.linux.arm differ diff --git a/lede/package/lean/leigod-acc/files/acc-gw.linux.arm64 b/lede/package/lean/leigod-acc/files/acc-gw.linux.arm64 new file mode 100644 index 0000000000..7f9d692663 Binary files /dev/null and b/lede/package/lean/leigod-acc/files/acc-gw.linux.arm64 differ diff --git a/lede/package/lean/leigod-acc/files/acc-gw.linux.mipsle b/lede/package/lean/leigod-acc/files/acc-gw.linux.mipsle new file mode 100644 index 0000000000..d0fe35884e Binary files /dev/null and b/lede/package/lean/leigod-acc/files/acc-gw.linux.mipsle differ diff --git a/lede/package/lean/leigod-acc/files/leigod_uninstall.sh b/lede/package/lean/leigod-acc/files/leigod_uninstall.sh new file mode 100644 index 0000000000..cb9d5b5dc6 --- /dev/null +++ b/lede/package/lean/leigod-acc/files/leigod_uninstall.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +# this script is use to install leigod plugin +ver_name="version" +init_file_name="acc" +binary_prefix="acc-gw.linux" +common_file_name="plugin_common.sh" +download_base_url="http://119.3.40.126/router_plugin" + +# include common file +. ${common_file_name} + +# preinstall_check +# check and set env +preinstall_check + +if [ ${is_openwrt} ]; then + echo "remove openwrt config" + remove_openwrt_series_config + remove_openwrt_series_init +fi + +# remove_binary remove binary +remove_binary diff --git a/lede/package/lean/leigod-acc/files/plugin_common.sh b/lede/package/lean/leigod-acc/files/plugin_common.sh new file mode 100644 index 0000000000..a46c5f2c80 --- /dev/null +++ b/lede/package/lean/leigod-acc/files/plugin_common.sh @@ -0,0 +1,502 @@ +# this script is use to install leigod plugin +# sbin_dir="/usr/sbin/leigod" +ver_name="version" +init_file_name="acc" +binary_prefix="acc-gw.linux" +common_file_name="plugin_common.sh" +uninstall_file_name="leigod_uninstall.sh" +download_base_url="http://119.3.40.126/router_plugin" + +# get_device_os +# current support os: Linux +get_device_os() { + os=$(uname) + if [ $? == "0" ]; then + return 0 + fi + echo "os cant be get" + return -1 +} + +# get_device_arch, +# current support arch: arm64 arm x86_64 mips +get_device_arch() { + arch=$(uname -m) + if [ $? == "0" ]; then + return 0 + fi + echo "arch cant be get" + return -1 +} + +# get_xiaomi_name check if is xiaomi +get_xiaomi_name() { + local name=$(uci get misc.hardware.displayName) + if [[ $? == "0" && ${name} != "" ]]; then + echo "router is xiaomi series, name: ${name}" + sbin_dir="/userdisk/appdata/leigod" + init_dir="/userdisk/appdata/leigod" + is_xiaomi=true + return 0 + fi + local name=$(uci get misc.hardware.model) + if [[ $? == "0" && ${name} != "" ]]; then + echo "router is xiaomi series, name: ${name}" + sbin_dir="/userdisk/appdata/leigod" + init_dir="/userdisk/appdata/leigod" + is_xiaomi=true + return 0 + fi + + local name=$(uci get misc.hardware.model) + if [[ $? == "0" && ${name} != "" ]]; then + echo "router is xiaomi series, name: ${name}" + sbin_dir="/userdisk/appdata/leigod" + init_dir="/userdisk/appdata/leigod" + is_xiaomi=true + return 0 + fi + echo "router is not xiaomi, use general openwrt" + sbin_dir="/usr/sbin/leigod" + init_dir="/etc/init.d" + # stop openwrt service first + echo "stop openwrt acc service first, in casue install failed" + /etc/init.d/acc stop + sleep 1 + show_openwrt_suggestion + return 0 +} + +# get_asus_name get asus name +get_merlin_party() { + if [[ -d "/koolshare" ]]; then + echo "router is merlin series, name: $(nvram get build_name)" + nvram set 3rd-party=merlin + is_merlin=true + sbin_dir="/koolshare/leigod/acc" + init_dir="/koolshare/init.d" + echo "message" + return 0 + elif [[ -d "/jffs/softcenter" ]]; then + echo "router is swrt series, name: $(nvram get build_name)" + is_swrt=true + nvram set 3rd-party=swrt + sbin_dir="/jffs/softcenter/leigod/acc" + init_dir="/jffs/softcenter/init.d" + return 0 + fi + # check merlin + echo "route is not merlin, use general asus" + return 0 +} + +# get_device_firmware get device firmware +# current support firmware: openwrt merlin +get_device_firmware() { + # openwrt file exist + if [ -f "/etc/openwrt_release" ]; then + echo "firmware is openwrt series" + is_openwrt=true + get_xiaomi_name + elif [[ -f "/etc/image_version" ]] || [[ -d "/koolshare" ]] || [[ -d "/jffs/softcenter" ]]; then + echo "firmware is asus series" + is_asus=true + echo "stop asus acc service first, in casue install failed" + PIDS=$(ps | grep acc | grep -v grep | awk '{print $1}') + # check if process exist + get_merlin_party + fi +} + +# install_openwrt_package install openwrt +install_binary() { + # create sbin dir + mkdir -p ${sbin_dir} + # create name + local acc_name=${binary_prefix}.${arch} + local download_bin_url=${download_base_url}/${acc_name} + echo "install ${acc_name} to ${sbin_dir}" + # download file + curl -s -o ${sbin_dir}/${acc_name} ${download_bin_url} + if [ $? != "0" ]; then + echo "download and install binary failed" + return -1 + fi + echo "install binary success" + chmod +x ${sbin_dir}/${acc_name} + if [ $? != "0" ]; then + echo "add binary permission failed" + return -1 + fi + echo "add acc binary permission success" + # download common file + local download_common_url=${download_base_url}/${common_file_name} + curl -s -o ${sbin_dir}/${common_file_name} ${download_common_url} + if [ $? != "0" ]; then + echo "download and install common file failed" + return -1 + fi + + # remote uninstall_file_name + local remote_uninstall_file_name=${download_base_url}/"plugin_uninstall.sh" + curl -s -o ${sbin_dir}/${uninstall_file_name} ${remote_uninstall_file_name} + if [ $? != "0" ]; then + echo "download and install uninstall file failed" + return -1 + fi + echo "add uninstall permission success" + local ver_file=${sbin_dir}/${ver_name} + touch ${ver_file} + if [ $? != "0" ]; then + echo "create version file failed" + return -1 + fi + # add version to file + echo "version=1.3.0.30" > ${ver_file} + echo "add version file success" +} + +# remove_binary remove binary +remove_binary() { + rm -r ${sbin_dir} +} + +# install xiaomi monitor +install_xiaomi_monitor() { + local cron_path="/etc/crontabs/root" + local monitor_file_name="plugin_monitor.sh" + local download_monitor_url=${download_base_url}/${monitor_file_name} + curl -o ${sbin_dir}/${monitor_file_name} ${download_monitor_url} + if [ $? != "0" ]; then + echo "download monitor file failed" + return -1 + fi + chmod +x ${sbin_dir}/${monitor_file_name} + # download + echo "download monitor file success" + echo "*/1 * * * * ${sbin_dir}/${monitor_file_name}" >> ${cron_path} + if [ $? != "0" ]; then + echo "add monitor to cron failed" + return -1 + fi + echo "add monitor to cron success" +} + +# install_openwrt_series_config save openwrt config +install_openwrt_series_config() { + # create accelerator config + touch /etc/config/accelerator + if [ $? != "0" ]; then + echo "make acc config file failed" + return -1 + fi + if [ ${install_env} == "test" ]; then + # use uci to add config + uci set accelerator.base=system + uci set accelerator.bind=bind + uci set accelerator.device=hardware + uci set accelerator.Phone=acceleration + uci set accelerator.PC=acceleration + uci set accelerator.Game=acceleration + uci set accelerator.Unknown=acceleration + uci set accelerator.base.url='https://test-opapi.nn.com/speed/router/plug/check' + uci set accelerator.base.heart='https://test-opapi.nn.com/speed/router/heartbeat' + uci set accelerator.base.base_url='https://test-opapi.nn.com/speed' + uci commit accelerator + elif [ ${install_env} == "test1" ]; then + # use uci to add config + uci set accelerator.base=system + uci set accelerator.bind=bind + uci set accelerator.device=hardware + uci set accelerator.Phone=acceleration + uci set accelerator.PC=acceleration + uci set accelerator.Game=acceleration + uci set accelerator.Unknown=acceleration + uci set accelerator.base.url='https://test1-opapi.nn.com/speed/router/plug/check' + uci set accelerator.base.heart='https://test1-opapi.nn.com/speed/router/heartbeat' + uci set accelerator.base.base_url='https://test1-opapi.nn.com/speed' + uci commit accelerator + else + # use uci to add config + uci set accelerator.base=system + uci set accelerator.bind=bind + uci set accelerator.device=hardware + uci set accelerator.Phone=acceleration + uci set accelerator.PC=acceleration + uci set accelerator.Game=acceleration + uci set accelerator.Unknown=acceleration + uci set accelerator.base.url='https://opapi.nn.com/speed/router/plug/check' + uci set accelerator.base.heart='https://opapi.nn.com/speed/router/heartbeat' + uci set accelerator.base.base_url='https://opapi.nn.com/speed' + uci commit accelerator + fi + if [ $? != "0" ]; then + echo "create openwrt config unit failed" + return -1 + fi + echo "create openwrt config unit success" +} + +# install_openwrt_series_luasrc install openwrt lua src +install_openwrt_series_luasrc() { + lua_base="/usr/lib/lua/luci" + # download index file + curl --create-dirs -o ${lua_base}/controller/acc.lua ${download_base_url}/openwrt/controller/acc.lua + if [ $? != "0" ]; then + echo "download acc.lua failed" + return -1 + fi + # download service view file + curl --create-dirs -o ${lua_base}/model/cbi/leigod/service.lua ${download_base_url}/openwrt/model/cbi/leigod/service.lua + if [ $? != "0" ]; then + echo "download service.lua failed" + return -1 + fi + # download device view file + curl --create-dirs -o ${lua_base}/model/cbi/leigod/device.lua ${download_base_url}/openwrt/model/cbi/leigod/device.lua + if [ $? != "0" ]; then + echo "download device.lua failed" + return -1 + fi + # download notice view file + curl --create-dirs -o ${lua_base}/model/cbi/leigod/notice.lua ${download_base_url}/openwrt/model/cbi/leigod/notice.lua + if [ $? != "0" ]; then + echo "download notice.lua failed" + return -1 + fi + # download service view file + curl --create-dirs -o ${lua_base}/view/leigod/notice.htm ${download_base_url}/openwrt/view/leigod/notice.htm + if [ $? != "0" ]; then + echo "download notice.htm failed" + return -1 + fi + # download service view file + curl --create-dirs -o ${lua_base}/view/leigod/service.htm ${download_base_url}/openwrt/view/leigod/service.htm + if [ $? != "0" ]; then + echo "download service.htm failed" + return -1 + fi + # download service translate file + curl --create-dirs -o ${lua_base}/i18n/acc.zh-cn.lmo ${download_base_url}/openwrt/po/zh-cn/acc.zh-cn.lmo + if [ $? != "0" ]; then + echo "download acc.zh-cn.lmo failed" + return -1 + fi + echo "download lua src success" +} + +install_openwrt_series_web() { + local luci_base="/usr/lib/lua/luci" + +} + +# remove_openwrt_series_config remove openwrt config +remove_openwrt_series_config() { + rm /etc/config/accelerator +} + +# install asus series config +install_asus_series_config() { + mkdir -p ${sbin_dir}/config + if [ ${install_env} == "test" ]; then + # install test asus config + echo " +[base] +url="https://test-opapi.nn.com/speed/router/plug/check" +channel="2" +appid="nnMobile_d0k3duup" +heart="https://test-opapi.nn.com/speed/router/heartbeat" +base_url="https://test-opapi.nn.com/speed" + +[update] +domain="https://test-opapi.nn.com/nn-version/version/plug/upgrade" + +[device] + " > ${sbin_dir}/config/accelerator + elif [ ${install_env} == "test1" ]; then + # install test1 asus config + echo " +[base] +url="https://test1-opapi.nn.com/speed/router/plug/check" +channel="2" +appid="nnMobile_d0k3duup" +heart="https://test1-opapi.nn.com/speed/router/heartbeat" +base_url="https://test1-opapi.nn.com/speed" + +[update] +domain="https://test1-opapi.nn.com/nn-version/version/plug/upgrade" + +[device] + " > ${sbin_dir}/config/accelerator + else + # install formel asus config + echo " +[base] +url="https://opapi.nn.com/speed/router/plug/check" +channel="2" +appid="nnMobile_d0k3duup" +heart="https://opapi.nn.com/speed/router/heartbeat" +base_url="https://opapi.nn.com/speed" + +[update] +domain="https://opapi.nn.com/nn-version/version/plug/upgrade" + +[device] + " > ${sbin_dir}/config/accelerator + + fi + + echo "create asus series config success" +} + +# remove_asus_series_config remove asus config +remove_asus_series_config() { + rm -r ${init_dir} +} + +# install_openwrt_init install openwrt to init +install_openwrt_series_init() { + local remote_init_name="openwrt_init.sh" + local download_init_url=${download_base_url}/${remote_init_name} + # download init file + curl -o ${init_dir}/${init_file_name} ${download_init_url} + if [ $? != "0" ]; then + echo "download init file failed" + return -1 + fi + echo "download init file success" + # add permission to file + chmod +x ${init_dir}/${init_file_name} + if [ $? != "0" ]; then + echo "add init permission failed" + return -1 + fi + echo "add init file permission success" + ${init_dir}/${init_file_name} enable + echo "set accelerator autostart success" + ${init_dir}/${init_file_name} start + if [ $? != "0" ]; then + echo "start accelerator failed" + return -1 + fi + echo "start accelerator success" +} + +# remove_openwrt_series_init remove openwrt init +remove_openwrt_series_init() { + ${init_dir}/${init_file_name} disable + ${init_dir}/${init_file_name} stop + rm ${init_dir}/${init_file_name} +} + +# install merlin init +install_merlin_init() { + local remote_init_name="asus_init.sh" + local download_init_url=${download_base_url}/${remote_init_name} + # download init file + curl -o ${sbin_dir}/${init_file_name} ${download_init_url} + if [ $? != "0" ]; then + echo "download init file failed" + return -1 + fi + # add permission + chmod +x ${sbin_dir}/${init_file_name} + # create link + local link_init_name="S99LeigodAcc.sh" + local link_init_file=${init_dir}/${link_init_name} + ln -sf ${sbin_dir}/${init_file_name} ${link_init_file} + if [ $? != "0" ]; then + echo "create merlin init link failed" + return -1 + fi + echo "create merlin link file success" + ${link_init_file} start + echo "acc start success" +} + +# show_openwrt_suggestion show openwrt install suggest +show_openwrt_suggestion() { + echo " + 雷神OpenWrt插件安装建议: + + 当前雷神路由器支持两种加速模式, + 1. tproxy加速模式(速度更快, CPU占用率更低) + 2. tun加速模式(需要依赖少, 安装灵活) + + 需要您根据以上的加速模式, 安装对应的依赖库, + 如下列出两种模式对应的安装依赖: + 1. TProxy模式: libpcap iptables kmod-ipt-nat iptables-mod-tproxy kmod-ipt-tproxy kmod-netem(可选) tc-full(可选) kmod-ipt-ipset ipset curl + 2. Tun模式: libpcap iptables kmod-tun kmod-ipt-nat kmod-ipt-ipset ipset curl + + 如何安装依赖: + 1. 升级依赖: opkg update + 2. 安装依赖: opkg install xxx + + 为了安装方便, 请选择一个模式, 复制以下命令到终端运行: + Tproxy模式: + opkg update + opkg install libpcap iptables kmod-ipt-nat iptables-mod-tproxy kmod-ipt-tproxy kmod-netem tc-full kmod-ipt-ipset ipset + + Tun模式: + opkg update + opkg install libpcap iptables kmod-tun kmod-ipt-nat kmod-ipt-ipset ipset curl + + 关于steamdeck的支持说明 + steamdeck设备请选择加速电脑游戏 + + 关于手机设备的支持: + 1. 安卓支持说明 + 当前代理仅支持ipv4代理, 请更改dhcp配置,更改完配置请重启路由器, + 配置路径在 /etc/config/dhcp + config dhcp 'lan' + ... 此处是一些其他配置 + ra 'disable' + dhcpv6 'disable' + list ra_flags 'none' + ... 此处是一些其他配置 + + 2. 关于ios设备的支持说明 + ios设备, 安装完插件后, 为了精准识别, 请在ios上选择忘记wifi, 然后重新连接即可 + " +} + +# preinstall_check check env +preinstall_check() { + # check os + get_device_os + if [ ${os} != "Linux" ]; then + echo "current os not support, os: ${os}" + return -1 + fi + # check arch + get_device_arch + if [[ ${arch} != "x86_64" && ${arch} != "aarch64" && ${arch} != "arm" && ${arch} != "mips" && ${arch} != "armv7l" ]];then + echo "current arch not support, arch: ${arch}" + return -1 + fi + # fix arch + if [ ${arch} == "x86_64" ]; then + echo "match x86_64 -> amd64" + arch="amd64" + elif [ ${arch} == "aarch64" ]; then + echo "match aarch64 -> arm64" + arch="arm64" + elif [ ${arch} == "mips" ]; then + echo "match mips -> mipsle" + arch="mipsle" + elif [ ${arch} == "armv7l" ]; then + arch="arm" + fi + # support plugin + echo "current system support plugin, system: ${os}-${arch}" + get_device_firmware + return 0 +} + +# show_install_success show install has been installed +show_install_success() { + echo "install success" + echo "雷神路由器插件安装已完成" + echo "请加群632342113体验" +} + diff --git a/lede/package/lean/leigod-acc/files/update_plugin b/lede/package/lean/leigod-acc/files/update_plugin new file mode 100644 index 0000000000..c9061d9a81 --- /dev/null +++ b/lede/package/lean/leigod-acc/files/update_plugin @@ -0,0 +1,175 @@ +#!/usr/bin/lua + +local util = require "luci.util" +local string = require "string" +local io = require "io" +local json = require "luci.jsonc" +local uci = require "luci.model.uci".cursor() +local client = require "luci.httpclient" +local helper = require "luci.helper" + +-- get cloud version +function get_cloud_version() + -- get cloud url + local url = uci:get("easyupdate", "main", "domain") + -- get request channel + local channel = uci:get("easyupdate", "main", "channel") + -- appid + local appid = uci:get("easyupdate", "main", "appid") + -- read brand and model from file + local brand, model = helper.get_board_info() + -- platfrom + local platform = 11 + -- create http request options + local options = {} + -- create http request method + options.method = "POST" + -- create http request headers + options.headers = {} + options.headers['appId'] = appid + options.headers['reqChannel'] = channel + options.headers['Content-Type'] = 'application/json' + -- create http request body + local body = { + -- + ['appId'] = 'spRouterPlugin_fhem2shgq9s5', + ['brand'] = brand, + ['model'] = model, + ['platform'] = platform, + } + -- marshal to json + local buf = util.serialize_json(body) + options.body = buf + -- begin to query + local status, _, buffer, _ = client.request_raw(url, options) + -- check if query success + if not status then + io.write("status is nil \n") + return "" + end + -- check if request success + if status ~= 200 and status ~=206 then + io.write("status is not success, status: " .. status .. "\n") + return "" + end + -- begin to parse response + local info = json.parse(buffer) + -- get ret data + local data = info['retData'] + -- check if data is nil + if data == nil then + io.write("response ret data is nil \n") + return "" + end + -- store url in config + local fw_url = data['url'] + local version = data['versionName'] + io.write("fw_url: " .. fw_url .. ", version: " .. version .. "\n") + -- get version success + return version, fw_url +end + +-- get local version +function get_local_version() + -- run command to get acc version + local cmd = 'opkg info acc' + local buf = util.exec(cmd) + -- check exec result + if buf == nil then + io.write("exec command failed \n") + return "" + end + io.write("buf: " .. buf .. "\n") + -- read line + for line in string.gmatch(buf, "[^\r\n]+") do + io.write("read line: " .. line .. "\n") + line = string.gsub(line, " ", "") + local valueSl = string.gmatch(line, "[^:]+") + local key = valueSl() + local value = valueSl() + if key == "Version" then + io.write("get local version: " .. value .. "\n") + return value + end + end + io.write("local version cant found \n") +end + +-- get file base name +function get_file_base_name(filepath) + local name = string.gmatch(filepath, "([^/]+)$") + io.write("get file base name: " .. name .. "\n") + return name +end + +-- download cloud version +function download_cloud_app(url, name) + io.write("download app, url: " .. url .. ", name: " .. name .. "\n") + -- run download + local cmd = 'curl -s -o /tmp/' .. name ..' -w %{http_code}' .. ' ' .. url + -- exec download + local code = util.exec(cmd) + if code ~= "200" then + io.write("download cloud app failed, code: " .. code .. "\n") + return false + end + io.write("download cloud app success \n") + return code +end + +-- install package name in tmp dir +function install_package(name) + io.write("install package, package name: " .. name .. "\n") + local cmd = "opkg install " .. "/tmp/" .. name .. " " .. "--force-overwrite" + local code = util.exec(cmd) + io.write("install package finish, package name: " .. name .. "\n") + return code +end + +-- remove pkg +function remove_pkg(name) + io.write("remove package, package name: " .. name .. "\n") + local cmd = "rm -rf " .. "/tmp/" .. name + local code = util.exec(cmd) + io.write("remove package finish, package name: " .. name .. "\n") + return code +end + +-- check network state +io.write("check network state \n") +while true do + local state_code = helper.get_network_state() + if state_code == 200 then + io.write("network available \n") + break + end + if state_code ~= nil then + io.write("network is not available, code: " .. state_code .. "\n") + end +end +io.write("network success") + +-- exec command +local local_version = get_local_version() +local cloud_version, url = get_cloud_version() +-- check if need update +if local_version >= cloud_version then + io.write("local version is new, dont need update, local ver: " .. local_version .. + ", cloud version:" .. cloud_version .. "\n") + return +end + +-- need update +io.write("local version is lower, need update, local version: " .. local_version .. + ", cloud version: " .. cloud_version .. "\n") + +-- download url +local name = "acc.ipk" +local result = download_cloud_app(url, name) +if not result then + return +end + +-- install package +install_package(name) +remove_pkg(name) diff --git a/lede/package/lean/leigod-acc/files/version b/lede/package/lean/leigod-acc/files/version new file mode 100644 index 0000000000..aacce34213 --- /dev/null +++ b/lede/package/lean/leigod-acc/files/version @@ -0,0 +1 @@ +version=1.3.0.30 diff --git a/lede/package/lean/luci-app-leigod-acc/Makefile b/lede/package/lean/luci-app-leigod-acc/Makefile new file mode 100644 index 0000000000..7ea702b07e --- /dev/null +++ b/lede/package/lean/luci-app-leigod-acc/Makefile @@ -0,0 +1,18 @@ +# +# Copyright (C) 2008-2014 The LuCI Team +# +# This is free software, licensed under the Apache License, Version 2.0 . +# + +include $(TOPDIR)/rules.mk + +LUCI_TITLE:=Luci for Leigod Game Accelerater +LUCI_PKGARCH:=all +LUCI_DEPENDS:=+libpcap +iptables +kmod-ipt-nat +iptables-mod-tproxy +kmod-ipt-tproxy +kmod-tun +kmod-netem +tc-full +kmod-ipt-ipset +ipset +curl +miniupnpd +conntrack +conntrackd +leigod-acc +PKG_VERSION:=1 +PKG_RELEASE:=2 + +include $(TOPDIR)/feeds/luci/luci.mk + +# call BuildPackage - OpenWrt buildroot signature + diff --git a/lede/package/lean/luci-app-leigod-acc/luasrc/controller/acc.lua b/lede/package/lean/luci-app-leigod-acc/luasrc/controller/acc.lua new file mode 100644 index 0000000000..896d2205ab --- /dev/null +++ b/lede/package/lean/luci-app-leigod-acc/luasrc/controller/acc.lua @@ -0,0 +1,74 @@ +module("luci.controller.acc", package.seeall) + +function index() + require("luci.i18n") + entry({ "admin", "services", "acc" }, alias("admin", "services", "acc", "service"), translate("Leigod Acc"), 50) + entry({ "admin", "services", "acc", "service" }, cbi("leigod/service"), translate("Leigod Service"), 30).i18n = "acc" + entry({ "admin", "services", "acc", "device" }, cbi("leigod/device"), translate("Leigod Device"), 50).i18n = "acc" + entry({ "admin", "services", "acc", "notice" }, cbi("leigod/notice"), translate("Leigod Notice"), 80).i18n = "acc" + entry({ "admin", "services", "acc", "status" }, call("get_acc_status")).leaf = true + entry({ "admin", "services", "acc", "start_acc_service" }, call("start_acc_service")) + entry({ "admin", "services", "acc", "stop_acc_service" }, call("stop_acc_service")) +end + +-- get_acc_status get acc status +function get_acc_status() + -- util module + local util = require "luci.util" + local uci = require "luci.model.uci".cursor() + local translate = luci.i18n.translate + -- init result + local resp = {} + -- init state + resp.service = translate("Acc Service Disabled") + resp.state = {} + -- check if exist + local exist = util.exec("ps | grep acc-gw | grep -v grep") + -- check if program is running + if exist ~= "" then + resp.service = translate("Acc Service Enabled") + end + -- get uci + local results = uci:get_all("accelerator") + for _, typ in pairs({ "Phone", "PC", "Game", "Unknown" }) do + local state = uci:get("accelerator", typ, "state") + -- check state + local state_text = "None" + if state == nil or state == '0' then + elseif state == '1' then + state_text = translate("Acc Catalog Started") + elseif state == '2' then + state_text = translate("Acc Catalog Stopped") + elseif state == '3' then + state_text = translate("Acc Catalog Paused") + end + -- store text + resp.state[translate(typ .. "_Catalog")] = state_text + end + luci.http.prepare_content("application/json") + luci.http.write_json(resp) +end + +-- start_acc_service +function start_acc_service() + -- util module + local util = require "luci.util" + util.exec("/etc/init.d/acc enable") + util.exec("/etc/init.d/acc restart") + local resp = {} + resp.result = "OK" + luci.http.prepare_content("application/json") + luci.http.write_json(resp) +end + +-- start_acc_service +function stop_acc_service() + -- util module + local util = require "luci.util" + util.exec("/etc/init.d/acc stop") + util.exec("/etc/init.d/acc disable") + local resp = {} + resp.result = "OK" + luci.http.prepare_content("application/json") + luci.http.write_json(resp) +end \ No newline at end of file diff --git a/lede/package/lean/luci-app-leigod-acc/luasrc/model/cbi/leigod/device.lua b/lede/package/lean/luci-app-leigod-acc/luasrc/model/cbi/leigod/device.lua new file mode 100644 index 0000000000..dc534f4b31 --- /dev/null +++ b/lede/package/lean/luci-app-leigod-acc/luasrc/model/cbi/leigod/device.lua @@ -0,0 +1,157 @@ +local uci = require "luci.model.uci".cursor() +local util = require "luci.util" +local fs = require "nixio.fs" +local pairs = pairs +local io = io + +-- config +m = Map("accelerator") +m.title = translate("Leigod Device Config") +m.description = translate("Control Device Config") + +-- get neigh info +neigh = m:section(NamedSection, "base", "system", translate("Neigh Device")) +neigh_tab = neigh:option(ListValue, "neigh", translate("acc interface")) +local sys_dir = util.exec("ls /sys/class/net") +if sys_dir ~= nil then + neigh_tab:value("br-lan") + for ifc in string.gmatch(sys_dir, "[^\n]+") do + neigh_tab:value(ifc) + end +end + +-- range all device +device = m:section(NamedSection, "device", "hardware", translate("Device Info")) +device:tab("none_catalog", translate("None_Catalog")) +device:tab("phone_catalog", translate("Phone_Catalog")) +device:tab("pc_catalog", translate("PC_Catalog")) +device:tab("game_catalog", translate("Game_Catalog")) +device:tab("vr_catalog", translate("VR_Catalog")) +device:tab("unknown_catalog", translate("Unknown_Catalog")) + + +local dhcp_map = {} +-- check dhcp file +if fs.access("/tmp/dhcp.leases") then + for line in io.lines("/tmp/dhcp.leases") do + -- check if read empty line + if line == "" then + break + end + -- split line + local valueSl = string.gmatch(line, "[^ ]+") + -- read time + valueSl() + -- read mac + local mac = valueSl() + -- get ip + local ip = valueSl() + -- get host name + local hostname = valueSl() + -- key + local key = string.gsub(mac, ":", "") + -- store key + dhcp_map[key] = { + ["key"] = key, + ["mac"] = mac, + ["ip"] = ip, + ["name"] = hostname + } + end +end + +ifc = uci:get("accelerator", "base", "neigh") +if ifc == nil then + ifc = "br-lan" +end + +local arp_map = {} +-- check if arp exist +if fs.access("/proc/net/arp") then + -- read all item from arp + for line in io.lines("/proc/net/arp") do + -- check if line is not exist + if line == "" then + break + end + -- split item + local valueSl = string.gmatch(line, "[^ ]+") + -- get ip + local ip = valueSl() + -- get type + valueSl() + -- get flag + local flag = valueSl() + -- get mac + local mac = valueSl() + -- get mask + valueSl() + -- get device + local dev = valueSl() + -- get key + local key = string.gsub(mac, ":", "") + -- check if device and flag state + if dev == ifc and flag == "0x2" then + -- get current name + local name = mac + if dhcp_map[key] ~= nil and dhcp_map[key] ~= "*" then + name = dhcp_map[key].name + end + arp_map[key] = { + ["key"] = key, + ["mac"] = mac, + ["ip"] = ip, + ["name"] = name + } + end + end +end + +-- get device config +for key, item in pairs(arp_map) do + local typ = uci:get("accelerator", "device", key) + -- get device catalog from type + local catalog = "none_catalog" + -- default to unknown device + if typ == nil then + typ = 9 + else + typ = tonumber(typ) + end + + if typ == nil then + catalog = "unknown_catalog" + elseif typ >= 1 and typ <= 3 then + catalog = "game_catalog" + elseif typ >= 4 and typ <= 6 then + catalog = "pc_catalog" + elseif typ >= 7 and typ <= 8 then + catalog = "phone_catalog" + elseif typ >= 20 and typ <= 21 then + catalog = "vr_catalog" + else + catalog = "unknown_catalog" + end + -- device type + device_typ = device:taboption(catalog, ListValue, key, item.name) + device_typ:value("0", "None") + device_typ:value("1", "XBox") + device_typ:value("2", "Switch") + device_typ:value("3", "Play Station") + device_typ:value("4", "Steam Deck") + device_typ:value("5", "Windows") + device_typ:value("6", "MacBook") + device_typ:value("7", "Android") + device_typ:value("8", "iPhone") + device_typ:value("20", "Oculus") + device_typ:value("21", "HTC Vive") + device_typ:value("22", "Pico") + device_typ:value("9", "Unknown") +end + +-- set +device.write = function() + util.exec("/etc/init.d/acc restart") +end + +return m \ No newline at end of file diff --git a/lede/package/lean/luci-app-leigod-acc/luasrc/model/cbi/leigod/notice.lua b/lede/package/lean/luci-app-leigod-acc/luasrc/model/cbi/leigod/notice.lua new file mode 100644 index 0000000000..8aafc2dd72 --- /dev/null +++ b/lede/package/lean/luci-app-leigod-acc/luasrc/model/cbi/leigod/notice.lua @@ -0,0 +1,10 @@ +local uci = require "luci.model.uci".cursor() + +-- config +m = Map("accelerator") +m.title = translate("Leigod Notice") +m.description = translate("Leigod Accelerator Usage") + +m:section(SimpleSection).template = "leigod/notice" + +return m diff --git a/lede/package/lean/luci-app-leigod-acc/luasrc/model/cbi/leigod/service.lua b/lede/package/lean/luci-app-leigod-acc/luasrc/model/cbi/leigod/service.lua new file mode 100644 index 0000000000..29f7f2ccf6 --- /dev/null +++ b/lede/package/lean/luci-app-leigod-acc/luasrc/model/cbi/leigod/service.lua @@ -0,0 +1,10 @@ +local uci = require "luci.model.uci".cursor() + +-- config +m = Map("accelerator") +m.title = translate("Leigod Accelerator Config") +m.description = translate("Control Accelerator Config") + +m:section(SimpleSection).template = "leigod/service" + +return m diff --git a/lede/package/lean/luci-app-leigod-acc/luasrc/view/leigod/notice.htm b/lede/package/lean/luci-app-leigod-acc/luasrc/view/leigod/notice.htm new file mode 100644 index 0000000000..b1b49d44e8 --- /dev/null +++ b/lede/package/lean/luci-app-leigod-acc/luasrc/view/leigod/notice.htm @@ -0,0 +1,74 @@ +
+ <%:Declare%> + 此插件为 LEDE/QWRT 官方合作版插件, 需要配合雷神手机APP使用 +

+ <%:Upgrade Info%> +

+ 2024-8-1
+ 支持LEDE/QWRT性能优化
+ 2024-7-25
+ 支持非桥接模式下的旁路由
+ 2024-6-5
+ 扩充ipset容量, 以支持大容量代理ip库
+ 2024-1-22
+ 新增自动选择低延迟线路
+ 新增下载不限速(switch除外)
+ 设备名为*时, 显示mac地址
+


+ 2023-11-28
+ 新增对mips设备的支持
+ 新增对旁路由的支持
+ 解决翻译异常的问题
+ 设备管理页面可以显示未识别设备
+

+ <%:Install Dependence%> +

+ 插件运行需要借助一些依赖才能运行, 一般第三方固件默认已经集成了大部分的依赖,
+ 如果使用的是openwrt官方的固件, 则需要确保依赖安装好了, 以下列出依赖包和注意事项
+ libpcap
+ iptables
+ kmod-ipt-nat
+ iptables-mod-tproxy
+ kmod-ipt-tproxy
+ kmod-netem(非必须, 针对于一些icmp测速的游戏使用, 只影响界面显示, 实际游戏效果不影响)
+ tc-full(非必须, 同kmod-netem)
+ kmod-ipt-ipset
+ ipset
+ curl(谨慎更新, 某些三方固件升级curl, 会导致curl出现问题)
+

+ <%:Bridge Mode%> +

+ 加速插件无法探知当前插件应该使用什么模式,
+ 当前默认使用网桥作为流量转发的设备,
+ 如果使用旁路由模式或者docker时, 此时默认设备不是网桥,
+ 此时需要手动调整接口,
+ 改变接口的地址在 设备管理->路由设备, 选择对应的设备
+

+ <%:Acc Mode%> +

+ 当前雷神路由器支持两种加速模式, tproxy 和 tun
+ 当前默认采用的是tproxy模式, 原因在于tproxy有更好的性能
+ tun模式暂时屏蔽, 后续luci会完善一键切换模式 +

+ <%:Acc Usage%> +

+ 当前雷神加速插件是根据设备类型进行加速的, 也就是说, 如果加速了相应的类型后, 理论上只要连接该路由器的设备都将获得加速效果
+ 当前支持的设备类型有以下几种:
+ 手机: Android iPhone
+ 电脑: Windows MacOS SteamDeck
+ 主机: XBox Switch PlayStation
+ 未识别: Others
+ 可以在设备管理中, 插件自己的设备是否被成功识别成对应的设备, 如果不是, 可以选择对应的设备, 这样设备就能成功加速了
+

+

+ 一些特殊的说明:
+ 关于ios设备, 最好是在安装插件之前, 先忘记网络, 等安装完插件后, 再重新连接网络
+ 关于android设备, 需要关闭dhcpv6后, 手机重连网络, 以下给出一个 /etc/config/dhcp 的配置
+ config dhcp 'lan'
+ ... 此处是一些其他配置
+ ra 'disable'
+ dhcpv6 'disable'
+ list ra_flags 'none'
+ ... 此处是一些其他配置
+

+
diff --git a/lede/package/lean/luci-app-leigod-acc/luasrc/view/leigod/service.htm b/lede/package/lean/luci-app-leigod-acc/luasrc/view/leigod/service.htm new file mode 100644 index 0000000000..6796134f47 --- /dev/null +++ b/lede/package/lean/luci-app-leigod-acc/luasrc/view/leigod/service.htm @@ -0,0 +1,61 @@ + +
+ + + + + + + + + + + +
<%:Acc_Service_Name%><%:Acc_Service_Status%><%:Acc_Service_Operate%>
<%:Acc_Service%><%:Acc_Service_Status%><%:Acc_Service_Operate%>
+
+ + +
+ <%:Acc Catalog State%> + + + + + +
<%:Acc_Catalog%><%:Acc_Catalog_Status%>
+
+ + + diff --git a/lede/package/lean/luci-app-leigod-acc/po/zh-cn/leigo-acc.po b/lede/package/lean/luci-app-leigod-acc/po/zh-cn/leigo-acc.po new file mode 100644 index 0000000000..b93f2e8859 --- /dev/null +++ b/lede/package/lean/luci-app-leigod-acc/po/zh-cn/leigo-acc.po @@ -0,0 +1,123 @@ + +msgid "Leigod Acc" +msgstr "雷神加速器" + +msgid "Leigod Service" +msgstr "服务控制" + +msgid "Leigod Device" +msgstr "设备管理" + +msgid "Leigod Notice" +msgstr "加速公告" + +msgid "Leigod Accelerator Config" +msgstr "加速器配置" + +msgid "Control Accelerator Config" +msgstr "控制加速配置选项" + +msgid "Acc_Service_Name" +msgstr "服务名称" + +msgid "Acc_Service_Status" +msgstr "启动状态" + +msgid "Acc_Service_Operate" +msgstr "加速设置" + +msgid "Acc Catalog State" +msgstr "加速状态" + +msgid "Acc_Catalog" +msgstr "加速设备" + +msgid "Acc_Catalog_Status" +msgstr "加速状态" + +msgid "Declare" +msgstr "说明" + +msgid "Upgrade Info" +msgstr "更新内容" + +msgid "Install Dependence" +msgstr "安装依赖" + +msgid "Bridge Mode" +msgstr "旁路由模式" + +msgid "Acc Mode" +msgstr "加速模式" + +msgid "Acc Usage" +msgstr "使用说明" + +msgid "Leigod Accelerator Usage" +msgstr "雷神加速插件使用" + +msgid "Leigod Device Config" +msgstr "设备管理配置" + +msgid "Control Device Config" +msgstr "控制和改变设备类型" + +msgid "Neigh Device" +msgstr "路由设置" + +msgid "acc interface" +msgstr "代理接口" + +msgid "Device Info" +msgstr "设备信息" + +msgid "None_Catalog" +msgstr "未分类设备" + +msgid "Phone_Catalog" +msgstr "手机设备" + +msgid "None_Catalog" +msgstr "未分类设备" + +msgid "None" +msgstr "无数据" + +msgid "PC_Catalog" +msgstr "PC 设备" + +msgid "Game_Catalog" +msgstr "游戏机" + +msgid "VR_Catalog" +msgstr "VR 设备" + +msgid "Unknown_Catalog" +msgstr "未识别设备" + +msgid "acc_service" +msgstr "雷神加速器服务" + +msgid "Acc_Service" +msgstr "雷神加速器服务" + +msgid "Stop Acc Service" +msgstr "停止加速服务" + +msgid "Start Acc Service" +msgstr "启动加速服务" + +msgid "Acc Service Disabled" +msgstr "加速服务已被禁用" + +msgid "Acc Service Enabled" +msgstr "已启动" + +msgid "Acc Catalog Started" +msgstr "该设备类别加速已启用" + +msgid "Acc Catalog Stopped" +msgstr "该设备类别加速已停止" + +msgid "Acc Catalog Paused" +msgstr "该设备类别加速已暂停" diff --git a/lede/package/lean/luci-app-leigod-acc/root/etc/config/accelerator b/lede/package/lean/luci-app-leigod-acc/root/etc/config/accelerator new file mode 100644 index 0000000000..a2511878ba --- /dev/null +++ b/lede/package/lean/luci-app-leigod-acc/root/etc/config/accelerator @@ -0,0 +1,18 @@ + +config system 'base' + option url 'https://opapi.nn.com/speed/router/plug/check' + option heart 'https://opapi.nn.com/speed/router/heartbeat' + option base_url 'https://opapi.nn.com/speed' + +config bind 'bind' + +config hardware 'device' + +config acceleration 'Phone' + +config acceleration 'PC' + +config acceleration 'Game' + +config acceleration 'Unknown' + diff --git a/lede/package/lean/luci-app-leigod-acc/root/etc/init.d/acc b/lede/package/lean/luci-app-leigod-acc/root/etc/init.d/acc new file mode 100755 index 0000000000..93fc99c2cf --- /dev/null +++ b/lede/package/lean/luci-app-leigod-acc/root/etc/init.d/acc @@ -0,0 +1,162 @@ +#!/bin/sh /etc/rc.common + +# start prio +START=50 +# use proc to start service +USE_PROCD=1 +# package name +PACKAGE_NAME="acc.ipk" +# binary dir +sbin_dir="/usr/sbin/leigod" +# binary name +binary_prefix="acc-gw.linux" + + +# download acceleration binary +download_acc_binary() { + echo "begin to download file... ..." + # create download + local url="http://119.3.40.126/${PACKAGE_NAME}" + echo "download path: ${url}" + local path="/tmp/${PACKAGE_NAME}" + local code=$(curl --connect-timeout 2 -L -s -k -o "${path}" -w "%{http_code}" "${url}") + for i in $(seq 1 2) + do + if [ "$?" != "0" -o "${code}" != "200" -o ! -f "${path}" ]; then + echo "download failed: ${code}" + rm -f ${path} + sleep 5 + continue + fi + echo "download success" + break + done + + echo "download file success... ..." +} + +# mkdir acc dir +mk_acc_dir() { + echo "begin to mkdir... ..." + # should make sure dir exist + local cmd="mkdir -p ${BINARY_DIR}" + echo "exec mkdir dir: ${cmd}" + local code=`$cmd` + echo "mkdir success... ..." +} + +# decompress file +decompress_file() { + echo "begin to decompress file... ..." + # compress file path + local path="/tmp/${PACKAGE_NAME}" + # decompress command + local cmd="tar -zxvf ${path} -C ${BINARY_DIR}" + local code=`${cmd}` + echo "decompress file success... ..." +} + +# install ipk +install_package() { + echo "begin to overwrite package" + # local cmd = "opkg install /tmp/${PACKAGE_NAME} --force-overwrite" + # `$cmd` + echo "overwrite finish, begin to start" +} + +# main +prepare_env() { + # mkdir + mk_acc_dir + # download file + download_acc_binary + # decompress file + install_package +} + +main() { + prepare_env +} + +# get_device_arch, +# current support arch: arm64 arm x86_64 mips +get_device_arch() { + arch=$(uname -m) + if [ $? != "0" ]; then + echo "arch cant be get" + return -1 + fi + # fix arch + if [ ${arch} == "x86_64" ]; then + echo "match x86_64 -> amd64" + arch="amd64" + elif [ ${arch} == "aarch64" ]; then + echo "match aarch64 -> arm64" + arch="arm64" + elif [ ${arch} == "mips" ]; then + echo "match mips -> mipsle" + arch="mipsle" + elif [ ${arch} == "armv7l" ]; then + echo "match armv7l -> arm" + arch="arm" + fi + return 0 +} + +# get_xiaomi_name check if is xiaomi +get_xiaomi_name() { + local name=$(uci get misc.hardware.displayName) + if [[ $? == "0" && ${name} != "" ]]; then + echo "router is xiaomi series, name: ${name}" + sbin_dir="/userdisk/appdata/leigod" + init_dir="/userdisk/appdata/leigod" + args="--mode tun" + is_xiaomi=true + return 0 + fi + local name=$(uci get misc.hardware.model) + if [[ $? == "0" && ${name} != "" ]]; then + echo "router is xiaomi series, name: ${name}" + sbin_dir="/userdisk/appdata/leigod" + init_dir="/userdisk/appdata/leigod" + args="--mode tun" + is_xiaomi=true + return 0 + fi + echo "router is not xiaomi, use general openwrt" + sbin_dir="/usr/sbin/leigod" + init_dir="/etc/init.d" + show_openwrt_suggestion + return 0 +} + +# run service as daemon +start_service_daemon() { + # open once instance + procd_open_instance + # run binary + local cmd="${sbin_dir}/${binary_prefix}.${arch} ${args}" + echo "start service: ${cmd}" + procd_set_param command ${cmd} + # auto start when failed + procd_set_param respawn + # close instance + procd_close_instance + # start check update + # update_plugin & + # start led light + # /etc/init.d/internetled start +} + + +# proc start service +start_service() { + # prepare env + # prepare_env + # run service as daemon + get_device_arch + get_xiaomi_name + start_service_daemon +} + + diff --git a/lede/package/lean/luci-app-leigod-acc/root/etc/uci-defaults/99-accelerator-config b/lede/package/lean/luci-app-leigod-acc/root/etc/uci-defaults/99-accelerator-config new file mode 100755 index 0000000000..b1b98e404f --- /dev/null +++ b/lede/package/lean/luci-app-leigod-acc/root/etc/uci-defaults/99-accelerator-config @@ -0,0 +1,24 @@ +#!/bin/sh + +# check if file exist +if [ ! -f /etc/config/accelerator ]; then + echo "file not exist, should create..." + touch /etc/config/accelerator + uci set accelerator.base=system + uci set accelerator.device=hardware + uci set accelerator.Phone=acceleration + uci set accelerator.PC=acceleration + uci set accelerator.Game=acceleration + uci set accelerator.Unknown=acceleration + uci commit accelerator +fi + +uci set accelerator.base.url='https://opapi.nn.com/speed/router/plug/check' +uci set accelerator.base.heart='https://opapi.nn.com/speed/router/heartbeat' +uci set accelerator.base.base_url='https://opapi.nn.com/speed' +uci commit accelerator + +echo "Fistboot Disable " + +/etc/init.d/acc stop +/etc/init.d/acc disable diff --git a/mieru/pkg/congestion/bandwidth_sampler.go b/mieru/pkg/congestion/bandwidth_sampler.go index 72c3bf75ff..b7235b48ea 100644 --- a/mieru/pkg/congestion/bandwidth_sampler.go +++ b/mieru/pkg/congestion/bandwidth_sampler.go @@ -16,14 +16,16 @@ package congestion import ( + "fmt" "math" + "sync" "time" "github.com/enfein/mieru/pkg/mathext" ) const ( - InfiniteBandwidth int64 = math.MaxInt64 + infiniteBandwidth int64 = math.MaxInt64 ) // BandwidthSample contains a single data point of network bandwidth. @@ -41,6 +43,10 @@ type BandwidthSample struct { isAppLimited bool } +func (bs BandwidthSample) String() string { + return fmt.Sprintf("BandwidthSample{bandwidth=%v, rtt=%v, isAppLimited=%v}", bs.bandwidth, bs.rtt.Truncate(time.Microsecond), bs.isAppLimited) +} + // BandwidthSamplerInterface is an interface common to any class that can // provide bandwidth samples from the information per individual acknowledged packet. type BandwidthSamplerInterface interface { @@ -109,7 +115,7 @@ func NewConnectionStateOnSentPacketFromSampler(sendTime time.Time, size int64, s totalBytesSentAtLastAckedPacket: sampler.totalBytesSentAtLastAckedPacket, lastAckedPacketSentTime: sampler.lastAckedPacketSentTime, lastAckedPacketAckTime: sampler.lastAckedPacketAckTime, - totalBytesAckedAtTheLastAckedPacket: sampler.totalBytesSentAtLastAckedPacket, + totalBytesAckedAtTheLastAckedPacket: sampler.totalBytesAcked, isAppLimited: sampler.isAppLimited, } } @@ -197,6 +203,7 @@ func NewConnectionStateOnSentPacketFromSampler(sendTime time.Time, size int64, s // Note that while the scenario above is not the only scenario when the // connection is app-limited, the approach works in other cases too. type BandwidthSampler struct { + mu sync.Mutex totalBytesSent int64 totalBytesAcked int64 totalBytesSentAtLastAckedPacket int64 @@ -217,6 +224,9 @@ func NewBandwidthSampler() BandwidthSamplerInterface { } func (bs *BandwidthSampler) OnPacketSent(sentTime time.Time, packetNumber int64, bytes int64, bytesInFlight int64, hasRetransmittableData bool) { + bs.mu.Lock() + defer bs.mu.Unlock() + bs.lastSentPacket = packetNumber if !hasRetransmittableData { return @@ -242,6 +252,9 @@ func (bs *BandwidthSampler) OnPacketSent(sentTime time.Time, packetNumber int64, } func (bs *BandwidthSampler) OnPacketAcknowledged(ackTime time.Time, packetNumber int64) BandwidthSample { + bs.mu.Lock() + defer bs.mu.Unlock() + sentPacket := bs.connectionStateMap.GetEntry(packetNumber) if sentPacket == nil { return BandwidthSample{} @@ -253,29 +266,44 @@ func (bs *BandwidthSampler) OnPacketAcknowledged(ackTime time.Time, packetNumber } func (bs *BandwidthSampler) OnPacketLost(packetNumber int64) { + bs.mu.Lock() + defer bs.mu.Unlock() + bs.connectionStateMap.Remove(packetNumber) } func (bs *BandwidthSampler) OnAppLimited() { + bs.mu.Lock() + defer bs.mu.Unlock() + bs.isAppLimited = true bs.endOfAppLimitedPhase = bs.lastSentPacket } func (bs *BandwidthSampler) RemoveObsoletePackets(leastUnacked int64) { + bs.mu.Lock() + defer bs.mu.Unlock() + for !bs.connectionStateMap.IsEmpty() && bs.connectionStateMap.FirstPacket() < leastUnacked { bs.connectionStateMap.Remove(bs.connectionStateMap.FirstPacket()) } } func (bs *BandwidthSampler) TotalBytesAcked() int64 { + bs.mu.Lock() + defer bs.mu.Unlock() return bs.totalBytesAcked } func (bs *BandwidthSampler) IsAppLimited() bool { + bs.mu.Lock() + defer bs.mu.Unlock() return bs.isAppLimited } func (bs *BandwidthSampler) EndOfAppLimitedPhase() int64 { + bs.mu.Lock() + defer bs.mu.Unlock() return bs.endOfAppLimitedPhase } @@ -300,9 +328,9 @@ func (bs *BandwidthSampler) onPacketAcknowledgedInner(ackTime time.Time, packetN // Infinite rate indicates that the sampler is supposed to discard the // current send rate sample and use only the ack rate. - sendRate := InfiniteBandwidth + sendRate := infiniteBandwidth if sentPacket.sentTime.After(sentPacket.lastAckedPacketSentTime) { - sendRate = bandwidthFromBytesAndTimeDelta(sentPacket.totalBytesSent-sentPacket.totalBytesSentAtLastAckedPacket, sentPacket.sentTime.Sub(sentPacket.lastAckedPacketSentTime)) + sendRate = BandwidthFromBytesAndTimeDelta(sentPacket.totalBytesSent-sentPacket.totalBytesSentAtLastAckedPacket, sentPacket.sentTime.Sub(sentPacket.lastAckedPacketSentTime)) } // During the slope calculation, ensure that ack time of the current packet is @@ -311,7 +339,7 @@ func (bs *BandwidthSampler) onPacketAcknowledgedInner(ackTime time.Time, packetN if !ackTime.After(sentPacket.lastAckedPacketAckTime) { return BandwidthSample{} } - ackRate := bandwidthFromBytesAndTimeDelta(bs.totalBytesAcked-sentPacket.totalBytesAckedAtTheLastAckedPacket, ackTime.Sub(sentPacket.lastAckedPacketAckTime)) + ackRate := BandwidthFromBytesAndTimeDelta(bs.totalBytesAcked-sentPacket.totalBytesAckedAtTheLastAckedPacket, ackTime.Sub(sentPacket.lastAckedPacketAckTime)) sample := BandwidthSample{ bandwidth: mathext.Min(sendRate, ackRate), @@ -321,7 +349,7 @@ func (bs *BandwidthSampler) onPacketAcknowledgedInner(ackTime time.Time, packetN return sample } -func bandwidthFromBytesAndTimeDelta(bytes int64, duration time.Duration) int64 { +func BandwidthFromBytesAndTimeDelta(bytes int64, duration time.Duration) int64 { if duration <= 0 { return 0 } diff --git a/mieru/pkg/congestion/bbr_sender.go b/mieru/pkg/congestion/bbr_sender.go index 61abae1cfa..b34c830b0f 100644 --- a/mieru/pkg/congestion/bbr_sender.go +++ b/mieru/pkg/congestion/bbr_sender.go @@ -17,8 +17,10 @@ package congestion import ( mrand "math/rand" + "sync" "time" + "github.com/enfein/mieru/pkg/log" "github.com/enfein/mieru/pkg/mathext" ) @@ -114,17 +116,22 @@ var ( ) type AckedPacketInfo struct { - packetNumber int64 - bytesAcked int64 - receiveTimestamp time.Time + PacketNumber int64 + BytesAcked int64 + ReceiveTimestamp time.Time } type LostPacketInfo struct { - packetNumber int64 - bytesLost int64 + PacketNumber int64 + BytesLost int64 } type BBRSender struct { + mu sync.Mutex + + // Additional context of this BBRSender. Used in the log. + logContext string + rttStats *RTTStats // Replaces unacked_packets_->bytes_in_flight(). @@ -283,8 +290,9 @@ type BBRSender struct { minRTTSinceLastProbeRTT time.Duration } -func NewBBRSender() *BBRSender { +func NewBBRSender(logContext string) *BBRSender { return &BBRSender{ + logContext: logContext, rttStats: NewRTTStats(), mode: modeStartUp, sampler: NewBandwidthSampler(), @@ -318,6 +326,9 @@ func (b *BBRSender) InSlowStart() bool { } func (b *BBRSender) OnPacketSent(sentTime time.Time, bytesInFlight int64, packetNumber int64, bytes int64, hasRetransmittableData bool) { + b.mu.Lock() + defer b.mu.Unlock() + b.lastSentPacket = packetNumber b.bytesInFlight = bytesInFlight @@ -333,12 +344,14 @@ func (b *BBRSender) OnPacketSent(sentTime time.Time, bytesInFlight int64, packet } func (b *BBRSender) CanSend(bytesInFlight int64) bool { + b.mu.Lock() + defer b.mu.Unlock() return bytesInFlight < b.GetCongestionWindow() } func (b *BBRSender) PacingRate(bytesInFlight int64) int64 { if b.pacingRate <= 0 { - return int64(highGain * float64(bandwidthFromBytesAndTimeDelta(b.initialCongestionWindow, b.GetMinRTT()))) + return int64(highGain * float64(BandwidthFromBytesAndTimeDelta(b.initialCongestionWindow, b.GetMinRTT()))) } return b.pacingRate } @@ -359,10 +372,6 @@ func (b *BBRSender) GetCongestionWindow() int64 { return b.congestionWindow } -func (b *BBRSender) GetSlowStartThreshold() int64 { - return 0 -} - func (b *BBRSender) InRecovery() bool { return b.recoveryState != stateNotInRecovery } @@ -381,16 +390,19 @@ func (b *BBRSender) AdjustNetworkParameters(bandwidth int64, rtt time.Duration) } func (b *BBRSender) OnCongestionEvent(priorInFlight int64, eventTime time.Time, ackedPackets []AckedPacketInfo, lostPackets []LostPacketInfo) { + b.mu.Lock() + defer b.mu.Unlock() + totalBytesAckedBefore := b.sampler.TotalBytesAcked() isRoundStart := false isMinRTTExpired := false b.bytesInFlight = priorInFlight for _, p := range ackedPackets { - b.bytesInFlight -= p.bytesAcked + b.bytesInFlight -= p.BytesAcked } for _, p := range lostPackets { - b.bytesInFlight -= p.bytesLost + b.bytesInFlight -= p.BytesLost } b.bytesInFlight = mathext.Max(b.bytesInFlight, 0) @@ -398,7 +410,7 @@ func (b *BBRSender) OnCongestionEvent(priorInFlight int64, eventTime time.Time, // Input the new data into the BBR model of the connection. if len(ackedPackets) > 0 { - lastAckedPacket := ackedPackets[len(ackedPackets)-1].packetNumber + lastAckedPacket := ackedPackets[len(ackedPackets)-1].PacketNumber isRoundStart = b.UpdateRoundTripCounter(lastAckedPacket) isMinRTTExpired = b.UpdateBandwidthAndMinRTT(eventTime, ackedPackets) b.UpdateRecoveryState(lastAckedPacket, len(lostPackets) > 0, isRoundStart) @@ -433,7 +445,7 @@ func (b *BBRSender) OnCongestionEvent(priorInFlight int64, eventTime time.Time, bytesAcked := b.sampler.TotalBytesAcked() - totalBytesAckedBefore var bytesLost int64 for _, lost := range lostPackets { - bytesLost += lost.bytesLost + bytesLost += lost.BytesLost } // After the model is updated, recalculate the pacing rate and congestion @@ -449,9 +461,9 @@ func (b *BBRSender) OnCongestionEvent(priorInFlight int64, eventTime time.Time, // no more than 2 packets. var leastUnacked int64 if len(ackedPackets) > 0 { - leastUnacked = ackedPackets[len(ackedPackets)-1].packetNumber - 2 + leastUnacked = ackedPackets[len(ackedPackets)-1].PacketNumber - 2 } else if len(lostPackets) > 0 { - leastUnacked = lostPackets[len(lostPackets)-1].packetNumber + 1 + leastUnacked = lostPackets[len(lostPackets)-1].PacketNumber + 1 } b.sampler.RemoveObsoletePackets(leastUnacked) } @@ -505,7 +517,7 @@ func (b *BBRSender) EnterProbeBandwidthMode(now time.Time) { func (b *BBRSender) DiscardLostPackets(lostPackets []LostPacketInfo) { for _, lost := range lostPackets { - b.sampler.OnPacketLost(lost.packetNumber) + b.sampler.OnPacketLost(lost.PacketNumber) } } @@ -521,7 +533,14 @@ func (b *BBRSender) UpdateRoundTripCounter(lastAckedPacket int64) bool { func (b *BBRSender) UpdateBandwidthAndMinRTT(now time.Time, ackedPackets []AckedPacketInfo) bool { sampleMinRTT := infDuration for _, acked := range ackedPackets { - bandwidthSample := b.sampler.OnPacketAcknowledged(now, acked.packetNumber) + bandwidthSample := b.sampler.OnPacketAcknowledged(now, acked.PacketNumber) + if log.IsLevelEnabled(log.TraceLevel) { + log.Tracef("[BBRSender %s] Acknowledged packet %d produced %v", b.logContext, acked.PacketNumber, bandwidthSample) + } + if bandwidthSample.bandwidth < 0 { + log.Debugf("[BBRSender %s] Acknowledged packet %d produced negative bandwidth %d B/s. Sample is dropped.", b.logContext, acked.PacketNumber, bandwidthSample.bandwidth) + continue + } b.lastSampleIsAppLimited = bandwidthSample.isAppLimited if bandwidthSample.rtt > 0 { sampleMinRTT = mathext.Min(sampleMinRTT, bandwidthSample.rtt) @@ -739,7 +758,7 @@ func (b *BBRSender) CalculatePacingRate() { // Pace at the rate of initial window / RTT as soon as RTT measurements are // available. if b.pacingRate <= 0 && b.rttStats.MinRTT() > 0 { - b.pacingRate = bandwidthFromBytesAndTimeDelta(b.initialCongestionWindow, b.rttStats.MinRTT()) + b.pacingRate = BandwidthFromBytesAndTimeDelta(b.initialCongestionWindow, b.rttStats.MinRTT()) } // Slow the pacing rate in STARTUP once loss has ever been detected. diff --git a/mieru/pkg/congestion/bbr_sender_test.go b/mieru/pkg/congestion/bbr_sender_test.go new file mode 100644 index 0000000000..4d7ec2984e --- /dev/null +++ b/mieru/pkg/congestion/bbr_sender_test.go @@ -0,0 +1,148 @@ +// Copyright (C) 2024 mieru authors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package congestion_test + +import ( + "context" + "encoding/binary" + "io" + "sync" + "testing" + "time" + + "github.com/enfein/mieru/pkg/congestion" + "github.com/enfein/mieru/pkg/log" + "github.com/enfein/mieru/pkg/testtool" +) + +type sender struct { + ctx context.Context + rwc io.ReadWriteCloser + nextSend int64 + nextAck int64 + bbr *congestion.BBRSender +} + +type receiver struct { + ctx context.Context + rwc io.ReadWriteCloser + ackSend uint64 +} + +func (s *sender) Run(t *testing.T) { + t.Helper() + var wg sync.WaitGroup + wg.Add(2) + + go func() { + loop: + for { + select { + case <-s.ctx.Done(): + wg.Done() + break loop + default: + b := make([]byte, 8) + inFlight := (s.nextSend - s.nextAck) * 8 + if s.bbr.CanSend(inFlight) { + binary.BigEndian.PutUint64(b, uint64(s.nextSend)) + s.rwc.Write(b) + s.bbr.OnPacketSent(time.Now(), inFlight, s.nextSend, 8, true) + s.nextSend++ + } else { + time.Sleep(time.Millisecond) + } + } + } + }() + + go func() { + loop: + for { + select { + case <-s.ctx.Done(): + wg.Done() + break loop + default: + b := make([]byte, 8) + inFlight := (s.nextSend - s.nextAck) * 8 + if _, err := io.ReadFull(s.rwc, b); err != nil { + t.Logf("error read ack: %v", err) + time.Sleep(time.Millisecond) + continue + } + now := time.Now() + s.nextAck = int64(binary.BigEndian.Uint64(b)) + s.bbr.OnCongestionEvent(inFlight, now, []congestion.AckedPacketInfo{{ + PacketNumber: s.nextAck - 1, + BytesAcked: 8, + ReceiveTimestamp: now, + }}, nil) + } + } + }() + + wg.Wait() +} + +func (r *receiver) Start(t *testing.T) { + t.Helper() + go func() { + for { + select { + case <-r.ctx.Done(): + return + default: + b := make([]byte, 8) + if _, err := io.ReadFull(r.rwc, b); err != nil { + t.Logf("error read data: %v", err) + time.Sleep(time.Millisecond) + continue + } + r.ackSend = binary.BigEndian.Uint64(b) + binary.BigEndian.PutUint64(b, r.ackSend+1) + if _, err := r.rwc.Write(b); err != nil { + t.Logf("error write ack: %v", err) + return + } + } + } + }() +} + +func TestBBRSender(t *testing.T) { + log.SetOutputToTest(t) + log.SetLevel("DEBUG") + + e1, e2 := testtool.BufPipe() + ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(time.Second)) + defer cancel() + s := &sender{ + ctx: ctx, + rwc: e1, + bbr: congestion.NewBBRSender("Test"), + } + r := &receiver{ + ctx: ctx, + rwc: e2, + } + r.Start(t) + s.Run(t) + t.Logf("nextSend: %v", s.nextSend) + t.Logf("nextAck: %v", s.nextAck) + t.Logf("ackSend: %v", r.ackSend) + t.Logf("Estimated bandwidth: %d B/s", s.bbr.BandwidthEstimate()) +} diff --git a/mieru/pkg/congestion/packet_number_indexed_queue.go b/mieru/pkg/congestion/packet_number_indexed_queue.go index c9d6437258..a4de3d8b61 100644 --- a/mieru/pkg/congestion/packet_number_indexed_queue.go +++ b/mieru/pkg/congestion/packet_number_indexed_queue.go @@ -15,7 +15,11 @@ package congestion -import "github.com/enfein/mieru/pkg/deque" +import ( + "sync" + + "github.com/enfein/mieru/pkg/deque" +) // PacketNumberIndexedQueue is a queue of mostly continuous numbered entries // which supports the following operations: @@ -39,6 +43,7 @@ import "github.com/enfein/mieru/pkg/deque" // Because of that, it is not a general-purpose container and should not be used // as one. type PacketNumberIndexedQueue[T any] struct { + mu sync.Mutex entries *deque.Deque[EntryWrapper[T]] numberOfPresentEntries int firstPacket int64 @@ -48,7 +53,7 @@ func NewPacketNumberIndexedQueue[T any]() *PacketNumberIndexedQueue[T] { return &PacketNumberIndexedQueue[T]{ entries: deque.New[EntryWrapper[T]](0), numberOfPresentEntries: 0, - firstPacket: 0, + firstPacket: -1, } } @@ -62,8 +67,11 @@ type EntryWrapper[T any] struct { // Returns the pointer to the entry in case of success, or nil if the entry // does not exist. func (p *PacketNumberIndexedQueue[T]) GetEntry(packetNumber int64) *T { - entry := p.getEntryWrapper(packetNumber) - if entry == nil { + p.mu.Lock() + defer p.mu.Unlock() + + entry, idx := p.getEntryWrapper(packetNumber) + if idx < 0 { return nil } return &entry.data @@ -74,7 +82,10 @@ func (p *PacketNumberIndexedQueue[T]) GetEntry(packetNumber int64) *T { // true if the element has been inserted successfully, false if it was already // in the queue or inserted out of order. func (p *PacketNumberIndexedQueue[T]) Emplace(packetNumber int64, args T) bool { - if p.IsEmpty() { + p.mu.Lock() + defer p.mu.Unlock() + + if p.numberOfPresentEntries == 0 { p.entries.PushBack(EntryWrapper[T]{data: args, present: true}) p.numberOfPresentEntries = 1 p.firstPacket = packetNumber @@ -100,11 +111,15 @@ func (p *PacketNumberIndexedQueue[T]) Emplace(packetNumber int64, args T) bool { // Remove removes data associated with packetNumber and frees the slots in the // queue as necessary. func (p *PacketNumberIndexedQueue[T]) Remove(packetNumber int64) bool { - entry := p.getEntryWrapper(packetNumber) - if entry == nil { + p.mu.Lock() + defer p.mu.Unlock() + + entry, idx := p.getEntryWrapper(packetNumber) + if idx < 0 { return false } entry.present = false + p.entries.Set(idx, entry) p.numberOfPresentEntries-- if packetNumber == p.firstPacket { @@ -113,50 +128,54 @@ func (p *PacketNumberIndexedQueue[T]) Remove(packetNumber int64) bool { return true } +func (p *PacketNumberIndexedQueue[T]) IsEmpty() bool { + p.mu.Lock() + defer p.mu.Unlock() + return p.numberOfPresentEntries == 0 +} + func (p *PacketNumberIndexedQueue[T]) FirstPacket() int64 { return p.firstPacket } func (p *PacketNumberIndexedQueue[T]) LastPacket() int64 { - if p.IsEmpty() { - return 0 + if p.numberOfPresentEntries == 0 { + return -1 } return p.firstPacket + int64(p.entries.Len()) - 1 } -func (p *PacketNumberIndexedQueue[T]) EntrySlotsUsed() int { +func (p *PacketNumberIndexedQueue[T]) entrySlotsUsed() int { return p.entries.Len() } -func (p *PacketNumberIndexedQueue[T]) IsEmpty() bool { - return p.numberOfPresentEntries == 0 -} - -// cleanup cleans up unused slots in the front after removing an element. +// cleanup cleans up unused slots in the front. func (p *PacketNumberIndexedQueue[T]) cleanup() { for p.entries.Len() > 0 && !p.entries.Front().present { p.entries.PopFront() p.firstPacket++ } if p.entries.Len() == 0 { - p.firstPacket = 0 + p.firstPacket = -1 } } -func (p *PacketNumberIndexedQueue[T]) getEntryWrapper(packetNumber int64) *EntryWrapper[T] { +// getEntryWrapper returns a copy of the wrapper and the index in the deque. +// If not found, index -1 is returned. +func (p *PacketNumberIndexedQueue[T]) getEntryWrapper(packetNumber int64) (EntryWrapper[T], int) { if packetNumber < p.firstPacket { - return nil + return EntryWrapper[T]{}, -1 } offset := packetNumber - p.firstPacket if offset >= int64(p.entries.Len()) { - return nil + return EntryWrapper[T]{}, -1 } entry := p.entries.At(int(offset)) if !entry.present { - return nil + return EntryWrapper[T]{}, -1 } - return &entry + return entry, int(offset) } diff --git a/mieru/pkg/congestion/packet_number_indexed_queue_test.go b/mieru/pkg/congestion/packet_number_indexed_queue_test.go index ea285d41d6..079619a756 100644 --- a/mieru/pkg/congestion/packet_number_indexed_queue_test.go +++ b/mieru/pkg/congestion/packet_number_indexed_queue_test.go @@ -29,7 +29,7 @@ func TestPacketNumberIndexedQueue(t *testing.T) { for i := 0; i < total; i++ { clockTime := time.Now().UnixMilli() packets[i] = clockTime - time.Sleep(10 * time.Millisecond) + time.Sleep(5 * time.Millisecond) } for i := 0; i < total; i++ { @@ -58,15 +58,18 @@ func TestPacketNumberIndexedQueue(t *testing.T) { if queue.LastPacket() != packets[total-1] { t.Errorf("Got unexpected last packet %v, want %v", queue.LastPacket(), packets[total-1]) } - if queue.EntrySlotsUsed() <= total { - t.Errorf("Queue has %d slots, want more than %d", queue.EntrySlotsUsed(), total) + if queue.entrySlotsUsed() <= total { + t.Errorf("Queue has %d slots, want more than %d", queue.entrySlotsUsed(), total) } - for i := total - 1; i >= 0; i-- { + for i := 0; i < total; i++ { if ok := queue.Remove(packets[i]); !ok { t.Fatalf("Remove() failed on packet %d", i) } } + if ok := queue.Remove(packets[0]); ok { + t.Errorf("Remove() is successful with removed packet") + } if !queue.IsEmpty() { t.Errorf("Queue is not empty after removing all the packets") } diff --git a/mieru/pkg/congestion/windowed_filter_test.go b/mieru/pkg/congestion/windowed_filter_test.go new file mode 100644 index 0000000000..daa058df6a --- /dev/null +++ b/mieru/pkg/congestion/windowed_filter_test.go @@ -0,0 +1,69 @@ +// Copyright (C) 2024 mieru authors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package congestion + +import ( + "testing" + "time" +) + +func TestMinFilter(t *testing.T) { + filter := NewWindowedFilter(5, 0, MinFilter[int]) + for i := 1; i <= 10; i++ { + filter.Update(i, int64(i)) + } + best := filter.GetBest() + second := filter.GetSecondBest() + third := filter.GetThirdBest() + t.Logf("MinFilter: %d %d %d", best, second, third) + if best >= second { + t.Errorf("with MinFilter, got best >= second") + } + if best >= third { + t.Errorf("with MinFilter, got best >= third") + } + if second >= third { + t.Errorf("with MinFilter, got second >= third") + } + filter.Reset(0, time.Now().UnixNano()) + if filter.GetBest() != 0 || filter.GetSecondBest() != 0 || filter.GetThirdBest() != 0 { + t.Errorf("got non-zero after Reset()") + } +} + +func TestMaxFilter(t *testing.T) { + filter := NewWindowedFilter(5, 0, MaxFilter[int]) + for i := 10; i >= 1; i-- { + filter.Update(i, int64(11-i)) + } + best := filter.GetBest() + second := filter.GetSecondBest() + third := filter.GetThirdBest() + t.Logf("MaxFilter: %d %d %d", best, second, third) + if best <= second { + t.Errorf("with MaxFilter, got best <= second") + } + if best <= third { + t.Errorf("with MaxFilter, got best <= third") + } + if second <= third { + t.Errorf("with MaxFilter, got second <= third") + } + filter.Reset(0, time.Now().UnixNano()) + if filter.GetBest() != 0 || filter.GetSecondBest() != 0 || filter.GetThirdBest() != 0 { + t.Errorf("got non-zero after Reset()") + } +} diff --git a/mieru/pkg/testtool/pipe.go b/mieru/pkg/testtool/pipe.go index 6674712c3f..8ff9e31e56 100644 --- a/mieru/pkg/testtool/pipe.go +++ b/mieru/pkg/testtool/pipe.go @@ -19,20 +19,26 @@ import ( "bytes" "errors" "io" + "sync" ) // BufPipe is like net.Pipe() but with an internal buffer. func BufPipe() (io.ReadWriteCloser, io.ReadWriteCloser) { var buf1, buf2 bytes.Buffer + var lock1, lock2 sync.Mutex ep1 := &ioEndpoint{ direction: forward, buf1: &buf1, buf2: &buf2, + lock1: &lock1, + lock2: &lock2, } ep2 := &ioEndpoint{ direction: backward, buf1: &buf1, buf2: &buf2, + lock1: &lock1, + lock2: &lock2, } return ep1, ep2 } @@ -48,6 +54,8 @@ type ioEndpoint struct { direction ioDirection buf1 *bytes.Buffer // forward writes to here buf2 *bytes.Buffer // backward writes to here + lock1 *sync.Mutex // lock of buf1 + lock2 *sync.Mutex // lock of buf2 closed bool } @@ -56,9 +64,13 @@ func (e *ioEndpoint) Read(b []byte) (n int, err error) { return 0, io.EOF } if e.direction == forward { + e.lock2.Lock() n, err = e.buf2.Read(b) + e.lock2.Unlock() } else { + e.lock1.Lock() n, err = e.buf1.Read(b) + e.lock1.Unlock() } if errors.Is(err, io.EOF) { err = nil @@ -66,14 +78,20 @@ func (e *ioEndpoint) Read(b []byte) (n int, err error) { return } -func (e *ioEndpoint) Write(b []byte) (int, error) { +func (e *ioEndpoint) Write(b []byte) (n int, err error) { if e.closed { return 0, io.ErrClosedPipe } if e.direction == forward { - return e.buf1.Write(b) + e.lock1.Lock() + n, err = e.buf1.Write(b) + e.lock1.Unlock() + } else { + e.lock2.Lock() + n, err = e.buf2.Write(b) + e.lock2.Unlock() } - return e.buf2.Write(b) + return } func (e *ioEndpoint) Close() error { diff --git a/naiveproxy/src/build/linux/sysroot_scripts/reversion_glibc.py b/naiveproxy/src/build/linux/sysroot_scripts/reversion_glibc.py index 5b50963b31..b7e18eff03 100644 --- a/naiveproxy/src/build/linux/sysroot_scripts/reversion_glibc.py +++ b/naiveproxy/src/build/linux/sysroot_scripts/reversion_glibc.py @@ -11,7 +11,7 @@ import subprocess # This constant comes from the oldest glibc version in # //chrome/installer/linux/debian/dist_package_versions.json and # //chrome/installer/linux/rpm/dist_package_provides.json -MAX_ALLOWED_GLIBC_VERSION = [2, 28] +MAX_ALLOWED_GLIBC_VERSION = [2, 26] VERSION_PATTERN = re.compile("GLIBC_([0-9\.]+)") SECTION_PATTERN = re.compile(r"^ *\[ *[0-9]+\] +(\S+) +\S+ + ([0-9a-f]+) .*$") diff --git a/naiveproxy/src/build/linux/sysroot_scripts/sysroot_creator.py b/naiveproxy/src/build/linux/sysroot_scripts/sysroot_creator.py index d2b16ab992..be7cab9f31 100755 --- a/naiveproxy/src/build/linux/sysroot_scripts/sysroot_creator.py +++ b/naiveproxy/src/build/linux/sysroot_scripts/sysroot_creator.py @@ -303,9 +303,17 @@ def hacks_and_patches(install_root: str, script_dir: str, arch: str) -> None: os.remove(qtchooser_conf) # __GLIBC_MINOR__ is used as a feature test macro. Replace it with the - # earliest supported version of glibc (2.28). + # earliest supported version of glibc (2.26). features_h = os.path.join(install_root, "usr", "include", "features.h") - replace_in_file(features_h, r"(#define\s+__GLIBC_MINOR__)", r"\1 28 //") + replace_in_file(features_h, r"(#define\s+__GLIBC_MINOR__)", r"\1 26 //") + + # fcntl64() was introduced in glibc 2.28. Make sure to use fcntl() instead. + fcntl_h = os.path.join(install_root, "usr", "include", "fcntl.h") + replace_in_file( + fcntl_h, + r"#ifndef __USE_FILE_OFFSET64(\nextern int fcntl)", + r"#if 1\1", + ) # Do not use pthread_cond_clockwait as it was introduced in glibc 2.30. cppconfig_h = os.path.join( diff --git a/openwrt-packages/ddns-go/Makefile b/openwrt-packages/ddns-go/Makefile index 158065ed0f..6879329910 100644 --- a/openwrt-packages/ddns-go/Makefile +++ b/openwrt-packages/ddns-go/Makefile @@ -8,12 +8,12 @@ include $(TOPDIR)/rules.mk PKG_NAME:=ddns-go -PKG_VERSION:=6.6.6 +PKG_VERSION:=6.6.7 PKG_RELEASE:=1 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=https://codeload.github.com/jeessy2/ddns-go/tar.gz/v$(PKG_VERSION)? -PKG_HASH:=95367d8680c27a78024be339aaad77d73cda05e7005b8d9c342182e4f97c7bcc +PKG_HASH:=a2b8625ec499cfa822924bf4109b74b362438c92a7c632b7574817de8deee744 PKG_LICENSE:=MIT PKG_LICENSE_FILES:=LICENSE diff --git a/openwrt-packages/homeproxy/htdocs/luci-static/resources/view/homeproxy/client.js b/openwrt-packages/homeproxy/htdocs/luci-static/resources/view/homeproxy/client.js index ab9cdd2400..1f42007199 100644 --- a/openwrt-packages/homeproxy/htdocs/luci-static/resources/view/homeproxy/client.js +++ b/openwrt-packages/homeproxy/htdocs/luci-static/resources/view/homeproxy/client.js @@ -432,11 +432,11 @@ return view.extend({ ss.sectiontitle = L.bind(hp.loadDefaultLabel, this, data[0]); ss.renderSectionAdd = L.bind(hp.renderSectionAdd, this, ss); - ss.tab('field_other', _('Other Fields')); - ss.tab('field_host', _('Host Fields')); - ss.tab('field_port', _('Port Fields')); - ss.tab('field_source_ip', _('SRC-IP Fields')); - ss.tab('field_source_port', _('SRC-Port Fields')); + ss.tab('field_other', _('Other fields')); + ss.tab('field_host', _('Host fields')); + ss.tab('field_port', _('Port fields')); + ss.tab('field_source_ip', _('SRC-IP fields')); + ss.tab('field_source_port', _('SRC-Port fields')); so = ss.taboption('field_other', form.Value, 'label', _('Label')); so.load = L.bind(hp.loadDefaultLabel, this, data[0]); @@ -764,11 +764,11 @@ return view.extend({ ss.sectiontitle = L.bind(hp.loadDefaultLabel, this, data[0]); ss.renderSectionAdd = L.bind(hp.renderSectionAdd, this, ss); - ss.tab('field_other', _('Other Fields')); - ss.tab('field_host', _('Host Fields')); - ss.tab('field_port', _('Port Fields')); - ss.tab('field_source_ip', _('SRC-IP Fields')); - ss.tab('field_source_port', _('SRC-Port Fields')); + ss.tab('field_other', _('Other fields')); + ss.tab('field_host', _('Host fields')); + ss.tab('field_port', _('Port fields')); + ss.tab('field_source_ip', _('SRC-IP fields')); + ss.tab('field_source_port', _('SRC-Port fields')); so = ss.taboption('field_other', form.Value, 'label', _('Label')); so.load = L.bind(hp.loadDefaultLabel, this, data[0]); diff --git a/openwrt-packages/homeproxy/po/templates/homeproxy.pot b/openwrt-packages/homeproxy/po/templates/homeproxy.pot index b46a958c80..1533c050ff 100644 --- a/openwrt-packages/homeproxy/po/templates/homeproxy.pot +++ b/openwrt-packages/homeproxy/po/templates/homeproxy.pot @@ -869,7 +869,7 @@ msgstr "" #: htdocs/luci-static/resources/view/homeproxy/client.js:436 #: htdocs/luci-static/resources/view/homeproxy/client.js:768 -msgid "Host Fields" +msgid "Host fields" msgstr "" #: htdocs/luci-static/resources/view/homeproxy/node.js:709 @@ -1391,7 +1391,7 @@ msgstr "" #: htdocs/luci-static/resources/view/homeproxy/client.js:435 #: htdocs/luci-static/resources/view/homeproxy/client.js:767 -msgid "Other Fields" +msgid "Other fields" msgstr "" #: htdocs/luci-static/resources/view/homeproxy/client.js:389 @@ -1488,7 +1488,7 @@ msgstr "" #: htdocs/luci-static/resources/view/homeproxy/client.js:437 #: htdocs/luci-static/resources/view/homeproxy/client.js:769 -msgid "Port Fields" +msgid "Port fields" msgstr "" #: htdocs/luci-static/resources/view/homeproxy/client.js:537 @@ -1756,12 +1756,12 @@ msgstr "" #: htdocs/luci-static/resources/view/homeproxy/client.js:438 #: htdocs/luci-static/resources/view/homeproxy/client.js:770 -msgid "SRC-IP Fields" +msgid "SRC-IP fields" msgstr "" #: htdocs/luci-static/resources/view/homeproxy/client.js:439 #: htdocs/luci-static/resources/view/homeproxy/client.js:771 -msgid "SRC-Port Fields" +msgid "SRC-Port fields" msgstr "" #: htdocs/luci-static/resources/view/homeproxy/node.js:515 diff --git a/openwrt-packages/homeproxy/po/zh_Hans/homeproxy.po b/openwrt-packages/homeproxy/po/zh_Hans/homeproxy.po index e034f7e516..66ea254ab3 100644 --- a/openwrt-packages/homeproxy/po/zh_Hans/homeproxy.po +++ b/openwrt-packages/homeproxy/po/zh_Hans/homeproxy.po @@ -890,7 +890,7 @@ msgstr "主机名" #: htdocs/luci-static/resources/view/homeproxy/client.js:436 #: htdocs/luci-static/resources/view/homeproxy/client.js:768 -msgid "Host Fields" +msgid "Host fields" msgstr "主机字段" #: htdocs/luci-static/resources/view/homeproxy/node.js:709 @@ -1419,7 +1419,7 @@ msgstr "仅代理中国大陆" #: htdocs/luci-static/resources/view/homeproxy/client.js:435 #: htdocs/luci-static/resources/view/homeproxy/client.js:767 -msgid "Other Fields" +msgid "Other fields" msgstr "其他字段" #: htdocs/luci-static/resources/view/homeproxy/client.js:389 @@ -1516,7 +1516,7 @@ msgstr "端口 %s 已存在!" #: htdocs/luci-static/resources/view/homeproxy/client.js:437 #: htdocs/luci-static/resources/view/homeproxy/client.js:769 -msgid "Port Fields" +msgid "Port fields" msgstr "端口字段" #: htdocs/luci-static/resources/view/homeproxy/client.js:537 @@ -1784,13 +1784,13 @@ msgstr "规则集 URL" #: htdocs/luci-static/resources/view/homeproxy/client.js:438 #: htdocs/luci-static/resources/view/homeproxy/client.js:770 -msgid "SRC-IP Fields" +msgid "SRC-IP fields" msgstr "源 IP 字段" #: htdocs/luci-static/resources/view/homeproxy/client.js:439 #: htdocs/luci-static/resources/view/homeproxy/client.js:771 -msgid "SRC-Port Fields" -msgstr "源端口 字段" +msgid "SRC-Port fields" +msgstr "源端口字段" #: htdocs/luci-static/resources/view/homeproxy/node.js:515 msgid "SSH" diff --git a/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/app.sh b/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/app.sh index e03f734fde..0a1ad74bd3 100755 --- a/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/app.sh +++ b/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/app.sh @@ -926,6 +926,16 @@ run_redir() { _args="${_args} udp_redir_port=${UDP_REDIR_PORT}" config_file=$(echo $config_file | sed "s/TCP/TCP_UDP/g") } + + local protocol=$(config_n_get $node protocol) + local default_node=$(config_n_get $node default_node) + local v2ray_dns_mode=$(config_t_get global v2ray_dns_mode tcp) + [ "${DNS_MODE}" != "sing-box" ] && [ "${DNS_MODE}" != "udp" ] && [ "$protocol" = "_shunt" ] && [ "$default_node" = "_direct" ] && { + DNS_MODE="sing-box" + v2ray_dns_mode="tcp" + echolog "* 当前TCP节点采用Sing-Box分流且默认节点为直连,远程DNS过滤模式将默认使用Sing-Box(TCP),防止环回!" + } + [ "${DNS_MODE}" = "sing-box" ] && { resolve_dns=1 config_file=$(echo $config_file | sed "s/.json/_DNS.json/g") @@ -934,11 +944,8 @@ run_redir() { [ "${DNS_CACHE}" == "0" ] && _args="${_args} dns_cache=0" resolve_dns_port=${dns_listen_port} _args="${_args} dns_listen_port=${resolve_dns_port}" - local local_dns=$(echo "${LOCAL_DNS}" | sed "s/,/\n/g" | head -n1) _args="${_args} direct_dns_udp_server=${local_dns}" - - local v2ray_dns_mode=$(config_t_get global v2ray_dns_mode tcp) _args="${_args} remote_dns_protocol=${v2ray_dns_mode}" case "$v2ray_dns_mode" in tcp) @@ -981,6 +988,16 @@ run_redir() { _args="${_args} udp_redir_port=${UDP_REDIR_PORT}" config_file=$(echo $config_file | sed "s/TCP/TCP_UDP/g") } + + local protocol=$(config_n_get $node protocol) + local default_node=$(config_n_get $node default_node) + local v2ray_dns_mode=$(config_t_get global v2ray_dns_mode tcp) + [ "${DNS_MODE}" != "xray" ] && [ "${DNS_MODE}" != "udp" ] && [ "$protocol" = "_shunt" ] && [ "$default_node" = "_direct" ] && { + DNS_MODE="xray" + v2ray_dns_mode="tcp" + echolog "* 当前TCP节点采用Xray分流且默认节点为直连,远程DNS过滤模式将默认使用Xray(TCP),防止环回!" + } + [ "${DNS_MODE}" = "xray" ] && { resolve_dns=1 config_file=$(echo $config_file | sed "s/.json/_DNS.json/g") @@ -992,7 +1009,6 @@ run_redir() { resolve_dns_port=${dns_listen_port} _args="${_args} dns_listen_port=${resolve_dns_port}" _args="${_args} remote_dns_tcp_server=${REMOTE_DNS}" - local v2ray_dns_mode=$(config_t_get global v2ray_dns_mode tcp) if [ "$v2ray_dns_mode" = "tcp+doh" ]; then remote_dns_doh=$(config_t_get global remote_dns_doh "https://1.1.1.1/dns-query") _args="${_args} remote_dns_doh=${remote_dns_doh}" diff --git a/ryujinx/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManager.cs b/ryujinx/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManager.cs index 0a035916cb..da5a0ad455 100644 --- a/ryujinx/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManager.cs +++ b/ryujinx/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManager.cs @@ -18,16 +18,12 @@ namespace Ryujinx.Audio.Renderer.Server.Performance if (version == 2) { - return (ulong)PerformanceManagerGeneric.GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref parameter); + return (ulong)PerformanceManagerGeneric.GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref parameter); } if (version == 1) { - return (ulong)PerformanceManagerGeneric.GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref parameter); + return (ulong)PerformanceManagerGeneric.GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref parameter); } throw new NotImplementedException($"Unknown Performance metrics data format version {version}"); diff --git a/ryujinx/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManagerGeneric.cs b/ryujinx/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManagerGeneric.cs index 5a70a1bcff..2e5d25b9cc 100644 --- a/ryujinx/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManagerGeneric.cs +++ b/ryujinx/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManagerGeneric.cs @@ -234,7 +234,7 @@ namespace Ryujinx.Audio.Renderer.Server.Performance { performanceEntry = null; - if (_entryDetailIndex > MaxFrameDetailCount) + if (_entryDetailIndex >= MaxFrameDetailCount) { return false; } @@ -245,7 +245,7 @@ namespace Ryujinx.Audio.Renderer.Server.Performance EntryCountOffset = (uint)CurrentHeader.GetEntryCountOffset(), }; - uint baseEntryOffset = (uint)(Unsafe.SizeOf() + GetEntriesSize() + Unsafe.SizeOf() * _entryDetailIndex); + uint baseEntryOffset = (uint)(Unsafe.SizeOf() + GetEntriesSize() + Unsafe.SizeOf() * _entryDetailIndex); ref TEntryDetail entryDetail = ref EntriesDetail[_entryDetailIndex]; diff --git a/ryujinx/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/VsmsMappingArguments.cs b/ryujinx/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/VsmsMappingArguments.cs index 1b56fcc444..baada91978 100644 --- a/ryujinx/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/VsmsMappingArguments.cs +++ b/ryujinx/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/VsmsMappingArguments.cs @@ -9,5 +9,6 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu.Types public byte Sm0TpcIndex; public byte Sm1GpcIndex; public byte Sm1TpcIndex; + public uint Reserved; } } diff --git a/small/luci-app-bypass/root/www/luci-static/bypass/flags/ad.svg b/small/luci-app-bypass/root/www/luci-static/bypass/flags/ad.svg index d2fea77733..403ac8c26f 100644 --- a/small/luci-app-bypass/root/www/luci-static/bypass/flags/ad.svg +++ b/small/luci-app-bypass/root/www/luci-static/bypass/flags/ad.svg @@ -1,99 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/small/luci-app-bypass/root/www/luci-static/bypass/flags/bl.svg b/small/luci-app-bypass/root/www/luci-static/bypass/flags/bl.svg index 21b071551e..8752a4c793 100644 --- a/small/luci-app-bypass/root/www/luci-static/bypass/flags/bl.svg +++ b/small/luci-app-bypass/root/www/luci-static/bypass/flags/bl.svg @@ -1,72 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/small/luci-app-bypass/root/www/luci-static/bypass/flags/bm.svg b/small/luci-app-bypass/root/www/luci-static/bypass/flags/bm.svg deleted file mode 100644 index 3edcfb418e..0000000000 --- a/small/luci-app-bypass/root/www/luci-static/bypass/flags/bm.svg +++ /dev/null @@ -1,95 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/small/luci-app-bypass/root/www/luci-static/bypass/flags/bo.svg b/small/luci-app-bypass/root/www/luci-static/bypass/flags/bo.svg index c0ed7ba685..4ff06eed85 100644 --- a/small/luci-app-bypass/root/www/luci-static/bypass/flags/bo.svg +++ b/small/luci-app-bypass/root/www/luci-static/bypass/flags/bo.svg @@ -1,101 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/small/luci-app-bypass/root/www/luci-static/bypass/flags/bz.svg b/small/luci-app-bypass/root/www/luci-static/bypass/flags/bz.svg deleted file mode 100644 index 6fc011a566..0000000000 --- a/small/luci-app-bypass/root/www/luci-static/bypass/flags/bz.svg +++ /dev/null @@ -1,193 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/small/luci-app-bypass/root/www/luci-static/bypass/flags/cr.svg b/small/luci-app-bypass/root/www/luci-static/bypass/flags/cr.svg index 2b82895097..5167529551 100644 --- a/small/luci-app-bypass/root/www/luci-static/bypass/flags/cr.svg +++ b/small/luci-app-bypass/root/www/luci-static/bypass/flags/cr.svg @@ -1,111 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/small/luci-app-bypass/root/www/luci-static/bypass/flags/cy.svg b/small/luci-app-bypass/root/www/luci-static/bypass/flags/cy.svg index 4d83efa267..817f5f7cda 100644 --- a/small/luci-app-bypass/root/www/luci-static/bypass/flags/cy.svg +++ b/small/luci-app-bypass/root/www/luci-static/bypass/flags/cy.svg @@ -1,7 +1 @@ - - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/small/luci-app-bypass/root/www/luci-static/bypass/flags/do.svg b/small/luci-app-bypass/root/www/luci-static/bypass/flags/do.svg index f164857ac8..185adde131 100644 --- a/small/luci-app-bypass/root/www/luci-static/bypass/flags/do.svg +++ b/small/luci-app-bypass/root/www/luci-static/bypass/flags/do.svg @@ -1,103 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/small/luci-app-bypass/root/www/luci-static/bypass/flags/fj.svg b/small/luci-app-bypass/root/www/luci-static/bypass/flags/fj.svg index a314dcb9ac..cc107c90f5 100644 --- a/small/luci-app-bypass/root/www/luci-static/bypass/flags/fj.svg +++ b/small/luci-app-bypass/root/www/luci-static/bypass/flags/fj.svg @@ -1,85 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/small/luci-app-bypass/root/www/luci-static/bypass/flags/gt.svg b/small/luci-app-bypass/root/www/luci-static/bypass/flags/gt.svg index 7e38e79eff..ae5894ec36 100644 --- a/small/luci-app-bypass/root/www/luci-static/bypass/flags/gt.svg +++ b/small/luci-app-bypass/root/www/luci-static/bypass/flags/gt.svg @@ -1,109 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/small/luci-app-bypass/root/www/luci-static/bypass/flags/im.svg b/small/luci-app-bypass/root/www/luci-static/bypass/flags/im.svg deleted file mode 100644 index 6672fb48c0..0000000000 --- a/small/luci-app-bypass/root/www/luci-static/bypass/flags/im.svg +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/small/luci-app-bypass/root/www/luci-static/bypass/flags/ir.svg b/small/luci-app-bypass/root/www/luci-static/bypass/flags/ir.svg deleted file mode 100644 index 22da09ed18..0000000000 --- a/small/luci-app-bypass/root/www/luci-static/bypass/flags/ir.svg +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/small/luci-app-bypass/root/www/luci-static/bypass/flags/je.svg b/small/luci-app-bypass/root/www/luci-static/bypass/flags/je.svg index 55c3c505c3..84aa07263a 100644 --- a/small/luci-app-bypass/root/www/luci-static/bypass/flags/je.svg +++ b/small/luci-app-bypass/root/www/luci-static/bypass/flags/je.svg @@ -1,89 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/small/luci-app-bypass/root/www/luci-static/bypass/flags/ky.svg b/small/luci-app-bypass/root/www/luci-static/bypass/flags/ky.svg deleted file mode 100644 index 6b88f63de5..0000000000 --- a/small/luci-app-bypass/root/www/luci-static/bypass/flags/ky.svg +++ /dev/null @@ -1,131 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/small/luci-app-bypass/root/www/luci-static/bypass/flags/kz.svg b/small/luci-app-bypass/root/www/luci-static/bypass/flags/kz.svg index 86ad610842..0a13f95491 100644 --- a/small/luci-app-bypass/root/www/luci-static/bypass/flags/kz.svg +++ b/small/luci-app-bypass/root/www/luci-static/bypass/flags/kz.svg @@ -1,72 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/small/luci-app-bypass/root/www/luci-static/bypass/flags/li.svg b/small/luci-app-bypass/root/www/luci-static/bypass/flags/li.svg deleted file mode 100644 index 138b09315e..0000000000 --- a/small/luci-app-bypass/root/www/luci-static/bypass/flags/li.svg +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/small/luci-app-bypass/root/www/luci-static/bypass/flags/lk.svg b/small/luci-app-bypass/root/www/luci-static/bypass/flags/lk.svg deleted file mode 100644 index 48a53d4e36..0000000000 --- a/small/luci-app-bypass/root/www/luci-static/bypass/flags/lk.svg +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/small/luci-app-bypass/root/www/luci-static/bypass/flags/loading.svg b/small/luci-app-bypass/root/www/luci-static/bypass/flags/loading.svg index c81ce816aa..13f20c2e2b 100644 --- a/small/luci-app-bypass/root/www/luci-static/bypass/flags/loading.svg +++ b/small/luci-app-bypass/root/www/luci-static/bypass/flags/loading.svg @@ -1,13 +1 @@ - - - - - - - - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/small/luci-app-bypass/root/www/luci-static/bypass/flags/md.svg b/small/luci-app-bypass/root/www/luci-static/bypass/flags/md.svg index c75c81a752..71e4d1fd1a 100644 --- a/small/luci-app-bypass/root/www/luci-static/bypass/flags/md.svg +++ b/small/luci-app-bypass/root/www/luci-static/bypass/flags/md.svg @@ -1,89 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/small/luci-app-bypass/root/www/luci-static/bypass/flags/me.svg b/small/luci-app-bypass/root/www/luci-static/bypass/flags/me.svg deleted file mode 100644 index 6cd9cb479d..0000000000 --- a/small/luci-app-bypass/root/www/luci-static/bypass/flags/me.svg +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/small/luci-app-bypass/root/www/luci-static/bypass/flags/mq.svg b/small/luci-app-bypass/root/www/luci-static/bypass/flags/mq.svg index ac91f6a95a..3343a0c287 100644 --- a/small/luci-app-bypass/root/www/luci-static/bypass/flags/mq.svg +++ b/small/luci-app-bypass/root/www/luci-static/bypass/flags/mq.svg @@ -1,42 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/small/luci-app-bypass/root/www/luci-static/bypass/flags/pn.svg b/small/luci-app-bypass/root/www/luci-static/bypass/flags/pn.svg deleted file mode 100644 index ada7b3125b..0000000000 --- a/small/luci-app-bypass/root/www/luci-static/bypass/flags/pn.svg +++ /dev/null @@ -1,113 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/small/luci-app-bypass/root/www/luci-static/bypass/flags/sa.svg b/small/luci-app-bypass/root/www/luci-static/bypass/flags/sa.svg index ccfd3f763e..1d882a70b9 100644 --- a/small/luci-app-bypass/root/www/luci-static/bypass/flags/sa.svg +++ b/small/luci-app-bypass/root/www/luci-static/bypass/flags/sa.svg @@ -1,62 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/small/luci-app-bypass/root/www/luci-static/bypass/flags/sm.svg b/small/luci-app-bypass/root/www/luci-static/bypass/flags/sm.svg index 6cdbe30f91..b535040322 100644 --- a/small/luci-app-bypass/root/www/luci-static/bypass/flags/sm.svg +++ b/small/luci-app-bypass/root/www/luci-static/bypass/flags/sm.svg @@ -1,118 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/small/luci-app-bypass/root/www/luci-static/bypass/flags/sv.svg b/small/luci-app-bypass/root/www/luci-static/bypass/flags/sv.svg index 7f82ce1c51..5debda96a5 100644 --- a/small/luci-app-bypass/root/www/luci-static/bypass/flags/sv.svg +++ b/small/luci-app-bypass/root/www/luci-static/bypass/flags/sv.svg @@ -1,115 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/small/luci-app-bypass/root/www/luci-static/bypass/flags/us.svg b/small/luci-app-bypass/root/www/luci-static/bypass/flags/us.svg index c678d9ad0b..8e31c6726e 100644 --- a/small/luci-app-bypass/root/www/luci-static/bypass/flags/us.svg +++ b/small/luci-app-bypass/root/www/luci-static/bypass/flags/us.svg @@ -1,88 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/small/luci-app-bypass/root/www/luci-static/bypass/flags/va.svg b/small/luci-app-bypass/root/www/luci-static/bypass/flags/va.svg deleted file mode 100644 index bed2d51fd8..0000000000 --- a/small/luci-app-bypass/root/www/luci-static/bypass/flags/va.svg +++ /dev/null @@ -1,96 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/small/luci-app-bypass/root/www/luci-static/bypass/flags/vg.svg b/small/luci-app-bypass/root/www/luci-static/bypass/flags/vg.svg deleted file mode 100644 index c417b2b0a8..0000000000 --- a/small/luci-app-bypass/root/www/luci-static/bypass/flags/vg.svg +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/small/luci-app-bypass/root/www/luci-static/bypass/flags/zm.svg b/small/luci-app-bypass/root/www/luci-static/bypass/flags/zm.svg index c36062d2c0..0bbdbb0834 100644 --- a/small/luci-app-bypass/root/www/luci-static/bypass/flags/zm.svg +++ b/small/luci-app-bypass/root/www/luci-static/bypass/flags/zm.svg @@ -1,42 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/small/luci-app-passwall/root/usr/share/passwall/app.sh b/small/luci-app-passwall/root/usr/share/passwall/app.sh index e03f734fde..0a1ad74bd3 100755 --- a/small/luci-app-passwall/root/usr/share/passwall/app.sh +++ b/small/luci-app-passwall/root/usr/share/passwall/app.sh @@ -926,6 +926,16 @@ run_redir() { _args="${_args} udp_redir_port=${UDP_REDIR_PORT}" config_file=$(echo $config_file | sed "s/TCP/TCP_UDP/g") } + + local protocol=$(config_n_get $node protocol) + local default_node=$(config_n_get $node default_node) + local v2ray_dns_mode=$(config_t_get global v2ray_dns_mode tcp) + [ "${DNS_MODE}" != "sing-box" ] && [ "${DNS_MODE}" != "udp" ] && [ "$protocol" = "_shunt" ] && [ "$default_node" = "_direct" ] && { + DNS_MODE="sing-box" + v2ray_dns_mode="tcp" + echolog "* 当前TCP节点采用Sing-Box分流且默认节点为直连,远程DNS过滤模式将默认使用Sing-Box(TCP),防止环回!" + } + [ "${DNS_MODE}" = "sing-box" ] && { resolve_dns=1 config_file=$(echo $config_file | sed "s/.json/_DNS.json/g") @@ -934,11 +944,8 @@ run_redir() { [ "${DNS_CACHE}" == "0" ] && _args="${_args} dns_cache=0" resolve_dns_port=${dns_listen_port} _args="${_args} dns_listen_port=${resolve_dns_port}" - local local_dns=$(echo "${LOCAL_DNS}" | sed "s/,/\n/g" | head -n1) _args="${_args} direct_dns_udp_server=${local_dns}" - - local v2ray_dns_mode=$(config_t_get global v2ray_dns_mode tcp) _args="${_args} remote_dns_protocol=${v2ray_dns_mode}" case "$v2ray_dns_mode" in tcp) @@ -981,6 +988,16 @@ run_redir() { _args="${_args} udp_redir_port=${UDP_REDIR_PORT}" config_file=$(echo $config_file | sed "s/TCP/TCP_UDP/g") } + + local protocol=$(config_n_get $node protocol) + local default_node=$(config_n_get $node default_node) + local v2ray_dns_mode=$(config_t_get global v2ray_dns_mode tcp) + [ "${DNS_MODE}" != "xray" ] && [ "${DNS_MODE}" != "udp" ] && [ "$protocol" = "_shunt" ] && [ "$default_node" = "_direct" ] && { + DNS_MODE="xray" + v2ray_dns_mode="tcp" + echolog "* 当前TCP节点采用Xray分流且默认节点为直连,远程DNS过滤模式将默认使用Xray(TCP),防止环回!" + } + [ "${DNS_MODE}" = "xray" ] && { resolve_dns=1 config_file=$(echo $config_file | sed "s/.json/_DNS.json/g") @@ -992,7 +1009,6 @@ run_redir() { resolve_dns_port=${dns_listen_port} _args="${_args} dns_listen_port=${resolve_dns_port}" _args="${_args} remote_dns_tcp_server=${REMOTE_DNS}" - local v2ray_dns_mode=$(config_t_get global v2ray_dns_mode tcp) if [ "$v2ray_dns_mode" = "tcp+doh" ]; then remote_dns_doh=$(config_t_get global remote_dns_doh "https://1.1.1.1/dns-query") _args="${_args} remote_dns_doh=${remote_dns_doh}" diff --git a/small/v2ray-geodata/Makefile b/small/v2ray-geodata/Makefile index 0ddec9b22a..bc0a869f24 100644 --- a/small/v2ray-geodata/Makefile +++ b/small/v2ray-geodata/Makefile @@ -12,13 +12,13 @@ PKG_MAINTAINER:=Tianling Shen include $(INCLUDE_DIR)/package.mk -GEOIP_VER:=202407250045 +GEOIP_VER:=202408010050 GEOIP_FILE:=geoip.dat.$(GEOIP_VER) define Download/geoip URL:=https://github.com/v2fly/geoip/releases/download/$(GEOIP_VER)/ URL_FILE:=geoip.dat FILE:=$(GEOIP_FILE) - HASH:=f83e89edfd3b35acbbbb862a4c88a8ca3e1ddce4d298cc617be79bdaa23a0672 + HASH:=86f2973bad78ce7baa1edef30e664076105c1e5cdcc35c3f2982e47890e5ef1d endef GEOSITE_VER:=20240726161926 diff --git a/v2rayng/V2rayNG/app/src/main/assets/custom_routing_direct b/v2rayng/V2rayNG/app/src/main/assets/custom_routing_direct index abcaee6640..083428bfce 100644 --- a/v2rayng/V2rayNG/app/src/main/assets/custom_routing_direct +++ b/v2rayng/V2rayNG/app/src/main/assets/custom_routing_direct @@ -1,2 +1 @@ -geosite:cn, -geosite:geolocation-cn \ No newline at end of file +geosite:cn \ No newline at end of file diff --git a/v2rayng/V2rayNG/app/src/main/kotlin/com/v2ray/ang/AppConfig.kt b/v2rayng/V2rayNG/app/src/main/kotlin/com/v2ray/ang/AppConfig.kt index f7d9db93e1..1dc544f8e2 100644 --- a/v2rayng/V2rayNG/app/src/main/kotlin/com/v2ray/ang/AppConfig.kt +++ b/v2rayng/V2rayNG/app/src/main/kotlin/com/v2ray/ang/AppConfig.kt @@ -87,6 +87,8 @@ object AppConfig { const val TAG_DIRECT = "direct" const val TAG_BLOCKED = "block" const val TAG_FRAGMENT = "fragment" + const val UPLINK = "uplink" + const val DOWNLINK = "downlink" const val androidpackagenamelistUrl = "https://raw.githubusercontent.com/2dust/androidpackagenamelist/master/proxy.txt" @@ -128,4 +130,7 @@ object AppConfig { const val MSG_MEASURE_CONFIG = 7 const val MSG_MEASURE_CONFIG_SUCCESS = 71 const val MSG_MEASURE_CONFIG_CANCEL = 72 + + const val CHANNEL_ID = "RAY_NG_M_CH_ID" + const val CHANNEL_NAME = "V2rayNG Background Service" } diff --git a/v2rayng/V2rayNG/app/src/main/kotlin/com/v2ray/ang/extension/_Ext.kt b/v2rayng/V2rayNG/app/src/main/kotlin/com/v2ray/ang/extension/_Ext.kt index 9b764c0d15..7183746703 100644 --- a/v2rayng/V2rayNG/app/src/main/kotlin/com/v2ray/ang/extension/_Ext.kt +++ b/v2rayng/V2rayNG/app/src/main/kotlin/com/v2ray/ang/extension/_Ext.kt @@ -9,15 +9,15 @@ import org.json.JSONObject import java.net.URI import java.net.URLConnection -val Context.v2RayApplication: AngApplication - get() = applicationContext as AngApplication +val Context.v2RayApplication: AngApplication? + get() = applicationContext as? AngApplication fun Context.toast(message: Int) { - ToastCompat.makeText(this, message, Toast.LENGTH_SHORT).apply { show() } + ToastCompat.makeText(this, message, Toast.LENGTH_SHORT).show() } fun Context.toast(message: CharSequence) { - ToastCompat.makeText(this, message, Toast.LENGTH_SHORT).apply { show() } + ToastCompat.makeText(this, message, Toast.LENGTH_SHORT).show() } fun JSONObject.putOpt(pair: Pair) { @@ -34,26 +34,14 @@ const val DIVISOR = 1024.0 fun Long.toSpeedString(): String = this.toTrafficString() + "/s" fun Long.toTrafficString(): String { - if (this < THRESHOLD) { - return "$this B" + val units = arrayOf("B", "KB", "MB", "GB", "TB", "PB") + var size = this.toDouble() + var unitIndex = 0 + while (size >= THRESHOLD && unitIndex < units.size - 1) { + size /= DIVISOR + unitIndex++ } - val kb = this / DIVISOR - if (kb < THRESHOLD) { - return "${String.format("%.1f KB", kb)}" - } - val mb = kb / DIVISOR - if (mb < THRESHOLD) { - return "${String.format("%.1f MB", mb)}" - } - val gb = mb / DIVISOR - if (gb < THRESHOLD) { - return "${String.format("%.1f GB", gb)}" - } - val tb = gb / DIVISOR - if (tb < THRESHOLD) { - return "${String.format("%.1f TB", tb)}" - } - return String.format("%.1f PB", tb / DIVISOR) + return String.format("%.1f %s", size, units[unitIndex]) } val URLConnection.responseLength: Long diff --git a/v2rayng/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayProxyOnlyService.kt b/v2rayng/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayProxyOnlyService.kt index 3403af6127..d3f6a29679 100644 --- a/v2rayng/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayProxyOnlyService.kt +++ b/v2rayng/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayProxyOnlyService.kt @@ -49,7 +49,7 @@ class V2RayProxyOnlyService : Service(), ServiceControl { @RequiresApi(Build.VERSION_CODES.N) override fun attachBaseContext(newBase: Context?) { val context = newBase?.let { - MyContextWrapper.wrap(newBase, Utils.getLocale(newBase)) + MyContextWrapper.wrap(newBase, Utils.getLocale()) } super.attachBaseContext(context) } diff --git a/v2rayng/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayServiceManager.kt b/v2rayng/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayServiceManager.kt index 82cf11884f..ee32417db6 100644 --- a/v2rayng/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayServiceManager.kt +++ b/v2rayng/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayServiceManager.kt @@ -27,8 +27,8 @@ import com.v2ray.ang.util.MmkvManager import com.v2ray.ang.util.Utils import com.v2ray.ang.util.V2rayConfigUtil import go.Seq +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import libv2ray.Libv2ray import libv2ray.V2RayPoint @@ -175,7 +175,7 @@ object V2RayServiceManager { val service = serviceControl?.get()?.getService() ?: return if (v2rayPoint.isRunning) { - GlobalScope.launch(Dispatchers.Default) { + CoroutineScope(Dispatchers.IO).launch { try { v2rayPoint.stopLoop() } catch (e: Exception) { @@ -237,7 +237,7 @@ object V2RayServiceManager { } private fun measureV2rayDelay() { - GlobalScope.launch(Dispatchers.IO) { + CoroutineScope(Dispatchers.IO).launch { val service = serviceControl?.get()?.getService() ?: return@launch var time = -1L var errstr = "" @@ -319,8 +319,8 @@ object V2RayServiceManager { @RequiresApi(Build.VERSION_CODES.O) private fun createNotificationChannel(): String { - val channelId = "RAY_NG_M_CH_ID" - val channelName = "V2rayNG Background Service" + val channelId = AppConfig.CHANNEL_ID + val channelName = AppConfig.CHANNEL_NAME val chan = NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_HIGH) chan.lightColor = Color.DKGRAY @@ -376,15 +376,15 @@ object V2RayServiceManager { var proxyTotal = 0L val text = StringBuilder() outboundTags?.forEach { - val up = v2rayPoint.queryStats(it, "uplink") - val down = v2rayPoint.queryStats(it, "downlink") + val up = v2rayPoint.queryStats(it, AppConfig.UPLINK) + val down = v2rayPoint.queryStats(it, AppConfig.DOWNLINK) if (up + down > 0) { appendSpeedString(text, it, up / sinceLastQueryInSeconds, down / sinceLastQueryInSeconds) proxyTotal += up + down } } - val directUplink = v2rayPoint.queryStats(TAG_DIRECT, "uplink") - val directDownlink = v2rayPoint.queryStats(TAG_DIRECT, "downlink") + val directUplink = v2rayPoint.queryStats(TAG_DIRECT, AppConfig.UPLINK) + val directDownlink = v2rayPoint.queryStats(TAG_DIRECT, AppConfig.DOWNLINK) val zeroSpeed = proxyTotal == 0L && directUplink == 0L && directDownlink == 0L if (!zeroSpeed || !lastZeroSpeed) { if (proxyTotal == 0L) { diff --git a/v2rayng/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayVpnService.kt b/v2rayng/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayVpnService.kt index 803c6b7c8a..1f97c2aca0 100644 --- a/v2rayng/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayVpnService.kt +++ b/v2rayng/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayVpnService.kt @@ -327,7 +327,7 @@ class V2RayVpnService : VpnService(), ServiceControl { @RequiresApi(Build.VERSION_CODES.N) override fun attachBaseContext(newBase: Context?) { val context = newBase?.let { - MyContextWrapper.wrap(newBase, Utils.getLocale(newBase)) + MyContextWrapper.wrap(newBase, Utils.getLocale()) } super.attachBaseContext(context) } diff --git a/v2rayng/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/BaseActivity.kt b/v2rayng/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/BaseActivity.kt index e865c31b48..db9e1d3f92 100644 --- a/v2rayng/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/BaseActivity.kt +++ b/v2rayng/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/BaseActivity.kt @@ -34,7 +34,7 @@ abstract class BaseActivity : AppCompatActivity() { @RequiresApi(Build.VERSION_CODES.N) override fun attachBaseContext(newBase: Context?) { val context = newBase?.let { - MyContextWrapper.wrap(newBase, Utils.getLocale(newBase)) + MyContextWrapper.wrap(newBase, Utils.getLocale()) } super.attachBaseContext(context) } diff --git a/v2rayng/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/Utils.kt b/v2rayng/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/Utils.kt index 9ebc42b54e..981200043c 100644 --- a/v2rayng/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/Utils.kt +++ b/v2rayng/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/Utils.kt @@ -112,12 +112,12 @@ object Utils { fun tryDecodeBase64(text: String?): String? { try { - return Base64.decode(text, Base64.NO_WRAP).toString(charset("UTF-8")) + return Base64.decode(text, Base64.NO_WRAP).toString(Charsets.UTF_8) } catch (e: Exception) { Log.i(ANG_PACKAGE, "Parse base64 standard failed $e") } try { - return Base64.decode(text, Base64.NO_WRAP.or(Base64.URL_SAFE)).toString(charset("UTF-8")) + return Base64.decode(text, Base64.NO_WRAP.or(Base64.URL_SAFE)).toString(Charsets.UTF_8) } catch (e: Exception) { Log.i(ANG_PACKAGE, "Parse base64 url safe failed $e") } @@ -129,7 +129,7 @@ object Utils { */ fun encode(text: String): String { return try { - Base64.encodeToString(text.toByteArray(charset("UTF-8")), Base64.NO_WRAP) + Base64.encodeToString(text.toByteArray(Charsets.UTF_8), Base64.NO_WRAP) } catch (e: Exception) { e.printStackTrace() "" @@ -228,7 +228,7 @@ object Utils { } private fun isCoreDNSAddress(s: String): Boolean { - return s.startsWith("https") || s.startsWith("tcp") || s.startsWith("quic") + return s.startsWith("https") || s.startsWith("tcp") || s.startsWith("quic") || s == "localhost" } /** @@ -288,7 +288,7 @@ object Utils { fun urlDecode(url: String): String { return try { - URLDecoder.decode(url, "UTF-8") + URLDecoder.decode(url, Charsets.UTF_8.toString()) } catch (e: Exception) { e.printStackTrace() url @@ -297,7 +297,7 @@ object Utils { fun urlEncode(url: String): String { return try { - URLEncoder.encode(url, "UTF-8") + URLEncoder.encode(url, Charsets.UTF_8.toString()) } catch (e: Exception) { e.printStackTrace() url @@ -336,7 +336,7 @@ object Utils { } fun getDeviceIdForXUDPBaseKey(): String { - val androidId = Settings.Secure.ANDROID_ID.toByteArray(charset("UTF-8")) + val androidId = Settings.Secure.ANDROID_ID.toByteArray(Charsets.UTF_8) return Base64.encodeToString(androidId.copyOf(32), Base64.NO_PADDING.or(Base64.URL_SAFE)) } @@ -412,7 +412,7 @@ object Utils { } } - fun getLocale(context: Context): Locale = + fun getLocale(): Locale = when (settingsStorage?.decodeString(AppConfig.PREF_LANGUAGE) ?: "auto") { "auto" -> getSysLocale() "en" -> Locale("en") @@ -421,6 +421,7 @@ object Utils { "vi" -> Locale("vi") "ru" -> Locale("ru") "fa" -> Locale("fa") + "bn" -> Locale("bn") else -> getSysLocale() } @@ -449,18 +450,14 @@ object Utils { fun isTv(context: Context): Boolean = context.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK) - fun getDelayTestUrl(): String { - val url = settingsStorage.decodeString(AppConfig.PREF_DELAY_TEST_URL) - return if (url.isNullOrEmpty()) AppConfig.DelayTestUrl else url - } - fun getDelayTestUrl(second: Boolean = false): String { return if (second) { AppConfig.DelayTestUrl2 } else { - val url = settingsStorage.decodeString(AppConfig.PREF_DELAY_TEST_URL) - if (url.isNullOrEmpty()) AppConfig.DelayTestUrl else url + settingsStorage.decodeString(AppConfig.PREF_DELAY_TEST_URL) ?: AppConfig.DelayTestUrl } } + + } diff --git a/v2rayng/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt b/v2rayng/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt index ee4f09d07e..aa3bec9bb2 100644 --- a/v2rayng/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt +++ b/v2rayng/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt @@ -8,8 +8,10 @@ import com.tencent.mmkv.MMKV import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig.ANG_PACKAGE import com.v2ray.ang.AppConfig.PROTOCOL_FREEDOM +import com.v2ray.ang.AppConfig.TAG_BLOCKED import com.v2ray.ang.AppConfig.TAG_DIRECT import com.v2ray.ang.AppConfig.TAG_FRAGMENT +import com.v2ray.ang.AppConfig.TAG_PROXY import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_ADDRESS_V4 import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_ADDRESS_V6 import com.v2ray.ang.dto.EConfigType @@ -186,25 +188,25 @@ object V2rayConfigUtil { routingUserRule( settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_BLOCKED) - ?: "", AppConfig.TAG_BLOCKED, v2rayConfig + ?: "", TAG_BLOCKED, v2rayConfig ) if (routingMode == ERoutingMode.GLOBAL_DIRECT.value) { - routingUserRule( - settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_AGENT) - ?: "", AppConfig.TAG_PROXY, v2rayConfig - ) routingUserRule( settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_DIRECT) ?: "", TAG_DIRECT, v2rayConfig ) + routingUserRule( + settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_AGENT) + ?: "", TAG_PROXY, v2rayConfig + ) } else { routingUserRule( - settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_DIRECT) - ?: "", TAG_DIRECT, v2rayConfig + settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_AGENT) + ?: "", TAG_PROXY, v2rayConfig ) routingUserRule( - settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_AGENT) - ?: "", AppConfig.TAG_PROXY, v2rayConfig + settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_DIRECT) + ?: "", TAG_DIRECT, v2rayConfig ) } @@ -214,7 +216,7 @@ object V2rayConfigUtil { // Hardcode googleapis.cn gstatic.com val googleapisRoute = V2rayConfig.RoutingBean.RulesBean( - outboundTag = AppConfig.TAG_PROXY, + outboundTag = TAG_PROXY, domain = arrayListOf("domain:googleapis.cn", "domain:gstatic.com") ) @@ -225,14 +227,12 @@ object V2rayConfigUtil { ERoutingMode.BYPASS_MAINLAND.value -> { routingGeo("", "cn", TAG_DIRECT, v2rayConfig) - routingGeo("domain", "geolocation-cn", TAG_DIRECT, v2rayConfig) v2rayConfig.routing.rules.add(0, googleapisRoute) } ERoutingMode.BYPASS_LAN_MAINLAND.value -> { routingGeo("ip", "private", TAG_DIRECT, v2rayConfig) routingGeo("", "cn", TAG_DIRECT, v2rayConfig) - routingGeo("domain", "geolocation-cn", TAG_DIRECT, v2rayConfig) v2rayConfig.routing.rules.add(0, googleapisRoute) } @@ -251,7 +251,7 @@ object V2rayConfigUtil { if (routingMode != ERoutingMode.GLOBAL_DIRECT.value) { val globalProxy = V2rayConfig.RoutingBean.RulesBean( - outboundTag = AppConfig.TAG_PROXY, + outboundTag = TAG_PROXY, ) if (v2rayConfig.routing.domainStrategy != "IPIfNonMatch") { globalProxy.port = "0-65535" @@ -469,7 +469,7 @@ object V2rayConfigUtil { ) } if (isCnRoutingMode) { - val geositeCn = arrayListOf("geosite:cn", "geosite:geolocation-cn") + val geositeCn = arrayListOf("geosite:cn") servers.add( V2rayConfig.DnsBean.ServersBean( domesticDns.first(), @@ -513,7 +513,7 @@ object V2rayConfigUtil { if (Utils.isPureIpAddress(remoteDns.first())) { v2rayConfig.routing.rules.add( 0, V2rayConfig.RoutingBean.RulesBean( - outboundTag = AppConfig.TAG_PROXY, + outboundTag = TAG_PROXY, port = "53", ip = arrayListOf(remoteDns.first()), domain = null diff --git a/v2rayng/V2rayNG/app/src/main/res/values-bn/strings.xml b/v2rayng/V2rayNG/app/src/main/res/values-bn/strings.xml new file mode 100644 index 0000000000..6accba407b --- /dev/null +++ b/v2rayng/V2rayNG/app/src/main/res/values-bn/strings.xml @@ -0,0 +1,306 @@ + + + v2rayNG + সুইচ + সুইচ + এই ফিচারটি প্রথম ব্যবহার করা হচ্ছে, সার্ভার যোগ করতে অ্যাপটি ব্যবহার করুন + নেভিগেশন ড্রয়ার খোলুন + নেভিগেশন ড্রয়ার বন্ধ করুন + ডেটা স্থানান্তর সফল! + ডেটা স্থানান্তর ব্যর্থ! + + + বন্ধ করুন + অনুমতি পাওয়া যাচ্ছে না + আরও দেখতে ক্লিক করুন + সার্ভিস শুরু করুন + সার্ভিস বন্ধ করুন + সার্ভিস সফলভাবে শুরু হয়েছে + সার্ভিস শুরু হতে ব্যর্থ হয়েছে + + + কনফিগারেশন ফাইল + কনফিগারেশন যোগ করুন + কনফিগারেশন সংরক্ষণ করুন + কনফিগারেশন মুছুন + QR কোড থেকে কনফিগারেশন আমদানি করুন + ক্লিপবোর্ড থেকে কনফিগারেশন আমদানি করুন + ম্যানুয়ালি টাইপ করুন [Vmess] + ম্যানুয়ালি টাইপ করুন [VLESS] + ম্যানুয়ালি টাইপ করুন [Shadowsocks] + ম্যানুয়ালি টাইপ করুন [Socks] + ম্যানুয়ালি টাইপ করুন [Trojan] + ম্যানুয়ালি টাইপ করুন [Wireguard] + কাস্টম কনফিগারেশন + ক্লিপবোর্ড থেকে কাস্টম কনফিগারেশন আমদানি করুন + স্থানীয়ভাবে কাস্টম কনফিগারেশন আমদানি করুন + URL থেকে কাস্টম কনফিগারেশন আমদানি করুন + কাস্টম কনফিগারেশন স্ক্যান URL আমদানি করুন + মুছে ফেলুন নিশ্চিত করুন? + মন্তব্য + ঠিকানা + পোর্ট + আইডি + অলটারআইডি + নিরাপত্তা + নেটওয়ার্ক + ট্রান্সপোর্ট + হেড টাইপ + gRPC মোড + হোস্ট + http হোস্ট + ws হোস্ট + httpupgrade হোস্ট + splithttp হোস্ট + h2 হোস্ট + QUIC নিরাপত্তা + gRPC কর্তৃপক্ষ + পথ + ws পথ + httpupgrade পথ + splithttp পথ + h2 পথ + QUIC কী + kcp বীজ + gRPC সেবার নাম + TLS + ফিঙ্গারপ্রিন্ট + Alpn + অনিরাপদ অনুমতি দিন + SNI + ঠিকানা + পোর্ট + পাসওয়ার্ড + নিরাপত্তা + পাসওয়ার্ড (ঐচ্ছিক) + ব্যবহারকারী (ঐচ্ছিক) + এনক্রিপশন + ফ্লো + পাবলিক কী + শর্ট আইডি + SpiderX + সিক্রেট কী + সংরক্ষিত (ঐচ্ছিক) + স্থানীয় ঠিকানা (ঐচ্ছিক IPv4/IPv6, কমা দ্বারা পৃথক করা) + MTU (ঐচ্ছিক, ডিফল্ট 1420) + সফল + ব্যর্থ + কোনও তথ্য নেই + ভুল প্রোটোকল + ডিকোডিং ব্যর্থ + একটি কনফিগারেশন ফাইল নির্বাচন করুন + অনুগ্রহ করে একটি ফাইল ম্যানেজার ইনস্টল করুন। + কাস্টমাইজ কনফিগারেশন + অবৈধ কনফিগারেশন + কনটেন্ট + ক্লিপবোর্ডে কোনও তথ্য নেই + অবৈধ URL + ইনবাউন্ড পোর্ট নিশ্চিত করুন সেটিংসের সাথে সামঞ্জস্যপূর্ণ + কনফিগারেশন বিকৃত + হোস্ট (SNI) (ঐচ্ছিক) + ফাইল কপি ব্যর্থ, অনুগ্রহ করে ফাইল ম্যানেজার ব্যবহার করুন + অ্যাসেট যোগ করুন + ফাইল যোগ করুন + URL যোগ করুন + URL + ফাইল ডাউনলোড করুন + অ্যাসেট URL যোগ করুন + ফাইল খুঁজে পাওয়া যায়নি + মন্তব্য ইতিমধ্যে বিদ্যমান + অ্যাকশন অনুমোদিত নয় + + লোড হচ্ছে + অনুসন্ধান করুন + সবকিছু নির্বাচন করুন + কীওয়ার্ড লিখুন + বাইপাস মোড + অটো সিলেক্ট প্রক্সি অ্যাপ + বিষয়বস্তু ডাউনলোড হচ্ছে + ক্লিপবোর্ডে রপ্তানি করুন + ক্লিপবোর্ড থেকে আমদানি করুন + + + সেটিংস + এডভান্সড সেটিংস + VPN সেটিংস + প্রতি-অ্যাপ প্রক্সি + সাধারণ: চেকড অ্যাপ প্রক্সি, আনচেকড সরাসরি সংযোগ; \nবাইপাস মোড: চেকড অ্যাপ সরাসরি সংযুক্ত, আনচেকড প্রক্সি। \nমেনুতে প্রক্সি অ্যাপ্লিকেশন স্বয়ংক্রিয়ভাবে নির্বাচন করার বিকল্প + + Mux সেটিংস + Mux সক্রিয় করুন + দ্রুত, তবে এটি অস্থিতিশীল সংযোগ সৃষ্টি করতে পারে\nTCP, UDP এবং QUIC পরিচালনা কাস্টমাইজ করুন + TCP সংযোগ (পরিসর -1 থেকে 1024) + XUDP সংযোগ (পরিসর -1 থেকে 1024) + Mux টানেলে QUIC পরিচালনা + + অস্বীকার করুন + অনুমতি দিন + এড়িয়ে যান + + + গতি প্রদর্শন সক্রিয় করুন + বিজ্ঞপ্তিতে বর্তমান গতি প্রদর্শন করুন।\nব্যবহারের উপর ভিত্তি করে বিজ্ঞপ্তি আইকন পরিবর্তিত হবে। + + স্নিফিং সক্রিয় করুন + প্যাকেট থেকে ডোমেইন স্নিফ করার চেষ্টা করুন (ডিফল্টভাবে চালু) + + রুটঅনলি সক্রিয় করুন + রুটিংয়ের জন্য শুধুমাত্র স্নিফড ডোমেইন নাম ব্যবহার করুন, এবং লক্ষ্য ঠিকানা হিসেবে আইপি ঠিকানা রাখুন। + + স্থানীয় DNS সক্রিয় করুন + DNS মূল DNS মডিউল দ্বারা প্রক্রিয়া করা হয় (প্রস্তাবিত, যদি LAN এবং মূলভূমি ঠিকানার বাইপাসিং প্রয়োজন হয়) + + ভুয়া DNS সক্রিয় করুন + স্থানীয় DNS মিথ্যা আইপি ঠিকানা ফেরত দেয় (দ্রুত, তবে এটি কিছু অ্যাপের জন্য কাজ নাও করতে পারে) + + IPv6 অগ্রাধিকার দিন + IPv6 ঠিকানা এবং রুটকে অগ্রাধিকার দিন + + রাউটিং + ডোমেইন কৌশল + পূর্বনির্ধারিত নিয়ম + কাস্টম নিয়ম + + রিমোট DNS (udp/tcp/https/quic)(ঐচ্ছিক) + DNS + + VPN DNS (শুধুমাত্র IPv4/v6) + + ঘরোয়া DNS (ঐচ্ছিক) + DNS + + সঠিক বিলম্ব পরীক্ষা ইউআরএল (http/https) + ইউআরএল + + LAN থেকে সংযোগ অনুমোদন করুন + অন্যান্য ডিভাইসগুলি আপনার আইপি ঠিকানা ব্যবহার করে প্রক্সিতে সংযুক্ত হতে পারে, শুধুমাত্র বিশ্বস্ত নেটওয়ার্কে সক্রিয় করুন যাতে অনুমোদিত সংযোগ এড়ানো যায় + LAN থেকে সংযোগ অনুমোদন করুন, নিশ্চিত করুন যে আপনি একটি বিশ্বস্ত নেটওয়ার্কে আছেন + + allowInsecure + যখন TLS, ডিফল্টভাবে allowInsecure + + SOCKS5 প্রক্সি পোর্ট + SOCKS5 প্রক্সি পোর্ট + + HTTP প্রক্সি পোর্ট + HTTP প্রক্সি পোর্ট + + স্থানীয় DNS পোর্ট + স্থানীয় DNS পোর্ট + + কনফিগারেশন ফাইল মুছে ফেলার নিশ্চিতকরণ + কনফিগারেশন ফাইল মুছে ফেলার জন্য ব্যবহারকারীর দ্বিতীয় নিশ্চিতকরণের প্রয়োজন + + তাত্ক্ষণিক স্ক্যান শুরু করুন + শুরুতে তাত্ক্ষণিকভাবে স্ক্যান করতে ক্যামেরা খুলুন, অন্যথায় আপনি কোড স্ক্যান বা টুলবারে একটি ছবি নির্বাচন করতে পারেন + মতামত + মতামত উন্নয়ন বা বাগগুলি GitHub-এ পাঠান + টেলিগ্রাম গ্রুপে যোগদান করুন + টেলিগ্রাম অ্যাপ পাওয়া যায়নি + গোপনীয়তা নীতি + সম্পর্কিত + সোর্স কোড + টেলিগ্রাম চ্যানেল + কনফিগারেশন ব্যাকআপ + স্টোরেজ অবস্থান: [%s], অ্যাপ আনইনস্টল বা স্টোরেজ ক্লিয়ার করার পরে ব্যাকআপ মুছে যাবে + কনফিগারেশন পুনরুদ্ধার + কনফিগারেশন শেয়ার করুন + + প্রচার + প্রচার, বিস্তারিত জানার জন্য ক্লিক করুন (ডোনেশন মুছে ফেলা যেতে পারে) + + স্বয়ংক্রিয় আপডেট সাবস্ক্রিপশন + পটভূমিতে একটি নির্দিষ্ট সময় পর পর আপনার সাবস্ক্রিপশন স্বয়ংক্রিয়ভাবে আপডেট করুন। ডিভাইসের উপর নির্ভর করে, এই বৈশিষ্ট্যটি সবসময় কাজ নাও করতে পারে + অটো আপডেট ইন্টারভ্যাল (মিনিট, সর্বনিম্ন মান ১৫) + + লগ স্তর + মোড + আরো সাহায্যের জন্য ক্লিক করুন + ভাষা + ইউআই সেটিংস + ইউআই মোড সেটিংস + + লগক্যাট + কপি করুন + স্পষ্ট করুন + সার্ভিস পুনরায় চালু করুন + সব কনফিগারেশন মুছে ফেলুন + ডুপ্লিকেট কনফিগারেশন মুছে ফেলুন + অবৈধ কনফিগারেশন মুছে ফেলুন (প্রথমে পরীক্ষা করুন) + কাস্টম না করা কনফিগারেশনগুলি ক্লিপবোর্ডে রপ্তানি করুন + সাবস্ক্রিপশন গ্রুপ সেটিং + মন্তব্য + ঐচ্ছিক URL + আপডেট সক্রিয় করুন + স্বয়ংক্রিয় আপডেট সক্রিয় করুন + সাবস্ক্রিপশন আপডেট + সব কনফিগারেশন TCPing + সব কনফিগারেশন প্রকৃত বিলম্ব + জিও অ্যাসেট ফাইলগুলি + টেস্ট ফলাফল দ্বারা সাজানো + কনফিগারেশন ফাইল ফিল্টার করুন + সব সাবস্ক্রিপশন গ্রুপ + %d ডুপ্লিকেট কনফিগারেশন মুছে ফেলুন + সার্ভিস শুরু করুন + নিশ্চিত করুন + + রাউটিং সেটিংস + কমা (,) দ্বারা আলাদা করুন, মনে রাখবেন সেভ করতে + সেভ করুন + মুছে ফেলুন + স্ক্যান করুন এবং প্রতিস্থাপন করুন + স্ক্যান করুন এবং যোগ করুন + ডিফল্ট রাউটিং নিয়ম সেট করুন + + সংযোগ পরীক্ষা করুন + পরীক্ষা চলছে… + সফল: HTTP সংযোগ নিয়েছে %dms + ইন্টারনেট সংযোগ সনাক্ত করতে ব্যর্থ: %s + ইন্টারনেট উপলব্ধ নয় + ত্রুটি কোড: #%d + সংযুক্ত, সংযোগ পরীক্ষা করতে ট্যাপ করুন + সংযুক্ত নয় + + সাবস্ক্রিপশন সফলভাবে আমদানি করা হয়েছে + সাবস্ক্রিপশন আমদানি ব্যর্থ + ফ্র্যাগমেন্ট সেটিংস + ফ্র্যাগমেন্ট প্যাকেটস + ফ্র্যাগমেন্ট দৈর্ঘ্য (ন্যূনতম-সর্বাধিক) + ফ্র্যাগমেন্ট ইন্টারভ্যাল (ন্যূনতম-সর্বাধিক) + ফ্র্যাগমেন্ট সক্রিয় করুন + + QR কোড + ক্লিপবোর্ডে রপ্তানি করুন + পূর্ণ কনফিগারেশন ক্লিপবোর্ডে রপ্তানি করুন + + + + QR কোড + ক্লিপবোর্ডে রপ্তানি করুন + + + + প্রক্সি URL বা IP + ডাইরেক্ট URL বা IP + ব্লকড URL বা IP + + + + গ্লোবাল প্রোক্সি + LAN ঠিকানা বাইপাস করে তারপর প্রোক্সি + মেইনল্যান্ড ঠিকানা বাইপাস করে তারপর প্রোক্সি + LAN এবং মেইনল্যান্ড ঠিকানা বাইপাস করে তারপর প্রোক্সি + গ্লোবাল ডাইরেক্ট + + + + VPN + শুধুমাত্র প্রোক্সি + + + + সিস্টেম অনুসরণ করুন + লাইট + ডার্ক + + \ No newline at end of file diff --git a/v2rayng/V2rayNG/app/src/main/res/values/arrays.xml b/v2rayng/V2rayNG/app/src/main/res/values/arrays.xml index 2adc0c35e3..684c44a5a6 100644 --- a/v2rayng/V2rayNG/app/src/main/res/values/arrays.xml +++ b/v2rayng/V2rayNG/app/src/main/res/values/arrays.xml @@ -178,6 +178,7 @@ Русский فارسی عربي + বাংলা @@ -190,6 +191,7 @@ ru fa ar + bn diff --git a/v2rayu/V2rayU/Info.plist b/v2rayu/V2rayU/Info.plist index ea56de5ddb..7482fd9eba 100644 --- a/v2rayu/V2rayU/Info.plist +++ b/v2rayu/V2rayU/Info.plist @@ -5,7 +5,7 @@ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName - + $(PRODUCT_NAME) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleGetInfoString diff --git a/yass/.github/workflows/releases-android-binary.yml b/yass/.github/workflows/releases-android-binary.yml index 9e20854bbb..c3264cdb34 100644 --- a/yass/.github/workflows/releases-android-binary.yml +++ b/yass/.github/workflows/releases-android-binary.yml @@ -131,10 +131,10 @@ jobs: run: | cd tools go build - - name: Replace Android NDK (to latest) + - name: Replace Android NDK (pin to r26c) run: | - echo "ANDROID_NDK_ROOT=${ANDROID_NDK_LATEST_HOME}" >> $GITHUB_ENV - echo "ANDROID_NDK_HOME=${ANDROID_NDK_LATEST_HOME}" >> $GITHUB_ENV + echo "ANDROID_NDK_ROOT=/usr/local/lib/android/sdk/ndk/26.3.11579264" >> $GITHUB_ENV + echo "ANDROID_NDK_HOME=/usr/local/lib/android/sdk/ndk/26.3.11579264" >> $GITHUB_ENV - name: "Install dependency: tun2proxy" run: | ./scripts/setup-android-rust.sh diff --git a/yass/.github/workflows/releases-deb.yml b/yass/.github/workflows/releases-deb.yml index 470c236326..6a1a41c615 100644 --- a/yass/.github/workflows/releases-deb.yml +++ b/yass/.github/workflows/releases-deb.yml @@ -54,9 +54,11 @@ jobs: - distro: jammy arch: amd64 gui_variant: gtk4 + use_cet: true - distro: jammy arch: amd64 gui_variant: qt6 + use_cet: true - distro: sid arch: riscv64 gui_variant: none @@ -167,6 +169,10 @@ jobs: echo "DEB_BUILD_PROFILES=clang ${{ env.DEB_BUILD_PROFILES }}" >> $GITHUB_ENV echo "CC=${{ github.workspace }}/third_party/llvm-build/Release+Asserts/bin/clang" >> $GITHUB_ENV echo "CXX=${{ github.workspace }}/third_party/llvm-build/Release+Asserts/bin/clang++" >> $GITHUB_ENV + - name: Set cet build profile + if: ${{ matrix.use_cet == true }} + run: | + echo "DEB_BUILD_PROFILES=cet ${{ env.DEB_BUILD_PROFILES }}" >> $GITHUB_ENV - name: Fix schroot permissions (set store permissions for restore) run: | sudo mkdir -p /etc/schroot/chroot.d /var/lib/schroot/chroots diff --git a/yass/.github/workflows/releases-rpm.yml b/yass/.github/workflows/releases-rpm.yml index efdc84d7c3..96129b2d16 100644 --- a/yass/.github/workflows/releases-rpm.yml +++ b/yass/.github/workflows/releases-rpm.yml @@ -90,15 +90,19 @@ jobs: # qt6 sits on epel repo, disabling # - container: 'centos9' # gui_variant: qt6 + # use_cet: true # - container: 'fedora39' # gui_variant: gtk3 + # use_cet: true # - container: 'fedora39' # gui_variant: gtk4 + # use_cet: true # https://rpmfind.net/linux/rpm2html/search.php?query=glibc-devel&submit=Search+...&system=&arch= # glibc: 2.38 for fedora39 # glibc: 2.39 for fedora40 # - container: 'fedora39' # gui_variant: qt6 + # use_cet: true # for opensuse users, you can use centos7's rpm packages # - container: 'opensuse15' # gui_variant: gtk3 @@ -150,6 +154,11 @@ jobs: echo "CC=${{ github.workspace }}/third_party/llvm-build/Release+Asserts/bin/clang" >> $GITHUB_ENV echo "CXX=${{ github.workspace }}/third_party/llvm-build/Release+Asserts/bin/clang++" >> $GITHUB_ENV echo "USE_CLANG=1" >> $GITHUB_ENV + - name: Set cet option + if: ${{ matrix.use_cet == true }} + run: | + # use CET + echo "USE_CET=1" >> $GITHUB_ENV - name: Set c++ standard library environment run: | # use custom libc++ diff --git a/yass/CMakeLists.txt b/yass/CMakeLists.txt index e847499975..c88fb1402c 100644 --- a/yass/CMakeLists.txt +++ b/yass/CMakeLists.txt @@ -510,7 +510,8 @@ option(ENABLE_LLD "Enable build with LLD" OFF) option(USE_MOLD "Build with mold linker" OFF) option(USE_LLD "Build with lld linker" OFF) -cmake_dependent_option(USE_CET "Build with CET support" OFF LINUX OFF) +cmake_dependent_option(USE_CET "Build with CET support" OFF + "LINUX OR MSVC; OS_X64 OR OS_X86" OFF) cmake_dependent_option(USE_QT6 "Build with Qt6 (GUI)" OFF @@ -652,7 +653,11 @@ endif() include(config-ix) if (USE_CET) - if (COMPILER_CLANG) + if (MSVC) + # see https://reviews.llvm.org/D70606 + add_link_options(/cetcompat) + elseif (COMPILER_CLANG) + # see https://reviews.llvm.org/D59780 add_compile_options(-fcf-protection=full) elseif(COMPILER_GCC) add_compile_options(-fcf-protection) diff --git a/yass/debian/rules b/yass/debian/rules index a884a27566..a13d630a4f 100755 --- a/yass/debian/rules +++ b/yass/debian/rules @@ -68,6 +68,10 @@ override_dh_auto_configure: CMAKE_OPTIONS += -DUSE_SYSTEM_ZLIB=on override_dh_auto_configure: CMAKE_OPTIONS += -DCMAKE_SYSTEM_NAME=Linux -DCMAKE_SYSTEM_PROCESSOR=$(DEB_HOST_ARCH) override_dh_auto_configure: CMAKE_OPTIONS += -DUSE_OLD_SYSTEMD_SERVICE=on +ifneq ($(filter cet,$(DEB_BUILD_PROFILES)),) +override_dh_auto_configure: CMAKE_OPTIONS += -DUSE_CET=on +endif + override_dh_auto_configure: dh_auto_configure ${DEB_BUILD_SYSTEM_OPTIONS} -- -DCMAKE_BUILD_TYPE=Release -DBUILD_BENCHMARKS=on -DBUILD_TESTS=on $(CMAKE_OPTIONS) -DCLI=on -DSERVER=on -DUSE_BUILTIN_CA_BUNDLE_CRT=off -DUSE_TCMALLOC=on diff --git a/yass/scripts/build-rpm.sh b/yass/scripts/build-rpm.sh index 3ed1a281b4..a5c4c0feba 100755 --- a/yass/scripts/build-rpm.sh +++ b/yass/scripts/build-rpm.sh @@ -50,6 +50,7 @@ mv -fv yass.spec $HOME/rpmbuild/SPECS [ "a$USE_QT5" != "a" ] && rpm_options="--with=use_qt5 $rpm_options" [ "a$USE_GTK4" != "a" ] && rpm_options="--with=use_gtk4 $rpm_options" [ "a$USE_LIBCXX" != "a" ] && rpm_options="--with=use_libcxx $rpm_options" +[ "a$USE_CET" != "a" ] && rpm_options="--with=use_cet $rpm_options" [ "a$USE_CLANG" != "a" ] && rpm_options="--with=toolchain_clang $rpm_options" rpm_options="--with=tests_dns $rpm_options" diff --git a/yass/yass.spec.in b/yass/yass.spec.in index d28a16f84a..7198ea232a 100644 --- a/yass/yass.spec.in +++ b/yass/yass.spec.in @@ -54,6 +54,14 @@ %global enable_libcxx_opt off %endif +# cet is only enabled on fedora currently +%bcond_with use_cet +%if %{with use_cet} +%global enable_cet_opt on +%else +%global enable_cet_opt off +%endif + # disable some warnings on clang compiler in fedora %bcond_with toolchain_clang %if %{with toolchain_clang} @@ -212,6 +220,7 @@ cd build -DUSE_SYSTEM_MBEDTLS="%enable_system_mbedtls_opt" \ -DUSE_SYSTEM_JSON="%enable_system_json_opt" \ -DUSE_SYSTEM_CARES="%enable_system_cares_opt" -DUSE_LIBCXX="%enable_libcxx_opt" \ + -DUSE_CET="%enable_cet_opt" \ -DENABLE_LTO=on -DENABLE_LLD="%enable_lld_opt" -DUSE_BUILTIN_CA_BUNDLE_CRT=off .. ninja cd .. diff --git a/youtube-dl/test/test_youtube_signature.py b/youtube-dl/test/test_youtube_signature.py index 5b4aa3aa05..1c5f667f57 100644 --- a/youtube-dl/test/test_youtube_signature.py +++ b/youtube-dl/test/test_youtube_signature.py @@ -174,6 +174,10 @@ _NSIG_TESTS = [ 'https://www.youtube.com/s/player/5604538d/player_ias.vflset/en_US/base.js', '7X-he4jjvMx7BCX', 'sViSydX8IHtdWA', ), + ( + 'https://www.youtube.com/s/player/20dfca59/player_ias.vflset/en_US/base.js', + '-fLCxedkAk4LUTK2', 'O8kfRq1y1eyHGw', + ), ] diff --git a/youtube-dl/youtube_dl/extractor/youtube.py b/youtube-dl/youtube_dl/extractor/youtube.py index 84371ff063..509e374a4d 100644 --- a/youtube-dl/youtube_dl/extractor/youtube.py +++ b/youtube-dl/youtube_dl/extractor/youtube.py @@ -1659,18 +1659,38 @@ class YoutubeIE(YoutubeBaseInfoExtractor): def _extract_n_function_name(self, jscode): func_name, idx = self._search_regex( # new: (b=String.fromCharCode(110),c=a.get(b))&&c=nfunc[idx](c) - # or: (b="nn"[+a.D],c=a.get(b))&&(c=nfunc[idx](c)s + # or: (b="nn"[+a.D],c=a.get(b))&&(c=nfunc[idx](c) + # or: (PL(a),b=a.j.n||null)&&(b=nfunc[idx](b) # old: .get("n"))&&(b=nfunc[idx](b) # older: .get("n"))&&(b=nfunc(b) r'''(?x) - (?:\(\s*(?P[a-z])\s*=\s*(?: + (?:\((?:[\w$()\s]+,)*?\s*(?P[a-z])\s*=\s*(?: String\s*\.\s*fromCharCode\s*\(\s*110\s*\)| - "n+"\[\s*\+?s*[\w$.]+\s*] - )\s*,(?P[a-z])\s*=\s*[a-z]\s*)? - \.\s*get\s*\(\s*(?(b)(?P=b)|"n{1,2}")(?:\s*\)){2}\s*&&\s*\(\s*(?(c)(?P=c)|b)\s*=\s* + "n+"\[\s*\+?s*[\w$.]+\s*]| + (?P(?:[\w$]+\s*\.\s*)+n\b(?:(?!&&).)+\)) + )\s* + (?(b1) + &&\s*\(\s*(?P=b)| + (?: + ,(?P[a-z])\s*=\s*[a-z]\s*)? + \.\s*get\s*\(\s*(?(b)(?P=b)|"n{1,2}")(?:\s*\)){2}\s* + &&\s*\(\s*(?(c)(?P=c)|(?P=b)) + ) + )\s*=\s* (?P[a-zA-Z_$][\w$]*)(?:\s*\[(?P\d+)\])?\s*\(\s*[\w$]+\s*\) - ''', jscode, 'Initial JS player n function name', group=('nfunc', 'idx')) + ''', jscode, 'Initial JS player n function name', group=('nfunc', 'idx'), + default=(None, None)) + # thx bashonly: yt-dlp/yt-dlp/pull/10611 + if not func_name: + self.report_warning('Falling back to generic n function search') + return self._search_regex( + r'''(?xs) + (?:(?<=[^\w$])|^) # instead of \b, which ignores $ + (?P(?!\d)[a-zA-Z\d_$]+)\s*=\s*function\((?!\d)[a-zA-Z\d_$]+\) + \s*\{(?:(?!};).)+?["']enhanced_except_ + ''', jscode, 'Initial JS player n function name', group='name') if not idx: + self.report_warning('Falling back to generic n function search') return func_name return self._parse_json(self._search_regex( diff --git a/yt-dlp/CONTRIBUTORS b/yt-dlp/CONTRIBUTORS index 01c07aab9a..2180ecfe20 100644 --- a/yt-dlp/CONTRIBUTORS +++ b/yt-dlp/CONTRIBUTORS @@ -653,3 +653,5 @@ LeSuisse DunnesH iancmy mokrueger +luvyana +szantnerb diff --git a/yt-dlp/Changelog.md b/yt-dlp/Changelog.md index b2cad7dc46..73bf828a60 100644 --- a/yt-dlp/Changelog.md +++ b/yt-dlp/Changelog.md @@ -4,6 +4,32 @@ # To create a release, dispatch the https://github.com/yt-dlp/yt-dlp/actions/workflows/release.yml workflow on master --> +### 2024.08.01 + +#### Core changes +- **utils**: `unified_timestamp`: [Recognize Sunday](https://github.com/yt-dlp/yt-dlp/commit/6daf2c27c0464fba98337be30de0b66d520d0db1) ([#10589](https://github.com/yt-dlp/yt-dlp/issues/10589)) by [bashonly](https://github.com/bashonly) + +#### Extractor changes +- **abematv**: [Fix availability extraction](https://github.com/yt-dlp/yt-dlp/commit/ef36d517f9b05785d61abca7691d9ab7d63cc75c) ([#10569](https://github.com/yt-dlp/yt-dlp/issues/10569)) by [middlingphys](https://github.com/middlingphys) +- **cbc.ca**: player: [Fix extractor](https://github.com/yt-dlp/yt-dlp/commit/94a1c5e642e468cebeb51f74c6c220434cb47d96) ([#10302](https://github.com/yt-dlp/yt-dlp/issues/10302)) by [bashonly](https://github.com/bashonly), [trainman261](https://github.com/trainman261) +- **discoveryplus**: [Support olympics URLs](https://github.com/yt-dlp/yt-dlp/commit/0b7728618417e1aa382722a4d29b916b594d4459) ([#10566](https://github.com/yt-dlp/yt-dlp/issues/10566)) by [bashonly](https://github.com/bashonly) +- **kick**: clips: [Add extractor](https://github.com/yt-dlp/yt-dlp/commit/bb3936ae2b3ce96d0b53f9e17cad1082058f032b) ([#10572](https://github.com/yt-dlp/yt-dlp/issues/10572)) by [luvyana](https://github.com/luvyana) +- **learningonscreen**: [Add extractor](https://github.com/yt-dlp/yt-dlp/commit/fe15d3178e242803ae7a934b90137f13598eba2e) ([#10590](https://github.com/yt-dlp/yt-dlp/issues/10590)) by [bashonly](https://github.com/bashonly), [Grub4K](https://github.com/Grub4K) +- **mediaklikk**: [Fix extractor](https://github.com/yt-dlp/yt-dlp/commit/7e3e4779ad13e4511c9ba3869879e53f0267bd7a) ([#10605](https://github.com/yt-dlp/yt-dlp/issues/10605)) by [szantnerb](https://github.com/szantnerb) +- **mlbtv**: [Fix makeup game extraction](https://github.com/yt-dlp/yt-dlp/commit/4b69e1b53ea21e631cd5dd68ff531e2f1671ec17) ([#10607](https://github.com/yt-dlp/yt-dlp/issues/10607)) by [bashonly](https://github.com/bashonly) +- **olympics**: [Fix extractor](https://github.com/yt-dlp/yt-dlp/commit/2f1ddfe12a2c174bc777264c5c8ffe7ca0922d94) ([#10604](https://github.com/yt-dlp/yt-dlp/issues/10604)) by [bashonly](https://github.com/bashonly) +- **tva**: [Fix extractor](https://github.com/yt-dlp/yt-dlp/commit/28d485714fef88937c82635438afba5db81f9089) ([#10567](https://github.com/yt-dlp/yt-dlp/issues/10567)) by [bashonly](https://github.com/bashonly) +- **tver**: [Support olympic URLs](https://github.com/yt-dlp/yt-dlp/commit/5260696b1cba77161828941fdb38f09f14ac6c60) ([#10600](https://github.com/yt-dlp/yt-dlp/issues/10600)) by [vvto33](https://github.com/vvto33) +- **vimeo**: review: [Fix password-protected video extraction](https://github.com/yt-dlp/yt-dlp/commit/2b6df93a243bdfb9d6bb5c1e18020625cd02d465) ([#10598](https://github.com/yt-dlp/yt-dlp/issues/10598)) by [bashonly](https://github.com/bashonly) +- **youtube** + - [Change default player clients to `ios,tv`](https://github.com/yt-dlp/yt-dlp/commit/efb42763dec23ccf6a2e3bac3afbfefce8efd012) ([#10457](https://github.com/yt-dlp/yt-dlp/issues/10457)) by [seproDev](https://github.com/seproDev) + - [Fix `n` function name extraction for player `20dfca59`](https://github.com/yt-dlp/yt-dlp/commit/011b4a04db2a636c3ef0a0ad4e2d3ae482c9fd76) ([#10611](https://github.com/yt-dlp/yt-dlp/issues/10611)) by [bashonly](https://github.com/bashonly) + - [Fix age-verification workaround](https://github.com/yt-dlp/yt-dlp/commit/d19fcb934269465fd707e68a87f735ec6983e93d) ([#10610](https://github.com/yt-dlp/yt-dlp/issues/10610)) by [bashonly](https://github.com/bashonly), [Grub4K](https://github.com/Grub4K) + - [Player client maintenance](https://github.com/yt-dlp/yt-dlp/commit/0e539617a41913c7da1edd74fb6543c10ad727b3) ([#10573](https://github.com/yt-dlp/yt-dlp/issues/10573)) by [bashonly](https://github.com/bashonly) + +#### Misc. changes +- **cleanup**: Miscellaneous: [ffd7781](https://github.com/yt-dlp/yt-dlp/commit/ffd7781d6588926f820b44a34b9e6e3068fb9f97) by [bashonly](https://github.com/bashonly) + ### 2024.07.25 #### Extractor changes diff --git a/yt-dlp/README.md b/yt-dlp/README.md index a35efffc44..dd78012a85 100644 --- a/yt-dlp/README.md +++ b/yt-dlp/README.md @@ -1767,7 +1767,7 @@ The following extractors use this feature: #### youtube * `lang`: Prefer translated metadata (`title`, `description` etc) of this language code (case-sensitive). By default, the video primary language metadata is preferred, with a fallback to `en` translated. See [youtube.py](https://github.com/yt-dlp/yt-dlp/blob/c26f9b991a0681fd3ea548d535919cec1fbbd430/yt_dlp/extractor/youtube.py#L381-L390) for list of supported content language codes * `skip`: One or more of `hls`, `dash` or `translated_subs` to skip extraction of the m3u8 manifests, dash manifests and [auto-translated subtitles](https://github.com/yt-dlp/yt-dlp/issues/4090#issuecomment-1158102032) respectively -* `player_client`: Clients to extract video data from. The main clients are `web`, `ios` and `android`, with variants `_music` and `_creator` (e.g. `web_creator`); and `mediaconnect`, `mweb`, `android_producer`, `android_testsuite`, `android_vr`, `web_safari`, `web_embedded`, `tv` and `tv_embedded` with no variants. By default, `ios,web` is used, but `tv_embedded` and `_creator` variants are added as required for age-gated videos. Similarly, the music variants are added for `music.youtube.com` urls. Most `android` clients will be given lowest priority since their formats are broken. You can use `all` to use all the clients, and `default` for the default clients. +* `player_client`: Clients to extract video data from. The main clients are `web`, `ios` and `android`, with variants `_music` and `_creator` (e.g. `ios_creator`); and `mediaconnect`, `mweb`, `android_producer`, `android_testsuite`, `android_vr`, `web_safari`, `web_embedded`, `tv` and `tv_embedded` with no variants. By default, `ios,tv` is used, but `tv_embedded`, `web_creator` and `mediaconnect` are added as required for age-gated videos. Similarly, the music variants are added for `music.youtube.com` urls. Most `android` clients will be given lowest priority since their formats are broken. You can use `all` to use all the clients, and `default` for the default clients. * `player_skip`: Skip some network requests that are generally needed for robust extraction. One or more of `configs` (skip client configs), `webpage` (skip initial webpage), `js` (skip js player). While these options can help reduce the number of requests needed or avoid some rate-limiting, they could cause some issues. See [#860](https://github.com/yt-dlp/yt-dlp/pull/860) for more details * `player_params`: YouTube player parameters to use for player requests. Will overwrite any default ones set by yt-dlp. * `comment_sort`: `top` or `new` (default) - choose comment sorting mode (on YouTube's side) diff --git a/yt-dlp/supportedsites.md b/yt-dlp/supportedsites.md index c8b8fbb35a..e3bbe03ec7 100644 --- a/yt-dlp/supportedsites.md +++ b/yt-dlp/supportedsites.md @@ -655,10 +655,11 @@ - **Ketnet** - **khanacademy** - **khanacademy:unit** - - **Kick** + - **kick:clips** + - **kick:live** + - **kick:vod** - **Kicker** - **KickStarter** - - **KickVOD** - **kinja:embed** - **KinoPoisk** - **Kommunetv** @@ -690,6 +691,7 @@ - **Lcp** - **LcpPlay** - **Le**: 乐视网 + - **LearningOnScreen** - **Lecture2Go**: (**Currently broken**) - **Lecturio**: [*lecturio*](## "netrc machine") - **LecturioCourse**: [*lecturio*](## "netrc machine") @@ -1140,7 +1142,6 @@ - **QuantumTV**: [*quantumtv*](## "netrc machine") - **QuantumTVLive**: [*quantumtv*](## "netrc machine") - **QuantumTVRecordings**: [*quantumtv*](## "netrc machine") - - **Qub** - **R7**: (**Currently broken**) - **R7Article**: (**Currently broken**) - **Radiko** @@ -1517,9 +1518,9 @@ - **tv5unis** - **tv5unis:video** - **tv8.it** - - **TVA** - **TVANouvelles** - **TVANouvellesArticle** + - **tvaplus**: TVA+ - **TVC** - **TVCArticle** - **TVer** diff --git a/yt-dlp/test/test_youtube_signature.py b/yt-dlp/test/test_youtube_signature.py index ae167d16d4..d37df7a2ea 100644 --- a/yt-dlp/test/test_youtube_signature.py +++ b/yt-dlp/test/test_youtube_signature.py @@ -175,6 +175,10 @@ _NSIG_TESTS = [ 'https://www.youtube.com/s/player/3400486c/player_ias.vflset/en_US/base.js', 'lL46g3XifCKUZn1Xfw', 'z767lhet6V2Skl', ), + ( + 'https://www.youtube.com/s/player/20dfca59/player_ias.vflset/en_US/base.js', + '-fLCxedkAk4LUTK2', 'O8kfRq1y1eyHGw', + ), ] diff --git a/yt-dlp/yt_dlp/extractor/_extractors.py b/yt-dlp/yt_dlp/extractor/_extractors.py index f4bd761583..9b73fcd75e 100644 --- a/yt-dlp/yt_dlp/extractor/_extractors.py +++ b/yt-dlp/yt_dlp/extractor/_extractors.py @@ -939,6 +939,7 @@ from .khanacademy import ( KhanAcademyUnitIE, ) from .kick import ( + KickClipIE, KickIE, KickVODIE, ) diff --git a/yt-dlp/yt_dlp/extractor/kick.py b/yt-dlp/yt_dlp/extractor/kick.py index 889548f526..1c1b2a1772 100644 --- a/yt-dlp/yt_dlp/extractor/kick.py +++ b/yt-dlp/yt_dlp/extractor/kick.py @@ -1,9 +1,14 @@ +import functools + from .common import InfoExtractor from ..networking import HEADRequest from ..utils import ( UserNotLive, + determine_ext, float_or_none, + int_or_none, merge_dicts, + parse_iso8601, str_or_none, traverse_obj, unified_timestamp, @@ -25,104 +30,192 @@ class KickBaseIE(InfoExtractor): def _call_api(self, path, display_id, note='Downloading API JSON', headers={}, **kwargs): return self._download_json( - f'https://kick.com/api/v1/{path}', display_id, note=note, + f'https://kick.com/api/{path}', display_id, note=note, headers=merge_dicts(headers, self._API_HEADERS), impersonate=True, **kwargs) class KickIE(KickBaseIE): + IE_NAME = 'kick:live' _VALID_URL = r'https?://(?:www\.)?kick\.com/(?!(?:video|categories|search|auth)(?:[/?#]|$))(?P[\w-]+)' _TESTS = [{ - 'url': 'https://kick.com/yuppy', + 'url': 'https://kick.com/buddha', 'info_dict': { - 'id': '6cde1-kickrp-joe-flemmingskick-info-heremust-knowmust-see21', + 'id': '92722911-nopixel-40', 'ext': 'mp4', 'title': str, 'description': str, - 'channel': 'yuppy', - 'channel_id': '33538', - 'uploader': 'Yuppy', - 'uploader_id': '33793', - 'upload_date': str, - 'live_status': 'is_live', 'timestamp': int, - 'thumbnail': r're:^https?://.*\.jpg', + 'thumbnail': r're:https?://.+\.jpg', 'categories': list, + 'upload_date': str, + 'channel': 'buddha', + 'channel_id': '32807', + 'uploader': 'Buddha', + 'uploader_id': '33057', + 'live_status': 'is_live', + 'concurrent_view_count': int, + 'release_timestamp': int, + 'age_limit': 18, + 'release_date': str, }, - 'skip': 'livestream', + 'params': {'skip_download': 'livestream'}, + # 'skip': 'livestream', }, { - 'url': 'https://kick.com/kmack710', + 'url': 'https://kick.com/xqc', 'only_matching': True, }] + @classmethod + def suitable(cls, url): + return False if KickClipIE.suitable(url) else super().suitable(url) + def _real_extract(self, url): channel = self._match_id(url) - response = self._call_api(f'channels/{channel}', channel) + response = self._call_api(f'v2/channels/{channel}', channel) if not traverse_obj(response, 'livestream', expected_type=dict): raise UserNotLive(video_id=channel) return { - 'id': str(traverse_obj( - response, ('livestream', ('slug', 'id')), get_all=False, default=channel)), - 'formats': self._extract_m3u8_formats( - response['playback_url'], channel, 'mp4', live=True), - 'title': traverse_obj( - response, ('livestream', ('session_title', 'slug')), get_all=False, default=''), - 'description': traverse_obj(response, ('user', 'bio')), 'channel': channel, - 'channel_id': str_or_none(traverse_obj(response, 'id', ('livestream', 'channel_id'))), - 'uploader': traverse_obj(response, 'name', ('user', 'username')), - 'uploader_id': str_or_none(traverse_obj(response, 'user_id', ('user', 'id'))), 'is_live': True, - 'timestamp': unified_timestamp(traverse_obj(response, ('livestream', 'created_at'))), - 'thumbnail': traverse_obj( - response, ('livestream', 'thumbnail', 'url'), expected_type=url_or_none), - 'categories': traverse_obj(response, ('recent_categories', ..., 'name')), + 'formats': self._extract_m3u8_formats(response['playback_url'], channel, 'mp4', live=True), + **traverse_obj(response, { + 'id': ('livestream', 'slug', {str}), + 'title': ('livestream', 'session_title', {str}), + 'description': ('user', 'bio', {str}), + 'channel_id': (('id', ('livestream', 'channel_id')), {int}, {str_or_none}, any), + 'uploader': (('name', ('user', 'username')), {str}, any), + 'uploader_id': (('user_id', ('user', 'id')), {int}, {str_or_none}, any), + 'timestamp': ('livestream', 'created_at', {unified_timestamp}), + 'release_timestamp': ('livestream', 'start_time', {unified_timestamp}), + 'thumbnail': ('livestream', 'thumbnail', 'url', {url_or_none}), + 'categories': ('recent_categories', ..., 'name', {str}), + 'concurrent_view_count': ('livestream', 'viewer_count', {int_or_none}), + 'age_limit': ('livestream', 'is_mature', {bool}, {lambda x: 18 if x else 0}), + }), } class KickVODIE(KickBaseIE): + IE_NAME = 'kick:vod' _VALID_URL = r'https?://(?:www\.)?kick\.com/video/(?P[\da-f]{8}-(?:[\da-f]{4}-){3}[\da-f]{12})' _TESTS = [{ - 'url': 'https://kick.com/video/58bac65b-e641-4476-a7ba-3707a35e60e3', + 'url': 'https://kick.com/video/e74614f4-5270-4319-90ad-32179f19a45c', 'md5': '3870f94153e40e7121a6e46c068b70cb', 'info_dict': { - 'id': '58bac65b-e641-4476-a7ba-3707a35e60e3', + 'id': 'e74614f4-5270-4319-90ad-32179f19a45c', 'ext': 'mp4', - 'title': '🤠REBIRTH IS BACK!!!!🤠!stake CODE JAREDFPS 🤠', - 'description': 'md5:02b0c46f9b4197fb545ab09dddb85b1d', - 'channel': 'jaredfps', - 'channel_id': '26608', - 'uploader': 'JaredFPS', - 'uploader_id': '26799', - 'upload_date': '20240402', - 'timestamp': 1712097108, - 'duration': 33859.0, + 'title': r're:❎ MEGA DRAMA ❎ LIVE ❎ CLICK ❎ ULTIMATE SKILLS .+', + 'description': 'THE BEST AT ABSOLUTELY EVERYTHING. THE JUICER. LEADER OF THE JUICERS.', + 'channel': 'xqc', + 'channel_id': '668', + 'uploader': 'xQc', + 'uploader_id': '676', + 'upload_date': '20240724', + 'timestamp': 1721796562, + 'duration': 18566.0, 'thumbnail': r're:^https?://.*\.jpg', - 'categories': ['Call of Duty: Warzone'], + 'view_count': int, + 'categories': ['VALORANT'], + 'age_limit': 0, }, - 'params': { - 'skip_download': 'm3u8', - }, - 'expected_warnings': [r'impersonation'], + 'params': {'skip_download': 'm3u8'}, }] def _real_extract(self, url): video_id = self._match_id(url) - response = self._call_api(f'video/{video_id}', video_id) + response = self._call_api(f'v1/video/{video_id}', video_id) return { 'id': video_id, 'formats': self._extract_m3u8_formats(response['source'], video_id, 'mp4'), - 'title': traverse_obj( - response, ('livestream', ('session_title', 'slug')), get_all=False, default=''), - 'description': traverse_obj(response, ('livestream', 'channel', 'user', 'bio')), - 'channel': traverse_obj(response, ('livestream', 'channel', 'slug')), - 'channel_id': str_or_none(traverse_obj(response, ('livestream', 'channel', 'id'))), - 'uploader': traverse_obj(response, ('livestream', 'channel', 'user', 'username')), - 'uploader_id': str_or_none(traverse_obj(response, ('livestream', 'channel', 'user_id'))), - 'timestamp': unified_timestamp(response.get('created_at')), - 'duration': float_or_none(traverse_obj(response, ('livestream', 'duration')), scale=1000), - 'thumbnail': traverse_obj( - response, ('livestream', 'thumbnail'), expected_type=url_or_none), - 'categories': traverse_obj(response, ('livestream', 'categories', ..., 'name')), + **traverse_obj(response, { + 'title': ('livestream', ('session_title', 'slug'), {str}, any), + 'description': ('livestream', 'channel', 'user', 'bio', {str}), + 'channel': ('livestream', 'channel', 'slug', {str}), + 'channel_id': ('livestream', 'channel', 'id', {int}, {str_or_none}), + 'uploader': ('livestream', 'channel', 'user', 'username', {str}), + 'uploader_id': ('livestream', 'channel', 'user_id', {int}, {str_or_none}), + 'timestamp': ('created_at', {parse_iso8601}), + 'duration': ('livestream', 'duration', {functools.partial(float_or_none, scale=1000)}), + 'thumbnail': ('livestream', 'thumbnail', {url_or_none}), + 'categories': ('livestream', 'categories', ..., 'name', {str}), + 'view_count': ('views', {int_or_none}), + 'age_limit': ('livestream', 'is_mature', {bool}, {lambda x: 18 if x else 0}), + }), + } + + +class KickClipIE(KickBaseIE): + IE_NAME = 'kick:clips' + _VALID_URL = r'https?://(?:www\.)?kick\.com/[\w-]+/?\?(?:[^#]+&)?clip=(?Pclip_[\w-]+)' + _TESTS = [{ + 'url': 'https://kick.com/mxddy?clip=clip_01GYXVB5Y8PWAPWCWMSBCFB05X', + 'info_dict': { + 'id': 'clip_01GYXVB5Y8PWAPWCWMSBCFB05X', + 'ext': 'mp4', + 'title': 'Maddy detains Abd D:', + 'channel': 'mxddy', + 'channel_id': '133789', + 'uploader': 'AbdCreates', + 'uploader_id': '3309077', + 'thumbnail': r're:^https?://.*\.jpeg', + 'duration': 35, + 'timestamp': 1682481453, + 'upload_date': '20230426', + 'view_count': int, + 'like_count': int, + 'categories': ['VALORANT'], + 'age_limit': 18, + }, + 'params': {'skip_download': 'm3u8'}, + }, { + 'url': 'https://kick.com/destiny?clip=clip_01H9SKET879NE7N9RJRRDS98J3', + 'info_dict': { + 'id': 'clip_01H9SKET879NE7N9RJRRDS98J3', + 'title': 'W jews', + 'ext': 'mp4', + 'channel': 'destiny', + 'channel_id': '1772249', + 'uploader': 'punished_furry', + 'uploader_id': '2027722', + 'duration': 49.0, + 'upload_date': '20230908', + 'timestamp': 1694150180, + 'thumbnail': 'https://clips.kick.com/clips/j3/clip_01H9SKET879NE7N9RJRRDS98J3/thumbnail.png', + 'view_count': int, + 'like_count': int, + 'categories': ['Just Chatting'], + 'age_limit': 0, + }, + 'params': {'skip_download': 'm3u8'}, + }] + + def _real_extract(self, url): + clip_id = self._match_id(url) + clip = self._call_api(f'v2/clips/{clip_id}/play', clip_id)['clip'] + clip_url = clip['clip_url'] + + if determine_ext(clip_url) == 'm3u8': + formats = self._extract_m3u8_formats(clip_url, clip_id, 'mp4') + else: + formats = [{'url': clip_url}] + + return { + 'id': clip_id, + 'formats': formats, + **traverse_obj(clip, { + 'title': ('title', {str}), + 'channel': ('channel', 'slug', {str}), + 'channel_id': ('channel', 'id', {int}, {str_or_none}), + 'uploader': ('creator', 'username', {str}), + 'uploader_id': ('creator', 'id', {int}, {str_or_none}), + 'thumbnail': ('thumbnail_url', {url_or_none}), + 'duration': ('duration', {float_or_none}), + 'categories': ('category', 'name', {str}, all), + 'timestamp': ('created_at', {parse_iso8601}), + 'view_count': ('views', {int_or_none}), + 'like_count': ('likes', {int_or_none}), + 'age_limit': ('is_mature', {bool}, {lambda x: 18 if x else 0}), + }), } diff --git a/yt-dlp/yt_dlp/extractor/youku.py b/yt-dlp/yt_dlp/extractor/youku.py index fa6b0539bb..3bdfa6c933 100644 --- a/yt-dlp/yt_dlp/extractor/youku.py +++ b/yt-dlp/yt_dlp/extractor/youku.py @@ -136,7 +136,7 @@ class YoukuIE(InfoExtractor): # request basic data basic_data_params = { 'vid': video_id, - 'ccode': '0524', + 'ccode': '0564', 'client_ip': '192.168.1.1', 'utid': cna, 'client_ts': time.time() / 1000, diff --git a/yt-dlp/yt_dlp/extractor/youtube.py b/yt-dlp/yt_dlp/extractor/youtube.py index 1a3e286c68..88e1a28ae3 100644 --- a/yt-dlp/yt_dlp/extractor/youtube.py +++ b/yt-dlp/yt_dlp/extractor/youtube.py @@ -3173,19 +3173,35 @@ class YoutubeIE(YoutubeBaseInfoExtractor): self.write_debug(f'Decrypted nsig {s} => {ret}') return ret - def _extract_n_function_name(self, jscode): + def _extract_n_function_name(self, jscode, player_url=None): + # Examples (with placeholders nfunc, narray, idx): + # * .get("n"))&&(b=nfunc(b) + # * .get("n"))&&(b=narray[idx](b) + # * b=String.fromCharCode(110),c=a.get(b))&&c=narray[idx](c) + # * a.D&&(b="nn"[+a.D],c=a.get(b))&&(c=narray[idx](c),a.set(b,c),narray.length||nfunc("") + # * a.D&&(PL(a),b=a.j.n||null)&&(b=narray[0](b),a.set("n",b),narray.length||nfunc("") funcname, idx = self._search_regex( r'''(?x) (?: \.get\("n"\)\)&&\(b=| (?: b=String\.fromCharCode\(110\)| - ([a-zA-Z0-9$.]+)&&\(b="nn"\[\+\1\] - ),c=a\.get\(b\)\)&&\(c= - ) - (?P[a-zA-Z0-9$]+)(?:\[(?P\d+)\])?\([a-zA-Z0-9]\)''', - jscode, 'Initial JS player n function name', group=('nfunc', 'idx')) - if not idx: + (?P[a-zA-Z0-9_$.]+)&&\(b="nn"\[\+(?P=str_idx)\] + ),c=a\.get\(b\)\)&&\(c=| + \b(?P[a-zA-Z0-9_$]+)= + )(?P[a-zA-Z0-9_$]+)(?:\[(?P\d+)\])?\([a-zA-Z]\) + (?(var),[a-zA-Z0-9_$]+\.set\("n"\,(?P=var)\),(?P=nfunc)\.length)''', + jscode, 'n function name', group=('nfunc', 'idx'), default=(None, None)) + if not funcname: + self.report_warning(join_nonempty( + 'Falling back to generic n function search', + player_url and f' player = {player_url}', delim='\n')) + return self._search_regex( + r'''(?xs) + ;\s*(?P[a-zA-Z0-9_$]+)\s*=\s*function\([a-zA-Z0-9_$]+\) + \s*\{(?:(?!};).)+?["']enhanced_except_''', + jscode, 'Initial JS player n function name', group='name') + elif not idx: return funcname return json.loads(js_to_json(self._search_regex( @@ -3201,7 +3217,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor): if func_code: return jsi, player_id, func_code - func_name = self._extract_n_function_name(jscode) + func_name = self._extract_n_function_name(jscode, player_url=player_url) func_code = jsi.extract_function_code(func_name) @@ -3721,7 +3737,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor): def _get_requested_clients(self, url, smuggled_data): requested_clients = [] broken_clients = [] - default = ['ios', 'web'] + default = ['ios', 'tv'] allowed_clients = sorted( (client for client in INNERTUBE_CLIENTS if client[:1] != '_'), key=lambda client: INNERTUBE_CLIENTS[client]['priority'], reverse=True) @@ -3840,14 +3856,28 @@ class YoutubeIE(YoutubeBaseInfoExtractor): f[STREAMING_DATA_CLIENT_NAME] = name prs.append(pr) - # creator clients can bypass AGE_VERIFICATION_REQUIRED if logged in - if variant == 'tv_embedded' and self._is_unplayable(pr) and self.is_authenticated: - append_client(f'{base_client}_creator') - elif variant != 'tv_embedded' and self._is_agegated(pr): - if self.is_authenticated: - append_client(f'{base_client}_creator') + # tv_embedded can work around age-gate and age-verification IF the video is embeddable + if self._is_agegated(pr) and variant != 'tv_embedded': append_client(f'tv_embedded.{base_client}') + # Unauthenticated users will only get tv_embedded client formats if age-gated + if self._is_agegated(pr) and not self.is_authenticated: + self.to_screen( + f'{video_id}: This video is age-restricted; some formats may be missing ' + f'without authentication. {self._login_hint()}', only_once=True) + + # EU countries require age-verification for accounts to access age-restricted videos + # If account is not age-verified, _is_agegated() will be truthy for non-embedded clients + # If embedding is disabled for the video, _is_unplayable() will be truthy for tv_embedded + embedding_is_disabled = variant == 'tv_embedded' and self._is_unplayable(pr) + if self.is_authenticated and (self._is_agegated(pr) or embedding_is_disabled): + self.to_screen( + f'{video_id}: This video is age-restricted and YouTube is requiring ' + 'account age-verification; some formats may be missing', only_once=True) + # web_creator and mediaconnect can work around the age-verification requirement + # _producer, _testsuite, & _vr variants can also work around age-verification + append_client('web_creator', 'mediaconnect') + if skipped_clients: self.report_warning( f'Skipping player responses from {"/".join(skipped_clients)} clients ' diff --git a/yt-dlp/yt_dlp/version.py b/yt-dlp/yt_dlp/version.py index e641bf5ae6..81d1c2c963 100644 --- a/yt-dlp/yt_dlp/version.py +++ b/yt-dlp/yt_dlp/version.py @@ -1,8 +1,8 @@ # Autogenerated by devscripts/update-version.py -__version__ = '2024.07.25' +__version__ = '2024.08.01' -RELEASE_GIT_HEAD = 'f0993391e6052ec8f7aacc286609564f226943b9' +RELEASE_GIT_HEAD = 'ffd7781d6588926f820b44a34b9e6e3068fb9f97' VARIANT = None @@ -12,4 +12,4 @@ CHANNEL = 'stable' ORIGIN = 'yt-dlp/yt-dlp' -_pkg_version = '2024.07.25' +_pkg_version = '2024.08.01'