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}