deterrencedispensed-dl

Download all Deterrence Dispensed gatalog stuff
git clone https://git.ea.contact/deterrencedispensed-dl
Log | Files | Refs | README

commit fe6a275a880d31078e995626a141028a1963a943
Author: egor-achkasov <eaachkasov@gmail.com>
Date:   Fri,  8 May 2026 20:51:19 +0000

Init commit

Diffstat:
A.github/workflows/build.yml | 111+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.gitignore | 1+
ACargo.toml | 13+++++++++++++
AREADME.md | 5+++++
Asrc/main.rs | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 203 insertions(+), 0 deletions(-)

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml @@ -0,0 +1,110 @@ +name: Build Windows Executables + +on: + push: + +jobs: + check: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + targets: x86_64-pc-windows-msvc + + - name: Cache cargo registry + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + restore-keys: ${{ runner.os }}-cargo- + + - name: Cargo check + run: cargo check --all-targets + + build: + needs: check + if: github.ref == 'refs/heads/master' + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + targets: x86_64-pc-windows-msvc + + - name: Cache cargo registry + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + restore-keys: ${{ runner.os }}-cargo- + + - name: Build CLI + run: cargo build --release + + - name: Upload CLI artifact + uses: actions/upload-artifact@v4 + with: + name: deterrencedispensed-dl + path: target/release/deterrencedispensed-dl.exe + + release: + needs: build + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Download CLI artifact + uses: actions/download-artifact@v4 + with: + name: deterrencedispensed-dl + path: artifacts/ + + - name: Generate changelog + id: changelog + run: | + PREV_TAG=$(git tag --sort=-version:refname | head -n 1) + if [ -z "$PREV_TAG" ]; then + CHANGELOG=$(git log --pretty=format:"- %s (%h)" | head -20) + else + CHANGELOG=$(git log "$PREV_TAG"..HEAD --pretty=format:"- %s (%h)") + fi + echo "CHANGELOG<<EOF" >> $GITHUB_OUTPUT + echo "$CHANGELOG" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + - name: Create release tag + id: tag + run: | + TAG="release-$(date +'%Y%m%d%H%M%S')" + git tag "$TAG" + git push origin "$TAG" + echo "TAG=$TAG" >> $GITHUB_OUTPUT + + - name: Create GitHub release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ steps.tag.outputs.TAG }} + name: Release ${{ steps.tag.outputs.TAG }} + body: | + ## Changes + ${{ steps.changelog.outputs.CHANGELOG }} + files: | + artifacts/deterrencedispensed-dl.exe +\ No newline at end of file diff --git a/.gitignore b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.toml b/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "deterrencedispensed-dl" +version = "0.1.0" +edition = "2024" + +[profile.release] +lto = true +strip = "symbols" + +[dependencies] +odysee-dl = { git = "https://github.com/egor-achkasov/odysee-dl" } +regex = "1.12.3" +ureq = "3.3.0" diff --git a/README.md b/README.md @@ -0,0 +1,5 @@ +# Download all Deterrence Dispensed stuff from their odysee channels + +This script uses [odysee-dl](https://github.com/egor-achkasov/odysee-dl) on every link in https://thegatalog.com/. +Run the [Windows binary](https://github.com/egor-achkasov/deterrencedispensed-dl/releases) in PowerShell or CMD or build it yourself with `cargo build`. +An interrupted download session can be continued if the binary is run in the same directory again. diff --git a/src/main.rs b/src/main.rs @@ -0,0 +1,73 @@ +use odysee_dl::config::Config; +use odysee_dl::event::Event; +use odysee_dl::run; + +use regex::Regex; + +fn main() { + // Get actual channel urls + let re = Regex::new(r#"https?://odysee\.com/@[^:]+:[0-9a-f]+[^\s"'<>]*"#).unwrap(); + let page = ureq::get("https://thegatalog.com/") + .call() + .unwrap() + .body_mut() + .read_to_string() + .unwrap(); + let urls = re + .find_iter(&page) + .map(|m| m.as_str().to_string()) + .collect::<Vec<String>>(); + println!("Found {} channel URLs", urls.len()); + + for url in urls { + let dirname = url + .split("/@") + .nth(1) + .unwrap() + .split(":") + .nth(0) + .unwrap(); + std::fs::create_dir_all(dirname).unwrap(); + let output_dir = std::path::PathBuf::from(dirname); + let config = Config { + url: url, + output_dir: output_dir, + resume: true, + }; + + let (tx, rx) = std::sync::mpsc::channel(); + let handle = std::thread::spawn(move || { + run(config, tx.clone()).unwrap(); + }); + for msg in rx.iter() { + render_event(&msg); + } + handle.join().unwrap(); + } +} + +fn render_event(event: &Event) { + use std::io::Write; + match event { + Event::GetChannelStarted(url) => { + print!("Fetching channel: {}...", url); + std::io::stdout().flush().ok(); + } + Event::GetChannelFailed(url, err) => eprintln!("\nFailed to fetch channel {}: {}", url, err), + Event::GetChannelFinished(_) => println!(" Done"), + Event::GetPostsStarted(url) => { + print!("Fetching posts from: {}...", url); + std::io::stdout().flush().ok(); + } + Event::GetPostsFailed(url, err) => eprintln!("\nFailed to fetch posts from {}: {}", url, err), + Event::GetPostsFinished(_) => println!(" Done"), + Event::DownloadPostStarted(name) => { + print!("Downloading: {}...", name); + std::io::stdout().flush().ok(); + } + Event::DownloadPostFailed(_, err) => println!(" Failed: {}", err), + Event::DownloadPostSkipped(_) => println!(" Skipped"), + Event::DownloadPostFinished(_) => println!(" Done"), + Event::Done => println!("Done."), + } +}