anki_bridge/graphical_actions/
gui_add_cards.rs

1/*
2* The MIT License (MIT)
3*
4* Copyright (c) 2023 Daniél Kerkmann <daniel@kerkmann.dev>
5*
6* Permission is hereby granted, free of charge, to any person obtaining a copy
7* of this software and associated documentation files (the "Software"), to deal
8* in the Software without restriction, including without limitation the rights
9* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10* copies of the Software, and to permit persons to whom the Software is
11* furnished to do so, subject to the following conditions:
12*
13* The above copyright notice and this permission notice shall be included in all
14* copies or substantial portions of the Software.
15*
16* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22* SOFTWARE.
23*/
24
25use serde::{Deserialize, Serialize};
26use std::collections::HashMap;
27
28use crate::AnkiRequest;
29use crate::entities::NoteId;
30
31/// Parameters for adding cards using the _Add Cards_ dialog.
32#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize)]
33pub struct GuiAddCardsRequest {
34    /// The note to add using the _Add Cards_ dialog.
35    pub note: GuiAddCardsNote,
36}
37
38/// A new Anki note (also called card) that will be added using the _Add Cards_ dialog.
39#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
40#[serde(rename_all = "camelCase")]
41pub struct GuiAddCardsNote {
42    /// Name of the deck where the note will be added.
43    pub deck_name: String,
44    /// Name of the model inside the deck where the note will be added.
45    pub model_name: String,
46    /// Fields available in the note.
47    #[serde(serialize_with = "crate::serialize::hashmap")]
48    pub fields: HashMap<String, String>,
49    /// The tags of the note.
50    pub tags: Vec<String>,
51    /// The audio samples attached to the note.
52    pub audio: Vec<GuiAddCardsNoteMedia>,
53    /// The videos attached to the note.
54    pub video: Vec<GuiAddCardsNoteMedia>,
55    /// The pictures attached to the note.
56    pub picture: Vec<GuiAddCardsNoteMedia>,
57}
58
59/// A file attached to the note for adding cards using the Add Cards dialog.
60#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
61pub struct GuiAddCardsNoteMedia {
62    /// The filename the file will be stored under in the media folder.
63    ///
64    /// To prevent Anki from removing files not used by any cards (e.g. for configuration files),
65    /// prefix the filename with an underscore.
66    pub filename: String,
67    /// List of fields that should play audio or video, or show a picture when the card is displayed
68    /// in Anki.
69    pub fields: Vec<String>,
70    /// The base64-encoded file contents. One of `data`, `path` or `url` must be specified.
71    pub data: Option<String>,
72    /// The absolute path where the file will be loaded from. One of `data`, `path` or `url` must be
73    /// specified.
74    pub path: Option<String>,
75    /// The URL where the file contents will be fetched from. One of `data`, `path` or `url` must be
76    /// specified.
77    pub url: Option<String>,
78}
79
80impl AnkiRequest for GuiAddCardsRequest {
81    type Response = NoteId;
82
83    const ACTION: &'static str = "guiAddCards";
84    const VERSION: u8 = 6;
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    #[test]
92    fn test_serialize() {
93        let request = GuiAddCardsRequest {
94            note: GuiAddCardsNote {
95                deck_name: "My Deck".into(),
96                model_name: "My Model".into(),
97                fields: HashMap::from([
98                    ("Text".into(), "暗記".into()),
99                    (
100                        "Meaning".into(),
101                        "memorization; memorisation; learning by heart".into(),
102                    ),
103                ]),
104                tags: vec!["Tag1".into(), "Tag2".into()],
105                audio: vec![
106                    GuiAddCardsNoteMedia {
107                        filename: "my_audio.mp3".into(),
108                        fields: vec!["Audio".into()],
109                        data: None,
110                        path: None,
111                        url: Some("https://mirrors.edge.kernel.org/pub/linux/kernel/SillySounds/english.ogg".into()),
112                    }
113                ],
114                video: vec![
115                    GuiAddCardsNoteMedia {
116                        filename: "my_video.mp4".into(),
117                        fields: vec!["Video".into()],
118                        data: None,
119                        path: Some("/tmp/my_video.mp4".into()),
120                        url: None,
121                    }
122                ],
123                picture: vec![
124                    GuiAddCardsNoteMedia {
125                        filename: "my_picture.mp4".into(),
126                        fields: vec!["Picture".into()],
127                        // https://gist.github.com/ondrek/7413434#gistcomment-3914686
128                        data: Some("iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=".into()),
129                        path: None,
130                        url: None,
131                    }],
132            },
133        };
134
135        let json = serde_json::to_string_pretty(&request).unwrap();
136        assert_eq!(
137            json,
138            r#"{
139  "note": {
140    "deckName": "My Deck",
141    "modelName": "My Model",
142    "fields": {
143      "Meaning": "memorization; memorisation; learning by heart",
144      "Text": "暗記"
145    },
146    "tags": [
147      "Tag1",
148      "Tag2"
149    ],
150    "audio": [
151      {
152        "filename": "my_audio.mp3",
153        "fields": [
154          "Audio"
155        ],
156        "data": null,
157        "path": null,
158        "url": "https://mirrors.edge.kernel.org/pub/linux/kernel/SillySounds/english.ogg"
159      }
160    ],
161    "video": [
162      {
163        "filename": "my_video.mp4",
164        "fields": [
165          "Video"
166        ],
167        "data": null,
168        "path": "/tmp/my_video.mp4",
169        "url": null
170      }
171    ],
172    "picture": [
173      {
174        "filename": "my_picture.mp4",
175        "fields": [
176          "Picture"
177        ],
178        "data": "iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=",
179        "path": null,
180        "url": null
181      }
182    ]
183  }
184}"#
185        );
186    }
187
188    #[test]
189    fn test_deserialize() {
190        let json = "1496198395707";
191        let response: usize = serde_json::from_str(json).unwrap();
192
193        assert_eq!(response, 1496198395707);
194    }
195}