initial commit
This commit is contained in:
commit
0c63ab3272
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
*/downloads/
|
||||||
|
*/target/
|
||||||
|
*/**/mkbsd-go
|
11
golang/go.mod
Normal file
11
golang/go.mod
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
module git.vomitblood.com/Vomitblood/mkbsd/golang
|
||||||
|
|
||||||
|
go 1.23.1
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
|
||||||
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
|
github.com/schollz/progressbar/v3 v3.16.0 // indirect
|
||||||
|
golang.org/x/sys v0.25.0 // indirect
|
||||||
|
golang.org/x/term v0.24.0 // indirect
|
||||||
|
)
|
10
golang/go.sum
Normal file
10
golang/go.sum
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
|
||||||
|
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
|
||||||
|
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
|
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
|
github.com/schollz/progressbar/v3 v3.16.0 h1:+MbBim/cE9DqDb8UXRfLJ6RZdyDkXG1BDy/sWc5s0Mc=
|
||||||
|
github.com/schollz/progressbar/v3 v3.16.0/go.mod h1:lLiKjKJ9/yzc9Q8jk+sVLfxWxgXKsktvUf6TO+4Y2nw=
|
||||||
|
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||||
|
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
|
||||||
|
golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
|
152
golang/main.go
Normal file
152
golang/main.go
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/schollz/progressbar/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
const url = "https://storage.googleapis.com/panels-api/data/20240916/media-1a-i-p~s"
|
||||||
|
|
||||||
|
// data struct to get only dhd stuff
|
||||||
|
type Data struct {
|
||||||
|
Data map[string]struct {
|
||||||
|
Dhd string `json:"dhd"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
response, error := fetchData(url)
|
||||||
|
if error != nil {
|
||||||
|
fmt.Printf("Failed to fetch data: %v\n", error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonData, error := parseData(response)
|
||||||
|
if error != nil {
|
||||||
|
fmt.Printf("Failed to parse data: %v\n", error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the total number of images to download
|
||||||
|
imageCount := countImages(jsonData)
|
||||||
|
fmt.Printf("Total images to download: %d\n", imageCount)
|
||||||
|
|
||||||
|
// ensure that the downloads directory exists in the same directory as the executable
|
||||||
|
downloadDir := "downloads"
|
||||||
|
|
||||||
|
if err := createDirectory(downloadDir); err != nil {
|
||||||
|
fmt.Printf("Failed to create directory: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bar := progressbar.Default(int64(imageCount))
|
||||||
|
fileIndex := 1
|
||||||
|
|
||||||
|
// iterate over the data with dhd prop and download each image
|
||||||
|
for _, subproperty := range jsonData.Data {
|
||||||
|
if subproperty.Dhd != "" {
|
||||||
|
imageUrl := subproperty.Dhd
|
||||||
|
|
||||||
|
// download the image and save it
|
||||||
|
err := downloadImage(imageUrl, downloadDir, fileIndex)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error downloading image: %v\n", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
bar.Add(1)
|
||||||
|
fileIndex++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%d/%d images downloaded successfully\n", fileIndex, imageCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchData(url string) (*http.Response, error) {
|
||||||
|
response, error := http.Get(url)
|
||||||
|
|
||||||
|
if error != nil {
|
||||||
|
return nil, fmt.Errorf("failed to fetch JSON file: %v", error)
|
||||||
|
}
|
||||||
|
|
||||||
|
if response.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("failed to fetch JSON file: %s", response.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseData(response *http.Response) (Data, error) {
|
||||||
|
var jsonData Data
|
||||||
|
error := json.NewDecoder(response.Body).Decode(&jsonData)
|
||||||
|
|
||||||
|
if error != nil {
|
||||||
|
return Data{}, fmt.Errorf("failed to parse JSON: %v", error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsonData, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// countImages returns the total number of images to download
|
||||||
|
func countImages(jsonData Data) int {
|
||||||
|
count := 0
|
||||||
|
for _, subproperty := range jsonData.Data {
|
||||||
|
if subproperty.Dhd != "" {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
func createDirectory(dir string) error {
|
||||||
|
if _, error := os.Stat(dir); os.IsNotExist(error) {
|
||||||
|
error = os.Mkdir(dir, os.ModePerm)
|
||||||
|
if error != nil {
|
||||||
|
return error
|
||||||
|
}
|
||||||
|
fmt.Printf("Created directory: %s\n", dir)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// downloadImage downloads an image from a url and saves it to the download directory
|
||||||
|
func downloadImage(imageUrl, downloadDir string, fileIndex int) error {
|
||||||
|
// fetch the image
|
||||||
|
response, error := http.Get(imageUrl)
|
||||||
|
if error != nil {
|
||||||
|
return fmt.Errorf("failed to download image: %v", error)
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
|
||||||
|
if response.StatusCode != http.StatusOK {
|
||||||
|
return fmt.Errorf("failed to download image: %s", response.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove all query parameters from the image url
|
||||||
|
imageUrlWithoutParams := strings.Split(imageUrl, "?")[0]
|
||||||
|
|
||||||
|
// create the image file
|
||||||
|
fileExtension := filepath.Ext(imageUrlWithoutParams)
|
||||||
|
filePath := filepath.Join(downloadDir, fmt.Sprintf("%d%s", fileIndex, fileExtension))
|
||||||
|
file, error := os.Create(filePath)
|
||||||
|
if error != nil {
|
||||||
|
return fmt.Errorf("failed to create file: %v", error)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
// write the image data to file
|
||||||
|
_, error = io.Copy(file, response.Body)
|
||||||
|
if error != nil {
|
||||||
|
return fmt.Errorf("failed to save image: %v", error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
1302
rust/Cargo.lock
generated
Normal file
1302
rust/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
10
rust/Cargo.toml
Normal file
10
rust/Cargo.toml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
[package]
|
||||||
|
name = "mkbsd-rs"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
reqwest = { version = "0.12.7", features = ["blocking", "json"] }
|
||||||
|
serde = { version = "1.0.210", features = ["derive"] }
|
||||||
|
serde_json = "1.0.128"
|
||||||
|
indicatif = "0.17.8"
|
105
rust/src/main.rs
Normal file
105
rust/src/main.rs
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
const URL: &str = "https://storage.googleapis.com/panels-api/data/20240916/media-1a-i-p~s";
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
struct Data {
|
||||||
|
data: std::collections::HashMap<String, SubProperty>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
struct SubProperty {
|
||||||
|
dhd: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
println!("Fetching data from: {URL}");
|
||||||
|
let response = fetch_data(URL)?;
|
||||||
|
let json_data: Data = parse_data(&response)?;
|
||||||
|
|
||||||
|
let image_count = count_images(&json_data);
|
||||||
|
println!("Total images to download: {}", image_count);
|
||||||
|
|
||||||
|
let download_dir = "downloads";
|
||||||
|
create_directory(download_dir)?;
|
||||||
|
|
||||||
|
// create the progress bar
|
||||||
|
let bar = indicatif::ProgressBar::new(image_count as u64);
|
||||||
|
bar.set_style(
|
||||||
|
indicatif::ProgressStyle::with_template(
|
||||||
|
"{spinner} [{pos}/{len}] {wide_bar} {percent_precise}% ({eta_precise})",
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.progress_chars("##-"),
|
||||||
|
);
|
||||||
|
|
||||||
|
let client = reqwest::blocking::Client::new();
|
||||||
|
let mut file_index = 1;
|
||||||
|
let mut success_count = 0;
|
||||||
|
|
||||||
|
for subproperty in json_data.data.values() {
|
||||||
|
if let Some(image_url) = &subproperty.dhd {
|
||||||
|
match download_image(&client, image_url, download_dir, file_index) {
|
||||||
|
Ok(_) => {
|
||||||
|
bar.inc(1);
|
||||||
|
file_index += 1;
|
||||||
|
success_count += 1;
|
||||||
|
}
|
||||||
|
Err(e) => eprintln!("Error downloading image: {}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bar.tick();
|
||||||
|
}
|
||||||
|
|
||||||
|
bar.finish();
|
||||||
|
println!(
|
||||||
|
"{}/{} images downloaded successfully",
|
||||||
|
success_count, image_count
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fetch_data(url: &str) -> Result<String, reqwest::Error> {
|
||||||
|
reqwest::blocking::get(url)?.text()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_data(response: &str) -> Result<Data, serde_json::Error> {
|
||||||
|
serde_json::from_str(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn count_images(json_data: &Data) -> usize {
|
||||||
|
json_data
|
||||||
|
.data
|
||||||
|
.values()
|
||||||
|
.filter(|subprop| subprop.dhd.is_some())
|
||||||
|
.count()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_directory(dir: &str) -> std::io::Result<()> {
|
||||||
|
std::fs::create_dir_all(dir)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn download_image(
|
||||||
|
client: &reqwest::blocking::Client,
|
||||||
|
image_url: &str,
|
||||||
|
download_dir: &str,
|
||||||
|
file_index: usize,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let response = client.get(image_url).send()?;
|
||||||
|
if !response.status().is_success() {
|
||||||
|
return Err(format!("Failed to download image: {}", response.status()).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let image_url_without_params = image_url.split('?').next().unwrap_or(image_url);
|
||||||
|
let file_extension = std::path::Path::new(image_url_without_params)
|
||||||
|
.extension()
|
||||||
|
.and_then(|ext| ext.to_str())
|
||||||
|
.map(|ext| format!(".{}", ext))
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let file_path = format!("{}/{}{}", download_dir, file_index, file_extension);
|
||||||
|
|
||||||
|
let mut file = std::fs::File::create(&file_path)?;
|
||||||
|
std::io::Write::write_all(&mut file, &response.bytes()?)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
Loading…
Reference in a new issue