mirror of
https://github.com/nadimkobeissi/mkbsd.git
synced 2024-12-23 11:15:32 +00:00
Added multi-threaded Rust implementation
Update README.md Delete MKBSD/target directory Update .gitignore
This commit is contained in:
parent
82e50c64f0
commit
77732af198
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,2 +1,3 @@
|
||||||
.DS_Store
|
.DS_Store
|
||||||
downloads
|
downloads
|
||||||
|
MKBSD/target
|
||||||
|
|
1349
MKBSD/Cargo.lock
generated
Normal file
1349
MKBSD/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
12
MKBSD/Cargo.toml
Normal file
12
MKBSD/Cargo.toml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
[package]
|
||||||
|
name = "MKBSD"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
reqwest = { version = "0.12.7", features = ["json", "blocking"] }
|
||||||
|
tokio = { version = "1.40", features = ["full"] }
|
||||||
|
url = "2.5.2"
|
||||||
|
serde = { version = "1.0.210", features = ["derive"] }
|
||||||
|
serde_json = "1.0.128"
|
||||||
|
futures = "0.3.30"
|
124
MKBSD/src/main.rs
Normal file
124
MKBSD/src/main.rs
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
// Licensed under the WTFPL License
|
||||||
|
|
||||||
|
use futures::future::join_all;
|
||||||
|
use reqwest::Client;
|
||||||
|
use serde_json::Value;
|
||||||
|
use std::fs::{self, File};
|
||||||
|
use std::io::Write;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::time::Duration;
|
||||||
|
use tokio::time::sleep;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
const URL: &str = "https://storage.googleapis.com/panels-api/data/20240916/media-1a-i-p~s";
|
||||||
|
const DOWNLOAD_DIR: &str = "downloads";
|
||||||
|
|
||||||
|
async fn download_image(
|
||||||
|
client: &Client,
|
||||||
|
image_url: &str,
|
||||||
|
file_path: &Path,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let response = client.get(image_url).send().await?;
|
||||||
|
|
||||||
|
if !response.status().is_success() {
|
||||||
|
return Err(format!("Failed to download image: {}", response.status()).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let content = response.bytes().await?;
|
||||||
|
let mut file = File::create(file_path)?;
|
||||||
|
file.write_all(&content)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn main_task() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let client = Client::new();
|
||||||
|
|
||||||
|
// Fetch the JSON data
|
||||||
|
let response = client.get(URL).send().await?;
|
||||||
|
if !response.status().is_success() {
|
||||||
|
return Err(format!("⛔ Failed to fetch JSON file: {}", response.status()).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let json_data: Value = response.json().await?;
|
||||||
|
let data = json_data
|
||||||
|
.get("data")
|
||||||
|
.ok_or("⛔ JSON does not have a 'data' property at its root.")?;
|
||||||
|
|
||||||
|
// Create download directory if it doesn't exist
|
||||||
|
let download_dir = Path::new(DOWNLOAD_DIR);
|
||||||
|
if !download_dir.exists() {
|
||||||
|
fs::create_dir_all(download_dir)?;
|
||||||
|
println!("📁 Created directory: {:?}", download_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut file_index = 1;
|
||||||
|
let mut download_tasks = vec![];
|
||||||
|
|
||||||
|
for (_key, subproperty) in data.as_object().unwrap().iter() {
|
||||||
|
if let Some(subproperty) = subproperty.as_object() {
|
||||||
|
if let Some(image_url) = subproperty.get("dhd").and_then(|v| v.as_str()) {
|
||||||
|
println!("🔍 Found image URL!");
|
||||||
|
|
||||||
|
// Parse URL to get the file extension
|
||||||
|
let parsed_url = Url::parse(image_url)?;
|
||||||
|
let ext = Path::new(parsed_url.path())
|
||||||
|
.extension()
|
||||||
|
.and_then(|e| e.to_str())
|
||||||
|
.unwrap_or("jpg");
|
||||||
|
|
||||||
|
let filename = format!("{}.{ext}", file_index);
|
||||||
|
let file_path = download_dir.join(filename);
|
||||||
|
|
||||||
|
let client_clone = client.clone();
|
||||||
|
let image_url_clone = image_url.to_string();
|
||||||
|
let file_path_clone = file_path.clone();
|
||||||
|
|
||||||
|
// Spawn a new task for each download
|
||||||
|
let download_task = tokio::spawn(async move {
|
||||||
|
if let Err(e) =
|
||||||
|
download_image(&client_clone, &image_url_clone, &file_path_clone).await
|
||||||
|
{
|
||||||
|
eprintln!("Error downloading {}: {}", image_url_clone, e);
|
||||||
|
} else {
|
||||||
|
println!("🖼️ Saved image to {:?}", file_path_clone);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
download_tasks.push(download_task);
|
||||||
|
file_index += 1;
|
||||||
|
sleep(Duration::from_millis(250)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for all download tasks to complete
|
||||||
|
join_all(download_tasks).await;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ascii_art() {
|
||||||
|
println!(
|
||||||
|
r#"
|
||||||
|
/$$ /$$ /$$ /$$ /$$$$$$$ /$$$$$$ /$$$$$$$
|
||||||
|
| $$$ /$$$| $$ /$$/| $$__ $$ /$$__ $$| $$__ $$
|
||||||
|
| $$$$ /$$$$| $$ /$$/ | $$ \ $$| $$ \__/| $$ \ $$
|
||||||
|
| $$ $$/$$ $$| $$$$$/ | $$$$$$$ | $$$$$$ | $$ | $$
|
||||||
|
| $$ $$$| $$| $$ $$ | $$__ $$ \____ $$| $$ | $$
|
||||||
|
| $$\ $ | $$| $$\ $$ | $$ \ $$ /$$ \ $$| $$ | $$
|
||||||
|
| $$ \/ | $$| $$ \ $$| $$$$$$$/| $$$$$$/| $$$$$$$/
|
||||||
|
|__/ |__/|__/ \__/|_______/ \______/ |_______/
|
||||||
|
"#
|
||||||
|
);
|
||||||
|
println!("🤑 Starting downloads from your favorite sellout grifter's wallpaper app...");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
ascii_art();
|
||||||
|
sleep(Duration::from_secs(5)).await;
|
||||||
|
if let Err(e) = main_task().await {
|
||||||
|
eprintln!("Error: {}", e);
|
||||||
|
}
|
||||||
|
}
|
57
README.md
57
README.md
|
@ -13,7 +13,7 @@ _Because selling out is bad_
|
||||||
|
|
||||||
## How to use
|
## How to use
|
||||||
|
|
||||||
MKBSD comes in two variants! Node.js and Python.
|
MKBSD comes in three variants! Node.js, Python, and Rust.
|
||||||
|
|
||||||
### Running in Node.js
|
### Running in Node.js
|
||||||
|
|
||||||
|
@ -30,23 +30,62 @@ MKBSD comes in two variants! Node.js and Python.
|
||||||
4. Wait a little.
|
4. Wait a little.
|
||||||
5. All wallpapers are now in a newly created `downloads` subfolder.
|
5. All wallpapers are now in a newly created `downloads` subfolder.
|
||||||
|
|
||||||
|
### Running in Rust
|
||||||
|
|
||||||
|
1. **Install Rust and Cargo**
|
||||||
|
- If you haven't already, install Rust and Cargo by following the instructions at [rustup.rs](https://rustup.rs/).
|
||||||
|
|
||||||
|
2. **Navigate to the Rust Implementation Directory**
|
||||||
|
- Open your terminal or command prompt.
|
||||||
|
- Navigate to the directory containing the Rust implementation (e.g., `mkbsd.rs` or the Cargo project folder).
|
||||||
|
|
||||||
|
3. **Build the Project**
|
||||||
|
- Run the following command to build the project in release mode:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo build --release
|
||||||
|
```
|
||||||
|
|
||||||
|
- This will create an optimized executable in the `target/release` directory.
|
||||||
|
|
||||||
|
4. **Run the Executable**
|
||||||
|
- After building, run the executable:
|
||||||
|
|
||||||
|
- On Linux/macOS:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./target/release/mkbsd
|
||||||
|
```
|
||||||
|
|
||||||
|
- On Windows:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
.\target\release\mkbsd.exe
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Wait for the Process to Complete**
|
||||||
|
- The program will start downloading wallpapers. Wait until it finishes.
|
||||||
|
|
||||||
|
6. **Find Your Wallpapers**
|
||||||
|
- All wallpapers are now in a newly created `downloads` subfolder.
|
||||||
|
|
||||||
## FAQ
|
## FAQ
|
||||||
|
|
||||||
### Q: What's the story behind this?
|
### Q: What's the story behind this?
|
||||||
|
|
||||||
On September 24th, 2024, well-known tech YouTuber MKBHD released Panels, a wallpaper app that:
|
On September 24th, 2024, a well-known tech personality released a wallpaper app that:
|
||||||
|
|
||||||
- Had insanely invasive, unjustified tracking including for location history and search history.
|
- Included invasive tracking features.
|
||||||
- Charged artists a predatory 50% commission (even Apple takes only 30% for app purchases).
|
- Charged artists a high commission fee.
|
||||||
- Forced you to watch two ads for every wallpaper that you wanted to download, and then only letting you download it in SD.
|
- Required users to watch multiple ads for each wallpaper download, offering only low-resolution images.
|
||||||
- Gatekept all HD wallpapers behind a **fifty dollars a year subscription**.
|
- Locked high-resolution wallpapers behind an expensive subscription.
|
||||||
- Had many wallpapers that were essentially AI-generated slop or badly edited stock photos.
|
- Featured wallpapers that were of questionable quality.
|
||||||
|
|
||||||
Especially given MKBHD's previous criticism of substandard companies and products, people justifiably got upset given that this looked like a pretty blatant grift and cash-grab that is exploitative of the fan base that's trusted his editorial integrity over the past fifteen years. However, on the same day, MKBHD wrote a post doubling down on the app.
|
This project was created as a response to those practices, aiming to provide users with access to wallpapers without such limitations.
|
||||||
|
|
||||||
### Q: Aren't you stealing from artists by running this script?
|
### Q: Aren't you stealing from artists by running this script?
|
||||||
|
|
||||||
MKBSD accesses publicly available media through the Panels app's own API. It doesn't do anything shady or illegal. The real problem here is Panels and MKBHD's complete inability to provide a secure platform for the artists that they're ~~exploiting~~ working with. Any other app could have avoided the issues that make MKBSD possible had it been engineered competently.
|
MKBSD accesses publicly available media through the app's own API. It doesn't do anything illegal or unethical. The script highlights issues in the app's design regarding content accessibility. Ideally, apps should ensure secure platforms for artists to protect their work.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue