about summary refs log tree commit diff stats
path: root/pkgs/sources/generate_moz_extension/src
diff options
context:
space:
mode:
authorBenedikt Peetz <benedikt.peetz@b-peetz.de>2024-05-20 16:10:21 +0200
committerBenedikt Peetz <benedikt.peetz@b-peetz.de>2024-05-20 16:14:26 +0200
commit368cb6b0d25db2ae23be42ad51584de059997e51 (patch)
tree3282e45d3ebced63c8498a47e83a255c35de620b /pkgs/sources/generate_moz_extension/src
parentrefactor(hm): Rename to `modules/home` (diff)
downloadnixos-config-368cb6b0d25db2ae23be42ad51584de059997e51.tar.gz
nixos-config-368cb6b0d25db2ae23be42ad51584de059997e51.zip
refactor(sys): Modularize and move to `modules/system` or `pkgs`
Diffstat (limited to 'pkgs/sources/generate_moz_extension/src')
-rw-r--r--pkgs/sources/generate_moz_extension/src/main.rs138
-rw-r--r--pkgs/sources/generate_moz_extension/src/types.rs71
2 files changed, 209 insertions, 0 deletions
diff --git a/pkgs/sources/generate_moz_extension/src/main.rs b/pkgs/sources/generate_moz_extension/src/main.rs
new file mode 100644
index 00000000..bde986a3
--- /dev/null
+++ b/pkgs/sources/generate_moz_extension/src/main.rs
@@ -0,0 +1,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,
+    })
+}
diff --git a/pkgs/sources/generate_moz_extension/src/types.rs b/pkgs/sources/generate_moz_extension/src/types.rs
new file mode 100644
index 00000000..b830fe0d
--- /dev/null
+++ b/pkgs/sources/generate_moz_extension/src/types.rs
@@ -0,0 +1,71 @@
+use std::fmt::Display;
+
+use anyhow::anyhow;
+use serde::{Deserialize, Serialize};
+
+#[derive(Debug, Serialize, Deserialize)]
+#[allow(non_snake_case)]
+pub struct Extension {
+    pub pname: String,
+    pub default_area: DefaultArea,
+    pub version: String,
+    pub addonId: String,
+    pub url: String,
+    pub sha256: String,
+}
+
+#[derive(Debug, Clone)]
+pub struct InputExtension {
+    pub moz_name: String,
+    pub default_area: DefaultArea,
+}
+#[derive(Debug, Clone, Copy, Deserialize, Serialize)]
+#[allow(non_camel_case_types)]
+pub enum DefaultArea {
+    navbar,
+    menupanel,
+}
+
+impl Display for DefaultArea {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            DefaultArea::navbar => f.write_str("navbar"),
+            DefaultArea::menupanel => f.write_str("menupanel"),
+        }
+    }
+}
+
+impl TryFrom<&str> for DefaultArea {
+    type Error = anyhow::Error;
+
+    fn try_from(value: &str) -> Result<Self, Self::Error> {
+        match value {
+            "navbar" => Ok(Self::navbar),
+            "menupanel" => Ok(Self::menupanel),
+            _ => Err(anyhow!(
+                "Your <default_area> needs to be one of 'navbar' or 'menupanel', but is: '{}'",
+                value
+            )),
+        }
+    }
+}
+
+impl Display for InputExtension {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.write_str(&self.moz_name)
+    }
+}
+impl TryFrom<String> for InputExtension {
+    type Error = anyhow::Error;
+
+    fn try_from(value: String) -> Result<Self, Self::Error> {
+        if let Some((moz_name, default_area)) = value.split_once(':') {
+            Ok(Self {
+                moz_name: moz_name.to_owned(),
+                default_area: default_area.try_into()?,
+            })
+        } else {
+            Err(anyhow!("Can't parse the input string as a InputExtension!\n Needs to be: '<moz_name>:<default_area>'"))
+        }
+    }
+}