anki_bridge 0.10.2

AnkiBridge is a Rust library that provides a bridge between your Rust code and the Anki application, enabling HTTP communication and seamless data transmission.
Documentation
/*
* The MIT License (MIT)
*
* Copyright (c) 2023 Daniél Kerkmann <daniel@kerkmann.dev>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

use serde::{Deserialize, Serialize};
use std::collections::HashMap;

use crate::AnkiRequest;
use crate::entities::NoteId;

/// Parameters for adding cards using the _Add Cards_ dialog.
#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize)]
pub struct GuiAddCardsRequest {
    /// The note to add using the _Add Cards_ dialog.
    pub note: GuiAddCardsNote,
}

/// A new Anki note (also called card) that will be added using the _Add Cards_ dialog.
#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GuiAddCardsNote {
    /// Name of the deck where the note will be added.
    pub deck_name: String,
    /// Name of the model inside the deck where the note will be added.
    pub model_name: String,
    /// Fields available in the note.
    #[serde(serialize_with = "crate::serialize::hashmap")]
    pub fields: HashMap<String, String>,
    /// The tags of the note.
    pub tags: Vec<String>,
    /// The audio samples attached to the note.
    pub audio: Vec<GuiAddCardsNoteMedia>,
    /// The videos attached to the note.
    pub video: Vec<GuiAddCardsNoteMedia>,
    /// The pictures attached to the note.
    pub picture: Vec<GuiAddCardsNoteMedia>,
}

/// A file attached to the note for adding cards using the Add Cards dialog.
#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct GuiAddCardsNoteMedia {
    /// The filename the file will be stored under in the media folder.
    ///
    /// To prevent Anki from removing files not used by any cards (e.g. for configuration files),
    /// prefix the filename with an underscore.
    pub filename: String,
    /// List of fields that should play audio or video, or show a picture when the card is displayed
    /// in Anki.
    pub fields: Vec<String>,
    /// The base64-encoded file contents. One of `data`, `path` or `url` must be specified.
    pub data: Option<String>,
    /// The absolute path where the file will be loaded from. One of `data`, `path` or `url` must be
    /// specified.
    pub path: Option<String>,
    /// The URL where the file contents will be fetched from. One of `data`, `path` or `url` must be
    /// specified.
    pub url: Option<String>,
}

impl AnkiRequest for GuiAddCardsRequest {
    type Response = NoteId;

    const ACTION: &'static str = "guiAddCards";
    const VERSION: u8 = 6;
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_serialize() {
        let request = GuiAddCardsRequest {
            note: GuiAddCardsNote {
                deck_name: "My Deck".into(),
                model_name: "My Model".into(),
                fields: HashMap::from([
                    ("Text".into(), "暗記".into()),
                    (
                        "Meaning".into(),
                        "memorization; memorisation; learning by heart".into(),
                    ),
                ]),
                tags: vec!["Tag1".into(), "Tag2".into()],
                audio: vec![
                    GuiAddCardsNoteMedia {
                        filename: "my_audio.mp3".into(),
                        fields: vec!["Audio".into()],
                        data: None,
                        path: None,
                        url: Some("https://mirrors.edge.kernel.org/pub/linux/kernel/SillySounds/english.ogg".into()),
                    }
                ],
                video: vec![
                    GuiAddCardsNoteMedia {
                        filename: "my_video.mp4".into(),
                        fields: vec!["Video".into()],
                        data: None,
                        path: Some("/tmp/my_video.mp4".into()),
                        url: None,
                    }
                ],
                picture: vec![
                    GuiAddCardsNoteMedia {
                        filename: "my_picture.mp4".into(),
                        fields: vec!["Picture".into()],
                        // https://gist.github.com/ondrek/7413434#gistcomment-3914686
                        data: Some("iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=".into()),
                        path: None,
                        url: None,
                    }],
            },
        };

        let json = serde_json::to_string_pretty(&request).unwrap();
        assert_eq!(
            json,
            r#"{
  "note": {
    "deckName": "My Deck",
    "modelName": "My Model",
    "fields": {
      "Meaning": "memorization; memorisation; learning by heart",
      "Text": "暗記"
    },
    "tags": [
      "Tag1",
      "Tag2"
    ],
    "audio": [
      {
        "filename": "my_audio.mp3",
        "fields": [
          "Audio"
        ],
        "data": null,
        "path": null,
        "url": "https://mirrors.edge.kernel.org/pub/linux/kernel/SillySounds/english.ogg"
      }
    ],
    "video": [
      {
        "filename": "my_video.mp4",
        "fields": [
          "Video"
        ],
        "data": null,
        "path": "/tmp/my_video.mp4",
        "url": null
      }
    ],
    "picture": [
      {
        "filename": "my_picture.mp4",
        "fields": [
          "Picture"
        ],
        "data": "iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=",
        "path": null,
        "url": null
      }
    ]
  }
}"#
        );
    }

    #[test]
    fn test_deserialize() {
        let json = "1496198395707";
        let response: usize = serde_json::from_str(json).unwrap();

        assert_eq!(response, 1496198395707);
    }
}