commit fe6a275a880d31078e995626a141028a1963a943
Author: egor-achkasov <eaachkasov@gmail.com>
Date: Fri, 8 May 2026 20:51:19 +0000
Init commit
Diffstat:
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."),
+ }
+}