Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 17 additions & 7 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ base64 = { version = "0.22.1", default-features = false, features = ["alloc"] }
lexopt = "0.3.0"
ratatui-image = { version = "1.0.5", optional = true , default-features = false }
image = { version = "0.25.1", optional = true, features = ["png"], default-features = false }
url = "2.5.4"

[lib]
name = "nyaa"
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ request_proxy = "localhost:8118" # request proxy for sending requests through (u
timeout = 30 # request timeout for sources and clients (measured in seconds)
scroll_padding = 6 # scroll padding for results table
save_config_on_change = true # save config when changing sources/themes
yank_full_magnet = true # if false, only keep the torrent hash in the magnet link


[source.nyaa]
Expand Down
13 changes: 11 additions & 2 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ use crate::{
},
sync::{EventSync, ReloadType, SearchQuery},
theme::{self, Theme},
util::conv::key_to_string,
util::{conv::key_to_string, strings::minimal_magnet_link},
widget::{
batch::BatchWidget,
category::CategoryPopup,
Expand Down Expand Up @@ -653,7 +653,16 @@ impl App {
Some(item) => {
let link = match c {
't' => item.torrent_link,
'm' => item.magnet_link,
'm' => {
if ctx.config.yank_full_magnet {
match minimal_magnet_link(&item.magnet_link) {
Ok(magnet) => magnet,
Err(e) => return ctx.notify_error(e),
}
} else {
item.magnet_link
}
}
'p' => item.post_link,
'i' => match item.extra.get("imdb").cloned() {
Some(imdb) => imdb,
Expand Down
70 changes: 70 additions & 0 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,4 +221,74 @@ impl Client {
Self::Transmission => TransmissionClient::load_config(cfg),
};
}

pub fn get_link(
use_magnet: bool,
yank_full_magnet: Option<bool>,
torrent: String,
magnet: String,
) -> String {
if use_magnet {
if let Some(full) = yank_full_magnet {
if full {
magnet
} else {
match crate::util::strings::minimal_magnet_link(&magnet) {
Ok(minimal) => minimal,
Err(_) => magnet,
}
}
} else {
magnet
}
} else {
torrent
}
}
}

#[cfg(test)]
mod tests {
use crate::util::strings::minimal_magnet_link;

use super::*;

#[test]
fn test_get_link() {
let torrent_link = String::from("torrent");
let magnet_link = String::from("magnet:?xt=urn:btih:691526c892951e9b41b7946524513f945e5c7c45&dn=Example.File.Name&tr=http://example.com/tracker/announce");

// use magnet_link
assert_eq!(
Client::get_link(true, None, torrent_link.clone(), magnet_link.clone()),
magnet_link
);
assert_eq!(
Client::get_link(true, Some(true), torrent_link.clone(), magnet_link.clone()),
magnet_link
);
assert_eq!(
Client::get_link(true, Some(false), torrent_link.clone(), magnet_link.clone()),
minimal_magnet_link(&magnet_link).unwrap()
);

// do not use magnet link
assert_eq!(
Client::get_link(false, None, torrent_link.clone(), magnet_link.clone()),
torrent_link
);
assert_eq!(
Client::get_link(false, Some(true), torrent_link.clone(), magnet_link.clone()),
torrent_link
);
assert_eq!(
Client::get_link(
false,
Some(false),
torrent_link.clone(),
magnet_link.clone()
),
torrent_link
);
}
}
15 changes: 13 additions & 2 deletions src/client/cmd.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use serde::{Deserialize, Serialize};

use crate::{source::Item, util::cmd::CommandBuilder};
use crate::{
source::Item,
util::{cmd::CommandBuilder, strings::minimal_magnet_link},
};

use super::{
multidownload, BatchDownloadResult, ClientConfig, DownloadClient, SingleDownloadResult,
Expand All @@ -11,6 +14,7 @@ use super::{
pub struct CmdConfig {
cmd: String,
shell_cmd: String,
yank_full_magnet: Option<bool>,
}

pub struct CmdClient;
Expand All @@ -24,6 +28,7 @@ impl Default for CmdConfig {
cmd: "curl \"{torrent}\" > ~/{file}".to_owned(),

shell_cmd: CommandBuilder::default_shell(),
yank_full_magnet: None,
}
}
}
Expand All @@ -36,8 +41,14 @@ impl DownloadClient for CmdClient {
return SingleDownloadResult::error("Failed to get cmd config");
}
};

let magnet = if cmd.yank_full_magnet.unwrap_or(true) {
item.magnet_link.clone()
} else {
minimal_magnet_link(&item.magnet_link).unwrap_or_else(|_| item.magnet_link.clone())
};
let res = CommandBuilder::new(cmd.cmd)
.sub("{magnet}", &item.magnet_link)
.sub("{magnet}", &magnet)
.sub("{torrent}", &item.torrent_link)
.sub("{title}", &item.title)
.sub("{file}", &item.file_name)
Expand Down
11 changes: 7 additions & 4 deletions src/client/default_app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use super::{
#[serde(default)]
pub struct DefaultAppConfig {
use_magnet: bool,
pub yank_full_magnet: Option<bool>,
}

pub struct DefaultAppClient;
Expand All @@ -22,10 +23,12 @@ impl DownloadClient for DefaultAppClient {
return SingleDownloadResult::error("Failed to get default app config");
}
};
let link = match conf.use_magnet {
true => item.magnet_link.to_owned(),
false => item.torrent_link.to_owned(),
};
let link = super::Client::get_link(
conf.use_magnet,
conf.yank_full_magnet,
item.torrent_link.clone(),
item.magnet_link.clone(),
);
match open::that_detached(link).map_err(|e| e.to_string()) {
Ok(()) => {
SingleDownloadResult::success("Successfully opened link in default app", item.id)
Expand Down
12 changes: 8 additions & 4 deletions src/client/rqbit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub struct RqbitConfig {
pub use_magnet: Option<bool>,
pub overwrite: Option<bool>,
pub output_folder: Option<String>,
pub yank_full_magnet: Option<bool>,
}

pub struct RqbitClient;
Expand All @@ -35,6 +36,7 @@ impl Default for RqbitConfig {
use_magnet: None,
overwrite: None,
output_folder: None,
yank_full_magnet: None,
}
}
}
Expand Down Expand Up @@ -76,10 +78,12 @@ impl DownloadClient for RqbitClient {
return SingleDownloadResult::error("Failed to get rqbit config");
}
};
let link = match conf.use_magnet.unwrap_or(true) {
true => item.magnet_link.to_owned(),
false => item.torrent_link.to_owned(),
};
let link = super::Client::get_link(
conf.use_magnet.unwrap_or(true),
conf.yank_full_magnet,
item.torrent_link.clone(),
item.magnet_link.clone(),
);
let res = match add_torrent(&conf, link, &client).await {
Ok(r) => r,
Err(e) => {
Expand Down
12 changes: 8 additions & 4 deletions src/client/transmission.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ pub struct TransmissionConfig {
pub peer_limit: Option<i64>,
pub download_dir: Option<String>,
pub bandwidth_priority: Option<Priority>,
pub yank_full_magnet: Option<bool>,
}

pub struct TransmissionClient;
Expand All @@ -42,6 +43,7 @@ impl Default for TransmissionConfig {
peer_limit: None,
download_dir: None,
bandwidth_priority: None,
yank_full_magnet: None,
}
}
}
Expand Down Expand Up @@ -113,10 +115,12 @@ impl DownloadClient for TransmissionClient {
}
}

let link = match conf.use_magnet {
None | Some(true) => item.magnet_link.to_owned(),
Some(false) => item.torrent_link.to_owned(),
};
let link = super::Client::get_link(
conf.use_magnet.unwrap_or(true),
conf.yank_full_magnet,
item.torrent_link.clone(),
item.magnet_link.clone(),
);
if let Err(e) = add_torrent(conf, link, client).await {
return SingleDownloadResult::error(e);
}
Expand Down
4 changes: 4 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ pub struct Config {
pub cursor_padding: usize,
pub save_config_on_change: bool,
pub hot_reload_config: bool,
/// Tell if we yank all available magnet info or just the minimal magnet info when it is `false`:
/// `magnet:?xt=urn:btih:691526c892951e9b41b7946524513f945e5c7c45&dn=Example.File.Name&tr=http://example.com/tracker/announce` become `magnet:?xt=urn:btih:691526c892951e9b41b7946524513f945e5c7c45` when `false`
pub yank_full_magnet: bool,

#[serde(rename = "notifications")]
pub notifications: Option<NotificationConfig>,
Expand All @@ -85,6 +88,7 @@ impl Default for Config {
cursor_padding: 4,
save_config_on_change: true,
hot_reload_config: true,
yank_full_magnet: true,

notifications: None,
clipboard: None,
Expand Down
34 changes: 33 additions & 1 deletion src/util/strings.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
use std::{collections::VecDeque, ops::RangeBounds};
use std::{
collections::{HashMap, VecDeque},
ops::RangeBounds,
};

use unicode_width::UnicodeWidthChar as _;
use url::Url;

pub fn pos_of_nth_char(s: &str, idx: usize) -> usize {
s.chars()
Expand Down Expand Up @@ -150,3 +154,31 @@ pub fn forward_word(input: &str, start: usize) -> usize {
.map(|n| n + nonws)
.unwrap_or(input.chars().count())
}

pub fn minimal_magnet_link(magnet_link: &String) -> Result<String, String> {
let url = Url::parse(magnet_link).map_err(|e| e.to_string())?;

// Extract the query parameters into a HashMap.
let query_pairs = url.query_pairs();
let params: HashMap<_, _> = query_pairs.into_owned().collect();

let xt = params
.get("xt")
.ok_or("Missing `xt` in magnet URL".to_string())?;
let mut magnet = "magnet:?xt=".to_string();
magnet.push_str(xt);
Ok(magnet)
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_minimal_magnet_link() {
assert_eq!(
minimal_magnet_link(&String::from("magnet:?xt=urn:btih:691526c892951e9b41b7946524513f945e5c7c45&dn=Example.File.Name&tr=http://example.com/tracker/announce")).unwrap(),
"magnet:?xt=urn:btih:691526c892951e9b41b7946524513f945e5c7c45"
);
}
}
Loading