about summary refs log tree commit diff stats
path: root/sys/nixpkgs/pkgs/generate_moz_extension/src/main.rs
blob: bde986a31b330a2df08f38cb7045494735564ebb (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
use std::env::args;

use anyhow::{bail, Context};
use futures::StreamExt;
use reqwest::Client;
use serde_json::{json, Map, Value};

pub mod types;

macro_rules! get_json_value {
    ($key:expr, $json_value:ident, $type:ident, $get:ident) => {
        match $json_value.get($key) {
            Some(resp) => {
                let resp = resp.to_owned();
                if resp.$type() {
                    resp.$get().expect(
                        "The should have been checked in the if guard, so unpacking here is fine",
                    ).to_owned()
                } else {
                    bail!(
                        "Value {} => \n{}\n is not of type: {}",
                        $key,
                        resp,
                        stringify!($type)
                    );
                }
            }
            None => {
                bail!(
                    "There seems to be no '{}' in your json data (json value: '{}')\n Has the api changend?",
                    $key, serde_json::to_string_pretty(&$json_value).expect("Will always work")
                );
            }
        }
    };
}

use futures::stream::futures_unordered::FuturesUnordered;
use types::{Extension, InputExtension};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let mut extensions: Vec<InputExtension> = vec![];
    for input_extension in args()
        .skip(1)
        .map(|str| InputExtension::try_from(str))
        .collect::<Vec<anyhow::Result<InputExtension>>>()
    {
        extensions.push(input_extension?);
    }

    let resulting_extensions = process_extensions(extensions).await?;

    let mut output = Map::new();
    for extension in resulting_extensions {
        output.insert(extension.pname.clone(), json!(extension));
    }

    println!(
        "{}",
        serde_json::to_string_pretty(&serde_json::Value::Object(output)).expect(
            "This is constructed from json, it should also be possible to serialize it again"
        )
    );
    Ok(())
}

async fn process_extensions(extensions: Vec<InputExtension>) -> anyhow::Result<Vec<Extension>> {
    let mut output = Vec::with_capacity(extensions.len());

    let client = Client::new();
    for extension in extensions
        .iter()
        .map(|ext| {
            let local_client = &client;
            index_extension(ext, local_client)
        })
        .collect::<FuturesUnordered<_>>()
        .collect::<Vec<_>>()
        .await
    {
        output.push(extension?);
    }
    Ok(output)
}

async fn index_extension(extension: &InputExtension, client: &Client) -> anyhow::Result<Extension> {
    let response = client
        .get(format!(
            "https://addons.mozilla.org/api/v5/addons/addon/{}",
            extension,
        ))
        .send()
        .await
        .context("Accessing the mozzila extenios api failed with error: {e}")?;

    eprintln!("Indexing {} ({})...", extension, response.status());
    let response: Value = serde_json::from_str(
        &response
            .text()
            .await
            .context("Turning the response to text fail with error: {e}")?,
    )
    .context("Deserializing the response failed! Error: {e}")?;

    if let Some(detail) = response.get("detail") {
        if detail == "Not found." {
            bail!("Your extension ('{}') was not found!", extension);
        }
    };

    let release = { get_json_value!("current_version", response, is_object, as_object) };

    #[allow(non_snake_case)]
    let addonId = { get_json_value!("guid", response, is_string, as_str) };

    let version = { get_json_value!("version", release, is_string, as_str) };
    let file = { get_json_value!("file", release, is_object, as_object) };

    let url = { get_json_value!("url", file, is_string, as_str) };
    let sha256 = {
        let hash = get_json_value!("hash", file, is_string, as_str);
        if hash.starts_with("sha256:") {
            hash
        } else {
            bail!("This hash type is unhandled: {}", hash);
        }
    };

    Ok(Extension {
        pname: extension.moz_name.clone(),
        default_area: extension.default_area,
        version,
        addonId,
        url,
        sha256,
    })
}