This commit is contained in:
funnym0nk3y 2022-08-31 00:32:41 +02:00
commit 5b18f43500
7 changed files with 544 additions and 542 deletions

View File

@ -3,7 +3,24 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased] ## [0.3.5]
### Added
- `--pass-path` option to get the password from [pass](https://www.passwordstore.org/) (PR [#33] by [@Ma27])
## [0.3.4]
### Added
- Display a warning if two or more courses/folders have the same name ([#31])
### Fixed
- `--keyring` option on Linux now tries to unlock password before using it (previously, this error would occur: `PlatformFailure(Zbus(MethodError("org.freedesktop.Secret.Error.IsLocked", None, Msg { type: Error, sender: ":1.34", reply-serial: 6 })))`)
## [0.3.3] - 2022-03-21
### Addded
- `--all` flag to download all courses ([#30])
## [0.3.2] - 2022-01-21
### Fixed
- Downloading of videos (PR [#28] by [@funnym0nk3y])
## [0.3.1] - 2022-01-07 ## [0.3.1] - 2022-01-07
### Fixed ### Fixed
@ -145,6 +162,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [0.1.0] - 2020-04-21 ## [0.1.0] - 2020-04-21
(undocumented) (undocumented)
[#33]: https://github.com/FliegendeWurst/KIT-ILIAS-downloader/pull/33
[#31]: https://github.com/FliegendeWurst/KIT-ILIAS-downloader/issues/31
[#30]: https://github.com/FliegendeWurst/KIT-ILIAS-downloader/issues/30
[#28]: https://github.com/FliegendeWurst/KIT-ILIAS-downloader/pull/28
[#27]: https://github.com/FliegendeWurst/KIT-ILIAS-downloader/issues/27 [#27]: https://github.com/FliegendeWurst/KIT-ILIAS-downloader/issues/27
[#19]: https://github.com/FliegendeWurst/KIT-ILIAS-downloader/pull/19 [#19]: https://github.com/FliegendeWurst/KIT-ILIAS-downloader/pull/19
[#15]: https://github.com/FliegendeWurst/KIT-ILIAS-downloader/issues/15 [#15]: https://github.com/FliegendeWurst/KIT-ILIAS-downloader/issues/15
@ -164,7 +185,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
[@Craeckie]: https://github.com/Craeckie [@Craeckie]: https://github.com/Craeckie
[@funnym0nk3y]: https://github.com/funnym0nk3y [@funnym0nk3y]: https://github.com/funnym0nk3y
[@Ma27]: https://github.com/Ma27 [@Ma27]: https://github.com/Ma27
[Unreleased]: https://github.com/FliegendeWurst/KIT-ILIAS-downloader/compare/v0.3.1...HEAD [Unreleased]: https://github.com/FliegendeWurst/KIT-ILIAS-downloader/compare/v0.3.5...HEAD
[0.3.5]: https://github.com/FliegendeWurst/KIT-ILIAS-downloader/compare/v0.3.5...v0.3.4
[0.3.4]: https://github.com/FliegendeWurst/KIT-ILIAS-downloader/compare/v0.3.3...v0.3.4
[0.3.3]: https://github.com/FliegendeWurst/KIT-ILIAS-downloader/compare/v0.3.2...v0.3.3
[0.3.2]: https://github.com/FliegendeWurst/KIT-ILIAS-downloader/compare/v0.3.1...v0.3.2
[0.3.1]: https://github.com/FliegendeWurst/KIT-ILIAS-downloader/compare/v0.3.0...v0.3.1 [0.3.1]: https://github.com/FliegendeWurst/KIT-ILIAS-downloader/compare/v0.3.0...v0.3.1
[0.3.0]: https://github.com/FliegendeWurst/KIT-ILIAS-downloader/compare/v0.2.24...v0.3.0 [0.3.0]: https://github.com/FliegendeWurst/KIT-ILIAS-downloader/compare/v0.2.24...v0.3.0
[0.2.24]: https://github.com/FliegendeWurst/KIT-ILIAS-downloader/compare/v0.2.23...v0.2.24 [0.2.24]: https://github.com/FliegendeWurst/KIT-ILIAS-downloader/compare/v0.2.23...v0.2.24

945
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
[package] [package]
name = "KIT-ILIAS-downloader" name = "KIT-ILIAS-downloader"
version = "0.3.1" version = "0.3.5"
authors = ["FliegendeWurst <2012gdwu@posteo.de>"] authors = ["FliegendeWurst <2012gdwu@posteo.de>"]
license = "GPL-3.0-or-later" license = "GPL-3.0-or-later"
edition = "2018" edition = "2018"
@ -10,28 +10,28 @@ edition = "2018"
[dependencies] [dependencies]
reqwest = { version = "0.11.0", default-features = false, features = ["cookies", "gzip", "json", "rustls-tls", "stream", "socks"] } reqwest = { version = "0.11.0", default-features = false, features = ["cookies", "gzip", "json", "rustls-tls", "stream", "socks"] }
tokio = { version = "1.0.2", features = ["fs", "macros", "net", "rt-multi-thread", "process"] } tokio = { version = "1.0.2", features = ["fs", "macros", "net", "rt-multi-thread", "process"] }
tokio-util = { version = "0.6.1", features = ["io"] } tokio-util = { version = "0.7.0", features = ["io"] }
serde_json = "1.0.51" serde_json = "1.0.51"
scraper = "0.12.0" scraper = "0.13.0"
url = "2.1.1" url = "2.1.1"
futures = "0.3.8" futures = "0.3.8"
futures-util = "0.3.8" futures-util = "0.3.8"
futures-channel = "0.3.8" futures-channel = "0.3.8"
regex = "1.3.7" regex = "1.3.7"
structopt = "0.3.13" structopt = "0.3.13"
rpassword = "5.0.0" rpassword = "6.0.1"
rprompt = "1.0.5" rprompt = "1.0.5"
ignore = "0.4.14" ignore = "0.4.14"
anyhow = "1.0.28" anyhow = "1.0.28"
colored = "2.0.0" colored = "2.0.0"
keyring = "1.0.0" keyring = { git = "https://github.com/FliegendeWurst/keyring-rs" }
cfg-if = "1.0.0" cfg-if = "1.0.0"
indicatif = "0.16.0" indicatif = "0.16.0"
once_cell = "1.7.2" once_cell = "1.7.2"
atty = "0.2.14" atty = "0.2.14"
h2 = "0.3.3" h2 = "0.3.3"
cookie_store = "0.15.1" cookie_store = "0.16.1"
reqwest_cookie_store = "0.2.0" reqwest_cookie_store = "0.3.0"
bytes = "1.0.1" bytes = "1.0.1"
toml = "0.5.8" toml = "0.5.8"
tempfile = "3.2.0" tempfile = "3.2.0"

View File

@ -12,7 +12,7 @@ Download content from ILIAS. That includes:
**Windows/Linux users**: go to the [releases](../../releases) and download the executable for your operating system. **Windows/Linux users**: go to the [releases](../../releases) and download the executable for your operating system.
**macOS users**: [Install Rust](https://www.rust-lang.org/tools/install) and compile from source: **macOS users**: [Install Rust](https://www.rust-lang.org/tools/install) and compile from source:
``` ```
$ cargo install --all-features --git 'https://github.com/FliegendeWurst/KIT-ILIAS-downloader' --branch stable $ cargo install --all-features --git 'https://github.com/FliegendeWurst/KIT-ILIAS-downloader'
``` ```
## Usage ## Usage
@ -35,13 +35,13 @@ $ KIT-ILIAS-downloader -o ./ILIAS/WS2021-HM1 --sync-url 'https://ilias.studium.k
### Options ### Options
``` ```
$ KIT-ILIAS-downloader --help KIT-ILIAS-downloader 0.3.5
KIT-ILIAS-downloader 0.2.24
USAGE: USAGE:
KIT-ILIAS-downloader [FLAGS] [OPTIONS] --output <output> KIT-ILIAS-downloader [FLAGS] [OPTIONS] --output <output>
FLAGS: FLAGS:
--all Download all courses
--check-videos Re-check OpenCast lectures (slow) --check-videos Re-check OpenCast lectures (slow)
--combine-videos Combine videos if there is more than one stream (requires ffmpeg) --combine-videos Combine videos if there is more than one stream (requires ffmpeg)
--content-tree Use content tree (experimental) --content-tree Use content tree (experimental)
@ -59,6 +59,7 @@ FLAGS:
OPTIONS: OPTIONS:
-j, --jobs <jobs> Parallel download jobs [default: 1] -j, --jobs <jobs> Parallel download jobs [default: 1]
-o, --output <output> Output directory -o, --output <output> Output directory
--pass-path <pass-path> Path inside `pass(1)` to the password for your KIT account
-P, --password <password> KIT account password -P, --password <password> KIT account password
-p, --proxy <proxy> Proxy, e.g. socks5h://127.0.0.1:1080 -p, --proxy <proxy> Proxy, e.g. socks5h://127.0.0.1:1080
--rate <rate> Requests per minute [default: 8] --rate <rate> Requests per minute [default: 8]
@ -68,7 +69,7 @@ OPTIONS:
### .iliasignore ### .iliasignore
.gitignore syntax can be used in a `.iliasignore` file: (located in the output folder) .gitignore syntax can be used in a `.iliasignore` file: (located in the output directory)
```ignore ```ignore
# example 1: only download a single course # example 1: only download a single course
/*/ /*/
@ -80,18 +81,25 @@ OPTIONS:
### Credentials ### Credentials
You can use the `--user` and `--keyring` options to get/store the password using the system password store. You can use the `--user` and `--keyring` options to get/store the password using the system password store:
If you use Linux, you'll have to compile from source to be able to use this option.
``` ```
$ KIT-ILIAS-downloader -U uabcd --keyring [...] $ KIT-ILIAS-downloader -U uabcd --keyring [...]
``` ```
If you use [pass](https://www.passwordstore.org/), you can use the `--pass-path` option to specify which password store entry to use:
```
$ KIT-ILIAS-downloader -U uabcd --pass-path edu/kit/uskyk [...]
```
You can also save your username and password in a `.iliaslogin` file: (located in the output folder) You can also save your username and password in a `.iliaslogin` file: (located in the output folder)
``` ```
username username
password password
``` ```
When running the downloader multiple times in a short period of time, you may want to use the `--keep-session` flag.
If specified, the downloader will save and restore session cookies (`.iliassession` file in the output directory).
### Renaming course names (0.2.24+) ### Renaming course names (0.2.24+)
If you'd like to avoid unwieldy course names (e.g. "24030 Programmierparadigmen"), you can create a `course_names.toml` file in the output directory. It should contain the desired mapping of course names to folder names, e.g.: If you'd like to avoid unwieldy course names (e.g. "24030 Programmierparadigmen"), you can create a `course_names.toml` file in the output directory. It should contain the desired mapping of course names to folder names, e.g.:
``` ```

View File

@ -1,6 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
use std::io::{Error, ErrorKind};
use std::path::PathBuf; use std::path::PathBuf;
use std::process::Command;
use std::sync::atomic::{AtomicBool, AtomicUsize}; use std::sync::atomic::{AtomicBool, AtomicUsize};
use anyhow::anyhow; use anyhow::anyhow;
@ -72,6 +74,10 @@ pub struct Opt {
#[structopt(short = "P", long)] #[structopt(short = "P", long)]
pub password: Option<String>, pub password: Option<String>,
/// Path inside `pass(1)` to the password for your KIT account
#[structopt(long)]
pub pass_path: Option<String>,
/// ILIAS page to download /// ILIAS page to download
#[structopt(long)] #[structopt(long)]
pub sync_url: Option<String>, pub sync_url: Option<String>,
@ -83,6 +89,10 @@ pub struct Opt {
/// Attempt to re-use session cookies /// Attempt to re-use session cookies
#[structopt(long)] #[structopt(long)]
pub keep_session: bool, pub keep_session: bool,
/// Download all courses
#[structopt(long)]
pub all: bool,
} }
pub static LOG_LEVEL: AtomicUsize = AtomicUsize::new(0); pub static LOG_LEVEL: AtomicUsize = AtomicUsize::new(0);
@ -162,12 +172,41 @@ pub fn ask_user_pass(opt: &Opt) -> Result<(String, String)> {
}, },
Err(e) => { Err(e) => {
error!(e); error!(e);
pass = rpassword::read_password_from_tty(Some("Password: ")).context("password prompt")?; pass = rpassword::prompt_password("Password: ").context("password prompt")?;
should_store = true; should_store = true;
} }
} }
} else if let Some(pass_path) = &opt.pass_path {
let pw_out = Command::new("pass")
.arg("show")
.arg(pass_path)
.output()
.map_err(|x| {
if x.kind() == ErrorKind::NotFound {
Error::new(ErrorKind::NotFound, "pass not found in $PATH!")
} else { } else {
pass = rpassword::read_password_from_tty(Some("Password: ")).context("password prompt")?; x
}
})?;
if !pw_out.status.success() {
return Err(Error::new(
ErrorKind::Other,
format!(
"`pass` failed with non-zero exit code {}: {}",
pw_out.status.code()
.expect("Failed retrieving pass exit code!"),
String::from_utf8(pw_out.stderr)
.expect("Failed decoding stderr of pass!")
)
))?
} else {
pass = String::from_utf8(pw_out.stdout).map(|x| {
x.lines().next().map(|x| x.to_owned()).ok_or_else(|| anyhow!("empty pass(1) entry!"))
})?.expect("utf-8 decode of `pass(1)`-output failed");
should_store = false;
}
} else {
pass = rpassword::prompt_password("Password: ").context("password prompt")?;
should_store = true; should_store = true;
} }
if should_store && opt.keyring { if should_store && opt.keyring {

View File

@ -1,4 +1,4 @@
use std::{path::Path, sync::Arc}; use std::{path::Path, sync::Arc, collections::HashSet};
use anyhow::{Context, Result}; use anyhow::{Context, Result};
@ -20,11 +20,17 @@ pub async fn download(path: &Path, ilias: Arc<ILIAS>, url: &URL) -> Result<()> {
.context("failed to write folder page html")?; .context("failed to write folder page html")?;
} }
} }
let mut names = HashSet::new();
for item in content.0 { for item in content.0 {
let item = item?; let item = item?;
let path = path.join(file_escape( let item_name = file_escape(
ilias.course_names.get(item.name()).map(|x| &**x).unwrap_or(item.name()), ilias.course_names.get(item.name()).map(|x| &**x).unwrap_or(item.name()),
)); );
if names.contains(&item_name) {
warning!(format => "folder {} contains duplicated folder {:?}", path.display(), item_name);
}
names.insert(item_name.clone());
let path = path.join(item_name);
let ilias = Arc::clone(&ilias); let ilias = Arc::clone(&ilias);
spawn(process_gracefully(ilias, path, item)); spawn(process_gracefully(ilias, path, item));
} }

View File

@ -160,9 +160,14 @@ async fn real_main(mut opt: Opt) -> Result<()> {
PROGRESS_BAR.set_message("initializing.."); PROGRESS_BAR.set_message("initializing..");
} }
let sync_url = ilias.opt.sync_url.as_deref().unwrap_or(DEFAULT_SYNC_URL); let sync_url = if ilias.opt.all {
// change on ILIAS update
format!("{}ilias.php?cmdClass=ilmembershipoverviewgui&cmdNode=iy&baseClass=ilmembershipoverviewgui", ILIAS_URL)
} else {
ilias.opt.sync_url.as_deref().unwrap_or(DEFAULT_SYNC_URL).to_owned()
};
let obj = Object::from_url( let obj = Object::from_url(
URL::from_href(sync_url).context("invalid sync URL")?, URL::from_href(&sync_url).context("invalid sync URL")?,
String::new(), String::new(),
None, None,
) )