anki_connect 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +67 -0
- data/lib/anki_connect/cards.rb +150 -0
- data/lib/anki_connect/client.rb +74 -0
- data/lib/anki_connect/decks.rb +108 -0
- data/lib/anki_connect/graphical.rb +165 -0
- data/lib/anki_connect/media.rb +55 -0
- data/lib/anki_connect/miscellaneous.rb +97 -0
- data/lib/anki_connect/models.rb +238 -0
- data/lib/anki_connect/notes.rb +168 -0
- data/lib/anki_connect/statistics.rb +65 -0
- data/lib/anki_connect/version.rb +9 -0
- data/lib/anki_connect.rb +24 -0
- metadata +56 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 12cd7e1f5f63083dfb30d873a1ba70985b90b2107dcb50de09f151b8414532aa
|
|
4
|
+
data.tar.gz: 03145b84fe4f8ef9bb7412d2bc65f79dafffcdc9a8758dc0ff84e3b1263f41f1
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 46dffccf988e0497c76c7129d40be5b2becd687615730d3b53ff515eceb2a0ddc93d991929c7de8b56e2fae8d57e7419c21d92059df636a125d5d49ab03ab2d9
|
|
7
|
+
data.tar.gz: 261dd1bcd832014c623ee8c87ade177c0084e0d578e6808f138e6170a7464110ee98c5e07e20244a6e2b85bef72ddeb1f7e97f71c25a2a481e3eb4830fcd89f2
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 vadik49b
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# AnkiConnect Ruby
|
|
2
|
+
|
|
3
|
+
[AnkiConnect](https://git.sr.ht/~foosoft/anki-connect) provides a simple HTTP API to communicate with Anki. This Ruby gem is a wrapper around that API.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- Ruby 3.4+
|
|
8
|
+
- Anki with Anki-Connect plugin installed
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
Add this line to your application's Gemfile:
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
gem 'anki_connect'
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Or install it yourself:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
gem install anki_connect
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
require 'anki_connect'
|
|
28
|
+
|
|
29
|
+
# Create a client (default: localhost:8765)
|
|
30
|
+
client = AnkiConnect::Client.new
|
|
31
|
+
|
|
32
|
+
# Get all decks
|
|
33
|
+
decks = client.deck_names
|
|
34
|
+
|
|
35
|
+
# Add a new note
|
|
36
|
+
note_id = client.add_note(
|
|
37
|
+
deck_name: "Default",
|
|
38
|
+
model_name: "Basic",
|
|
39
|
+
fields: { Front: "What is Ruby?", Back: "A programming language" },
|
|
40
|
+
tags: ["programming"]
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
# Get note details
|
|
44
|
+
notes = client.get_notes(query: "deck:Default")
|
|
45
|
+
# => [{ "noteId" => 1234567890,
|
|
46
|
+
# "modelName" => "Basic",
|
|
47
|
+
# "tags" => ["programming"],
|
|
48
|
+
# "fields" => { "Front" => { "value" => "What is Ruby?", "order" => 0 },
|
|
49
|
+
# "Back" => { "value" => "A programming language", "order" => 1 } } }]
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
For more examples, see the [`examples/`](examples/) directory.
|
|
53
|
+
|
|
54
|
+
## Development
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# Install dependencies
|
|
58
|
+
bundle install
|
|
59
|
+
|
|
60
|
+
# Open console with gem loaded
|
|
61
|
+
bundle exec rake console
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Acknowledgments
|
|
65
|
+
|
|
66
|
+
- [Anki-Connect](https://git.sr.ht/~foosoft/anki-connect) by FooSoft for the excellent Anki plugin
|
|
67
|
+
- [Anki](https://apps.ankiweb.net/) for the amazing spaced repetition software
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AnkiConnect
|
|
4
|
+
class Client
|
|
5
|
+
# Methods to query, modify, suspend, and manage individual flashcards.
|
|
6
|
+
module Cards
|
|
7
|
+
# Gets ease factors for cards.
|
|
8
|
+
#
|
|
9
|
+
# @param card_ids [Array<Integer>] Array of card IDs
|
|
10
|
+
# @return [Array<Integer>] Array of ease factor values
|
|
11
|
+
def get_ease_factors(card_ids)
|
|
12
|
+
request(:getEaseFactors, cards: card_ids)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Sets ease factors for cards.
|
|
16
|
+
#
|
|
17
|
+
# @param card_ids [Array<Integer>] Array of card IDs
|
|
18
|
+
# @param factors [Array<Integer>] Array of ease factor values
|
|
19
|
+
# @return [Array<Boolean>] Array indicating success for each card
|
|
20
|
+
def set_ease_factors(card_ids, factors)
|
|
21
|
+
request(:setEaseFactors, cards: card_ids, easeFactors: factors)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Sets specific database values for a single card.
|
|
25
|
+
#
|
|
26
|
+
# @param card_id [Integer] Card ID
|
|
27
|
+
# @param fields [Hash] Database field names to new values
|
|
28
|
+
# @param warning_check [Boolean] Must be true for certain risky keys
|
|
29
|
+
# @return [Array<Boolean>] Array indicating success for each field
|
|
30
|
+
def update_card(card_id, fields, warning_check: false)
|
|
31
|
+
request(:setSpecificValueOfCard, card: card_id, keys: fields.keys, newValues: fields.values,
|
|
32
|
+
warning_check: warning_check)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Suspends cards.
|
|
36
|
+
#
|
|
37
|
+
# @param card_ids [Array<Integer>] Array of card IDs
|
|
38
|
+
# @return [Boolean] true if at least one card wasn't already suspended
|
|
39
|
+
def suspend_cards(card_ids)
|
|
40
|
+
request(:suspend, cards: card_ids)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Unsuspends cards.
|
|
44
|
+
#
|
|
45
|
+
# @param card_ids [Array<Integer>] Array of card IDs
|
|
46
|
+
# @return [Boolean] true if at least one card was previously suspended
|
|
47
|
+
def unsuspend_cards(card_ids)
|
|
48
|
+
request(:unsuspend, cards: card_ids)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Checks suspension status for cards.
|
|
52
|
+
#
|
|
53
|
+
# @param card_ids [Integer, Array<Integer>] Single card ID or array
|
|
54
|
+
# @return [Boolean, Array<Boolean, nil>] Boolean for single, array for multiple
|
|
55
|
+
def suspended?(card_ids)
|
|
56
|
+
if card_ids.is_a?(Array)
|
|
57
|
+
request(:areSuspended, cards: card_ids)
|
|
58
|
+
else
|
|
59
|
+
request(:suspended, card: card_ids)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Checks if cards are due for review.
|
|
64
|
+
#
|
|
65
|
+
# @param card_ids [Integer, Array<Integer>] Single card ID or array
|
|
66
|
+
# @return [Boolean, Array<Boolean>] Boolean for single, array for multiple
|
|
67
|
+
def due?(card_ids)
|
|
68
|
+
if card_ids.is_a?(Array)
|
|
69
|
+
request(:areDue, cards: card_ids)
|
|
70
|
+
else
|
|
71
|
+
request(:areDue, cards: [card_ids]).first
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Gets intervals for cards.
|
|
76
|
+
#
|
|
77
|
+
# @param card_ids [Array<Integer>] Array of card IDs
|
|
78
|
+
# @param complete [Boolean] If true, returns all intervals
|
|
79
|
+
# @return [Array<Integer>, Array<Array<Integer>>] Intervals
|
|
80
|
+
def get_intervals(card_ids, complete: false)
|
|
81
|
+
request(:getIntervals, cards: card_ids, complete: complete)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Searches for cards matching a query.
|
|
85
|
+
#
|
|
86
|
+
# @param query [String] Anki search query string
|
|
87
|
+
# @return [Array<Integer>] Array of card IDs
|
|
88
|
+
def search_cards(query)
|
|
89
|
+
request(:findCards, query: query)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Converts card IDs to their parent note IDs.
|
|
93
|
+
#
|
|
94
|
+
# @param card_ids [Array<Integer>] Array of card IDs
|
|
95
|
+
# @return [Array<Integer>] Array of note IDs
|
|
96
|
+
def get_note_ids(card_ids)
|
|
97
|
+
request(:cardsToNotes, cards: card_ids)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Gets modification times for cards.
|
|
101
|
+
#
|
|
102
|
+
# @param card_ids [Array<Integer>] Array of card IDs
|
|
103
|
+
# @return [Array<Hash>] Array of objects with cardId and mod
|
|
104
|
+
def get_cards_mod_time(card_ids)
|
|
105
|
+
request(:cardsModTime, cards: card_ids)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Gets detailed information about cards.
|
|
109
|
+
#
|
|
110
|
+
# @param card_ids [Array<Integer>] Array of card IDs
|
|
111
|
+
# @return [Array<Hash>] Array of card objects
|
|
112
|
+
def get_cards(card_ids)
|
|
113
|
+
request(:cardsInfo, cards: card_ids)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Resets cards to "new" status.
|
|
117
|
+
#
|
|
118
|
+
# @param card_ids [Array<Integer>] Array of card IDs
|
|
119
|
+
# @return [nil]
|
|
120
|
+
def forget_cards(card_ids)
|
|
121
|
+
request(:forgetCards, cards: card_ids)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Makes cards enter "relearning" state.
|
|
125
|
+
#
|
|
126
|
+
# @param card_ids [Array<Integer>] Array of card IDs
|
|
127
|
+
# @return [nil]
|
|
128
|
+
def relearn_cards(card_ids)
|
|
129
|
+
request(:relearnCards, cards: card_ids)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Answers cards programmatically.
|
|
133
|
+
#
|
|
134
|
+
# @param answers [Array<Hash>] Array of { cardId:, ease: } (1=Again, 2=Hard, 3=Good, 4=Easy)
|
|
135
|
+
# @return [Array<Boolean>] Array indicating if each card exists
|
|
136
|
+
def answer_cards(answers)
|
|
137
|
+
request(:answerCards, answers: answers)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Sets due date for cards.
|
|
141
|
+
#
|
|
142
|
+
# @param card_ids [Array<Integer>] Array of card IDs
|
|
143
|
+
# @param days [String, Integer] Due date (0=today, 1!=tomorrow, 3-7=random range)
|
|
144
|
+
# @return [Boolean] true on success
|
|
145
|
+
def set_due_date(card_ids, days)
|
|
146
|
+
request(:setDueDate, cards: card_ids, days: days)
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'net/http'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'uri'
|
|
6
|
+
|
|
7
|
+
module AnkiConnect
|
|
8
|
+
# Main client class that includes all API modules and provides
|
|
9
|
+
# the core request mechanism.
|
|
10
|
+
class Client
|
|
11
|
+
include AnkiConnect::Client::Cards
|
|
12
|
+
include AnkiConnect::Client::Decks
|
|
13
|
+
include AnkiConnect::Client::Models
|
|
14
|
+
include AnkiConnect::Client::Notes
|
|
15
|
+
include AnkiConnect::Client::Media
|
|
16
|
+
include AnkiConnect::Client::Graphical
|
|
17
|
+
include AnkiConnect::Client::Statistics
|
|
18
|
+
include AnkiConnect::Client::Miscellaneous
|
|
19
|
+
|
|
20
|
+
# @return [String] AnkiConnect server host
|
|
21
|
+
attr_reader :host
|
|
22
|
+
# @return [Integer] AnkiConnect server port
|
|
23
|
+
attr_reader :port
|
|
24
|
+
# @return [String, nil] API key for authentication (if configured)
|
|
25
|
+
attr_reader :api_key
|
|
26
|
+
|
|
27
|
+
# Creates a new AnkiConnect client.
|
|
28
|
+
#
|
|
29
|
+
# @param host [String] AnkiConnect server host (default: "127.0.0.1")
|
|
30
|
+
# @param port [Integer] AnkiConnect server port (default: 8765)
|
|
31
|
+
# @param api_key [String, nil] Optional API key for authentication
|
|
32
|
+
def initialize(host: '127.0.0.1', port: 8765, api_key: nil)
|
|
33
|
+
@host = host
|
|
34
|
+
@port = port
|
|
35
|
+
@api_key = api_key
|
|
36
|
+
@uri = URI("http://#{host}:#{port}")
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Makes a request to the AnkiConnect API.
|
|
40
|
+
# This is the core method used by all API operations.
|
|
41
|
+
#
|
|
42
|
+
# @param action [Symbol] The API action to perform
|
|
43
|
+
# @param params [Hash] Parameters to send with the request
|
|
44
|
+
# @return [Object] The result from the API
|
|
45
|
+
# @raise [Error] If the API returns an error
|
|
46
|
+
def request(action, **params)
|
|
47
|
+
body = {
|
|
48
|
+
action: action,
|
|
49
|
+
version: API_VERSION,
|
|
50
|
+
params: params
|
|
51
|
+
}
|
|
52
|
+
body[:key] = @api_key if @api_key
|
|
53
|
+
|
|
54
|
+
response = Net::HTTP.post(
|
|
55
|
+
@uri,
|
|
56
|
+
body.to_json,
|
|
57
|
+
'Content-Type' => 'application/json'
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
result = JSON.parse(response.body)
|
|
61
|
+
|
|
62
|
+
raise Error, result['error'] if result['error']
|
|
63
|
+
|
|
64
|
+
result['result']
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
attr_reader :uri
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Error raised when the AnkiConnect API returns an error response.
|
|
73
|
+
class Error < StandardError; end
|
|
74
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AnkiConnect
|
|
4
|
+
class Client
|
|
5
|
+
# Methods to create, configure, and manage decks and their settings.
|
|
6
|
+
module Decks
|
|
7
|
+
# Gets complete list of deck names.
|
|
8
|
+
#
|
|
9
|
+
# @return [Array<String>] Array of deck name strings
|
|
10
|
+
def deck_names
|
|
11
|
+
request(:deckNames)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Gets deck names with their IDs.
|
|
15
|
+
#
|
|
16
|
+
# @return [Hash] Deck names mapped to IDs
|
|
17
|
+
def deck_names_and_ids
|
|
18
|
+
request(:deckNamesAndIds)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Gets deck membership for given cards.
|
|
22
|
+
#
|
|
23
|
+
# @param card_ids [Array<Integer>] Array of card IDs
|
|
24
|
+
# @return [Hash] Deck names mapped to arrays of card IDs
|
|
25
|
+
def get_decks_for_cards(card_ids)
|
|
26
|
+
request(:getDecks, cards: card_ids)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Creates a new empty deck.
|
|
30
|
+
#
|
|
31
|
+
# @param name [String] Deck name (use :: for hierarchy)
|
|
32
|
+
# @return [Integer] Deck ID
|
|
33
|
+
def create_deck(name)
|
|
34
|
+
request(:createDeck, deck: name)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Moves cards to a different deck.
|
|
38
|
+
#
|
|
39
|
+
# @param card_ids [Array<Integer>] Array of card IDs
|
|
40
|
+
# @param to [String] Target deck name
|
|
41
|
+
# @return [nil]
|
|
42
|
+
def move_cards(card_ids, to:)
|
|
43
|
+
request(:changeDeck, cards: card_ids, deck: to)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Deletes decks by name.
|
|
47
|
+
#
|
|
48
|
+
# @param names [Array<String>] Array of deck names
|
|
49
|
+
# @param cards_too [Boolean] Must be true to confirm deletion
|
|
50
|
+
# @return [nil]
|
|
51
|
+
def delete_decks(names, cards_too: true)
|
|
52
|
+
request(:deleteDecks, decks: names, cardsToo: cards_too)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Gets configuration for a deck.
|
|
56
|
+
#
|
|
57
|
+
# @param name [String] Deck name
|
|
58
|
+
# @return [Hash] Configuration object
|
|
59
|
+
def get_deck_config(name)
|
|
60
|
+
request(:getDeckConfig, deck: name)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Saves a deck configuration.
|
|
64
|
+
#
|
|
65
|
+
# @param config [Hash] Complete configuration object
|
|
66
|
+
# @return [Boolean] true on success
|
|
67
|
+
def save_deck_config(config)
|
|
68
|
+
request(:saveDeckConfig, config: config)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Sets configuration for decks.
|
|
72
|
+
#
|
|
73
|
+
# @param names [Array<String>] Array of deck names
|
|
74
|
+
# @param config_id [Integer] Configuration group ID
|
|
75
|
+
# @return [Boolean] true on success
|
|
76
|
+
def set_deck_config(names, config_id)
|
|
77
|
+
request(:setDeckConfigId, decks: names, configId: config_id)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Clones a deck configuration.
|
|
81
|
+
#
|
|
82
|
+
# @param name [String] Name for new config group
|
|
83
|
+
# @param clone_from [Integer, nil] Config ID to clone from
|
|
84
|
+
# @return [Integer, Boolean] New config ID, or false if source doesn't exist
|
|
85
|
+
def clone_deck_config(name, clone_from: nil)
|
|
86
|
+
params = { name: name }
|
|
87
|
+
params[:cloneFrom] = clone_from if clone_from
|
|
88
|
+
request(:cloneDeckConfigId, **params)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Removes a deck configuration.
|
|
92
|
+
#
|
|
93
|
+
# @param config_id [Integer] Configuration group ID
|
|
94
|
+
# @return [Boolean] true on success
|
|
95
|
+
def remove_deck_config(config_id)
|
|
96
|
+
request(:removeDeckConfigId, configId: config_id)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Gets statistics for decks.
|
|
100
|
+
#
|
|
101
|
+
# @param names [Array<String>] Array of deck names
|
|
102
|
+
# @return [Hash] Deck IDs mapped to stats objects
|
|
103
|
+
def get_deck_stats(names)
|
|
104
|
+
request(:getDeckStats, decks: names)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AnkiConnect
|
|
4
|
+
class Client
|
|
5
|
+
# Methods to interact with Anki's GUI windows and dialogs
|
|
6
|
+
# (card browser, review screen, editing interfaces).
|
|
7
|
+
module Graphical
|
|
8
|
+
# Opens Card Browser dialog and searches for query.
|
|
9
|
+
#
|
|
10
|
+
# @param query [String] Search query string
|
|
11
|
+
# @param reorder_cards [Hash, nil] (optional) Object with order (ascending/descending) and columnId
|
|
12
|
+
# @return [Array<Integer>] Array of card IDs found
|
|
13
|
+
def gui_browse(query, reorder_cards: nil)
|
|
14
|
+
params = { query: query }
|
|
15
|
+
params[:reorderCards] = reorder_cards if reorder_cards
|
|
16
|
+
request(:guiBrowse, **params)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Selects a card in the open Card Browser.
|
|
20
|
+
#
|
|
21
|
+
# @param card_id [Integer] Card ID
|
|
22
|
+
# @return [Boolean] true if browser is open, false otherwise
|
|
23
|
+
def gui_select_card(card_id)
|
|
24
|
+
request(:guiSelectCard, card: card_id)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Gets selected notes from open Card Browser.
|
|
28
|
+
#
|
|
29
|
+
# @return [Array<Integer>] Array of note IDs (empty if browser not open)
|
|
30
|
+
def gui_selected_notes
|
|
31
|
+
request(:guiSelectedNotes)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Opens Add Cards dialog with preset values.
|
|
35
|
+
# Multiple invocations close old window and reopen with new values.
|
|
36
|
+
#
|
|
37
|
+
# @param note [Hash] Note object with deckName, modelName, fields, tags, and optional audio/video/picture
|
|
38
|
+
# @return [Integer] Note ID that would be created if user confirms
|
|
39
|
+
def gui_add_cards(note)
|
|
40
|
+
request(:guiAddCards, note: note)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Opens Edit dialog for a note.
|
|
44
|
+
# Opens edit dialog with Preview, Browse, and navigation buttons.
|
|
45
|
+
#
|
|
46
|
+
# @param note_id [Integer] Note ID
|
|
47
|
+
# @return [nil]
|
|
48
|
+
def gui_edit_note(note_id)
|
|
49
|
+
request(:guiEditNote, note: note_id)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Sets fields/tags/deck/model in open Add Note dialog.
|
|
53
|
+
# Returns error if Add Note dialog not open. Deck/model always replace; fields/tags respect append flag.
|
|
54
|
+
#
|
|
55
|
+
# @param note [Hash] Note object with optional deckName, modelName, fields, tags
|
|
56
|
+
# @param append [Boolean] If true, appends to fields/tags; otherwise replaces (default: false)
|
|
57
|
+
# @return [Boolean] true on success
|
|
58
|
+
def gui_add_note_set_data(note, append: false)
|
|
59
|
+
request(:guiAddNoteSetData, note: note, append: append)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Gets information about current card in review.
|
|
63
|
+
#
|
|
64
|
+
# @return [Hash, nil] Object with card info, or nil if not in review mode
|
|
65
|
+
def gui_current_card
|
|
66
|
+
request(:guiCurrentCard)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Starts/resets timer for current card.
|
|
70
|
+
# Useful for accurate time tracking when displaying cards via API.
|
|
71
|
+
#
|
|
72
|
+
# @return [Boolean] true
|
|
73
|
+
def gui_start_card_timer
|
|
74
|
+
request(:guiStartCardTimer)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Shows question side of current card.
|
|
78
|
+
#
|
|
79
|
+
# @return [Boolean] true if in review mode, false otherwise
|
|
80
|
+
def gui_show_question
|
|
81
|
+
request(:guiShowQuestion)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Shows answer side of current card.
|
|
85
|
+
#
|
|
86
|
+
# @return [Boolean] true if in review mode, false otherwise
|
|
87
|
+
def gui_show_answer
|
|
88
|
+
request(:guiShowAnswer)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Answers the current card.
|
|
92
|
+
# Answer must be displayed before answering.
|
|
93
|
+
#
|
|
94
|
+
# @param ease [Integer] Answer button (1-4)
|
|
95
|
+
# @return [Boolean] true on success, false otherwise
|
|
96
|
+
def gui_answer_card(ease)
|
|
97
|
+
request(:guiAnswerCard, ease: ease)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Undoes last action/card.
|
|
101
|
+
#
|
|
102
|
+
# @return [Boolean] true on success, false otherwise
|
|
103
|
+
def gui_undo
|
|
104
|
+
request(:guiUndo)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Opens Deck Overview dialog for a deck.
|
|
108
|
+
#
|
|
109
|
+
# @param name [String] Deck name
|
|
110
|
+
# @return [Boolean] true on success, false otherwise
|
|
111
|
+
def gui_deck_overview(name)
|
|
112
|
+
request(:guiDeckOverview, name: name)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Opens Deck Browser dialog.
|
|
116
|
+
#
|
|
117
|
+
# @return [nil]
|
|
118
|
+
def gui_deck_browser
|
|
119
|
+
request(:guiDeckBrowser)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Starts review for a deck.
|
|
123
|
+
#
|
|
124
|
+
# @param name [String] Deck name
|
|
125
|
+
# @return [Boolean] true on success, false otherwise
|
|
126
|
+
def gui_deck_review(name)
|
|
127
|
+
request(:guiDeckReview, name: name)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Opens Import dialog with optional file path.
|
|
131
|
+
# Opens file dialog if no path provided. Forward slashes required on Windows. Anki 2.1.52+ only.
|
|
132
|
+
#
|
|
133
|
+
# @param path [String, nil] File path to import (optional)
|
|
134
|
+
# @return [nil]
|
|
135
|
+
def gui_import_file(path: nil)
|
|
136
|
+
params = {}
|
|
137
|
+
params[:path] = path if path
|
|
138
|
+
request(:guiImportFile, **params)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Schedules graceful Anki shutdown.
|
|
142
|
+
# Asynchronous - returns immediately without waiting for termination.
|
|
143
|
+
#
|
|
144
|
+
# @return [nil]
|
|
145
|
+
def gui_exit_anki
|
|
146
|
+
request(:guiExitAnki)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Requests database check.
|
|
150
|
+
# Returns immediately without waiting for check to complete.
|
|
151
|
+
#
|
|
152
|
+
# @return [Boolean] true (always)
|
|
153
|
+
def gui_check_database
|
|
154
|
+
request(:guiCheckDatabase)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Plays audio for current card side.
|
|
158
|
+
#
|
|
159
|
+
# @return [Boolean] true on success, false otherwise
|
|
160
|
+
def gui_play_audio
|
|
161
|
+
request(:guiPlayAudio)
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AnkiConnect
|
|
4
|
+
class Client
|
|
5
|
+
# Methods to store, retrieve, and manage media files.
|
|
6
|
+
module Media
|
|
7
|
+
# Stores a file in the media folder.
|
|
8
|
+
#
|
|
9
|
+
# @param filename [String] File name (prefix with _ to prevent auto-deletion)
|
|
10
|
+
# @param data [String, nil] Base64-encoded contents
|
|
11
|
+
# @param path [String, nil] Absolute file path
|
|
12
|
+
# @param url [String, nil] URL to download from
|
|
13
|
+
# @param overwrite [Boolean] If true, overwrites existing file
|
|
14
|
+
# @return [String] Filename (possibly modified if overwrite=false)
|
|
15
|
+
def store_media(filename, data: nil, path: nil, url: nil, overwrite: true)
|
|
16
|
+
params = { filename: filename, deleteExisting: overwrite }
|
|
17
|
+
params[:data] = data if data
|
|
18
|
+
params[:path] = path if path
|
|
19
|
+
params[:url] = url if url
|
|
20
|
+
request(:storeMediaFile, **params)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Retrieves a media file's contents.
|
|
24
|
+
#
|
|
25
|
+
# @param filename [String] File name
|
|
26
|
+
# @return [String, Boolean] Base64-encoded contents, or false if not found
|
|
27
|
+
def retrieve_media(filename)
|
|
28
|
+
request(:retrieveMediaFile, filename: filename)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Lists media files matching a pattern.
|
|
32
|
+
#
|
|
33
|
+
# @param pattern [String] Glob pattern
|
|
34
|
+
# @return [Array<String>] Array of filenames
|
|
35
|
+
def list_media(pattern: '*')
|
|
36
|
+
request(:getMediaFilesNames, pattern: pattern)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Gets the media folder path.
|
|
40
|
+
#
|
|
41
|
+
# @return [String] Absolute path
|
|
42
|
+
def media_dir_path
|
|
43
|
+
request(:getMediaDirPath)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Deletes a media file.
|
|
47
|
+
#
|
|
48
|
+
# @param filename [String] File name
|
|
49
|
+
# @return [nil]
|
|
50
|
+
def delete_media(filename)
|
|
51
|
+
request(:deleteMediaFile, filename: filename)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AnkiConnect
|
|
4
|
+
class Client
|
|
5
|
+
# Methods for API permissions, version checking, profile management,
|
|
6
|
+
# synchronization, and import/export.
|
|
7
|
+
module Miscellaneous
|
|
8
|
+
# Requests API permission (first call to establish trust).
|
|
9
|
+
# Only method accepting requests from any origin. Shows popup for untrusted origins.
|
|
10
|
+
#
|
|
11
|
+
# @return [Hash] Object with permission (granted/denied), and optionally requireApiKey and version
|
|
12
|
+
def request_permission
|
|
13
|
+
request(:requestPermission)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Gets AnkiConnect API version.
|
|
17
|
+
#
|
|
18
|
+
# @return [Integer] Version number (currently 6)
|
|
19
|
+
def version
|
|
20
|
+
request(:version)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Gets information about available APIs.
|
|
24
|
+
#
|
|
25
|
+
# @param scopes [Array<String>] Array of scope names (currently only "actions" supported)
|
|
26
|
+
# @param actions [Array<String>, nil] null for all actions, or array of action names to check (optional)
|
|
27
|
+
# @return [Hash] Object with scopes used and available actions
|
|
28
|
+
def api_reflect(scopes, actions: nil)
|
|
29
|
+
params = { scopes: scopes }
|
|
30
|
+
params[:actions] = actions if actions
|
|
31
|
+
request(:apiReflect, **params)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Synchronizes local collection with AnkiWeb.
|
|
35
|
+
#
|
|
36
|
+
# @return [nil]
|
|
37
|
+
def sync
|
|
38
|
+
request(:sync)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Retrieves list of profiles.
|
|
42
|
+
#
|
|
43
|
+
# @return [Array<String>] Array of profile names
|
|
44
|
+
def profiles
|
|
45
|
+
request(:getProfiles)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Gets the active profile.
|
|
49
|
+
#
|
|
50
|
+
# @return [String] Profile name string
|
|
51
|
+
def active_profile
|
|
52
|
+
request(:getActiveProfile)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Switches to specified profile.
|
|
56
|
+
#
|
|
57
|
+
# @param name [String] Profile name
|
|
58
|
+
# @return [Boolean] true on success
|
|
59
|
+
def load_profile(name)
|
|
60
|
+
request(:loadProfile, name: name)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Performs multiple actions in one request.
|
|
64
|
+
#
|
|
65
|
+
# @param actions [Array<Hash>] Array of action objects (each with action, version, params)
|
|
66
|
+
# @return [Array] Array of responses in same order
|
|
67
|
+
def multi(actions)
|
|
68
|
+
request(:multi, actions: actions)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Exports deck to .apkg format.
|
|
72
|
+
#
|
|
73
|
+
# @param deck_name [String] Deck name
|
|
74
|
+
# @param path [String] Output file path
|
|
75
|
+
# @param include_scheduling [Boolean] Include scheduling data (default: false)
|
|
76
|
+
# @return [Boolean] true on success, false otherwise
|
|
77
|
+
def export_deck(deck_name, path, include_scheduling: false)
|
|
78
|
+
request(:exportPackage, deck: deck_name, path: path, includeSched: include_scheduling)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Imports .apkg file into collection.
|
|
82
|
+
#
|
|
83
|
+
# @param path [String] File path (relative to collection.media folder)
|
|
84
|
+
# @return [Boolean] true on success, false otherwise
|
|
85
|
+
def import_deck(path)
|
|
86
|
+
request(:importPackage, path: path)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Reloads all data from database.
|
|
90
|
+
#
|
|
91
|
+
# @return [nil]
|
|
92
|
+
def reload_collection
|
|
93
|
+
request(:reloadCollection)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AnkiConnect
|
|
4
|
+
class Client
|
|
5
|
+
# Methods to create and modify note types (models).
|
|
6
|
+
module Models
|
|
7
|
+
# Gets complete list of model names.
|
|
8
|
+
#
|
|
9
|
+
# @return [Array<String>] Array of model name strings
|
|
10
|
+
def model_names
|
|
11
|
+
request(:modelNames)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Gets model names with their IDs.
|
|
15
|
+
#
|
|
16
|
+
# @return [Hash] Model names mapped to IDs
|
|
17
|
+
def model_names_and_ids
|
|
18
|
+
request(:modelNamesAndIds)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Gets models by ID.
|
|
22
|
+
#
|
|
23
|
+
# @param ids [Array<Integer>] Array of model IDs
|
|
24
|
+
# @return [Array<Hash>] Array of model objects
|
|
25
|
+
def get_models_by_id(ids)
|
|
26
|
+
request(:findModelsById, modelIds: ids)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Gets models by name.
|
|
30
|
+
#
|
|
31
|
+
# @param names [Array<String>] Array of model names
|
|
32
|
+
# @return [Array<Hash>] Array of model objects
|
|
33
|
+
def get_models_by_name(names)
|
|
34
|
+
request(:findModelsByName, modelNames: names)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Gets field names for a model.
|
|
38
|
+
#
|
|
39
|
+
# @param model_name [String] Model name
|
|
40
|
+
# @return [Array<String>] Array of field names in order
|
|
41
|
+
def get_field_names(model_name)
|
|
42
|
+
request(:modelFieldNames, modelName: model_name)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Gets field descriptions for a model.
|
|
46
|
+
#
|
|
47
|
+
# @param model_name [String] Model name
|
|
48
|
+
# @return [Array<String>] Array of description strings
|
|
49
|
+
def get_field_descriptions(model_name)
|
|
50
|
+
request(:modelFieldDescriptions, modelName: model_name)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Gets field fonts and sizes for a model.
|
|
54
|
+
#
|
|
55
|
+
# @param model_name [String] Model name
|
|
56
|
+
# @return [Hash] Field names mapped to { font:, size: }
|
|
57
|
+
def get_field_fonts(model_name)
|
|
58
|
+
request(:modelFieldFonts, modelName: model_name)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Gets fields used on templates.
|
|
62
|
+
#
|
|
63
|
+
# @param model_name [String] Model name
|
|
64
|
+
# @return [Hash] Template names mapped to [questionFields, answerFields]
|
|
65
|
+
def get_fields_on_templates(model_name)
|
|
66
|
+
request(:modelFieldsOnTemplates, modelName: model_name)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Creates a new model.
|
|
70
|
+
#
|
|
71
|
+
# @param name [String] Model name
|
|
72
|
+
# @param fields [Array<String>] Field names in order
|
|
73
|
+
# @param templates [Array<Hash>] Template objects with Name, Front, Back
|
|
74
|
+
# @param css [String, nil] CSS styling
|
|
75
|
+
# @param is_cloze [Boolean] true for cloze type
|
|
76
|
+
# @return [Hash] Complete model object
|
|
77
|
+
def create_model(name:, fields:, templates:, css: nil, is_cloze: false)
|
|
78
|
+
params = { modelName: name, inOrderFields: fields, cardTemplates: templates, isCloze: is_cloze }
|
|
79
|
+
params[:css] = css if css
|
|
80
|
+
request(:createModel, **params)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Gets templates for a model.
|
|
84
|
+
#
|
|
85
|
+
# @param model_name [String] Model name
|
|
86
|
+
# @return [Hash] Template names mapped to { Front:, Back: }
|
|
87
|
+
def get_templates(model_name)
|
|
88
|
+
request(:modelTemplates, modelName: model_name)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Gets CSS styling for a model.
|
|
92
|
+
#
|
|
93
|
+
# @param model_name [String] Model name
|
|
94
|
+
# @return [Hash] Object with css property
|
|
95
|
+
def get_styling(model_name)
|
|
96
|
+
request(:modelStyling, modelName: model_name)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Updates a model's templates and/or CSS.
|
|
100
|
+
#
|
|
101
|
+
# @param name [String] Model name
|
|
102
|
+
# @param templates [Hash, nil] Template names mapped to Front/Back
|
|
103
|
+
# @param css [String, nil] CSS styling
|
|
104
|
+
# @return [nil]
|
|
105
|
+
def update_model(name, templates: nil, css: nil)
|
|
106
|
+
request(:updateModelTemplates, model: { name: name, templates: templates }) if templates
|
|
107
|
+
return unless css
|
|
108
|
+
|
|
109
|
+
request(:updateModelStyling, model: { name: name, css: css })
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Find and replace in model templates/CSS.
|
|
113
|
+
#
|
|
114
|
+
# @param model_name [String] Model name
|
|
115
|
+
# @param find [String] Text to find
|
|
116
|
+
# @param replace [String] Replacement text
|
|
117
|
+
# @param front [Boolean] Search front templates
|
|
118
|
+
# @param back [Boolean] Search back templates
|
|
119
|
+
# @param css [Boolean] Search CSS
|
|
120
|
+
# @return [Integer] Number of replacements made
|
|
121
|
+
def find_and_replace_in_model(model_name:, find:, replace:, front: true, back: true, css: true)
|
|
122
|
+
request(:findAndReplaceInModels, model: {
|
|
123
|
+
modelName: model_name, findText: find, replaceText: replace,
|
|
124
|
+
front: front, back: back, css: css
|
|
125
|
+
})
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Renames a template.
|
|
129
|
+
#
|
|
130
|
+
# @param model_name [String] Model name
|
|
131
|
+
# @param from [String] Current template name
|
|
132
|
+
# @param to [String] New template name
|
|
133
|
+
# @return [nil]
|
|
134
|
+
def rename_template(model_name, from:, to:)
|
|
135
|
+
request(:modelTemplateRename, modelName: model_name, oldTemplateName: from, newTemplateName: to)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Moves a template to a new position.
|
|
139
|
+
#
|
|
140
|
+
# @param model_name [String] Model name
|
|
141
|
+
# @param template_name [String] Template name
|
|
142
|
+
# @param index [Integer] New position (0-based)
|
|
143
|
+
# @return [nil]
|
|
144
|
+
def reposition_template(model_name, template_name, index)
|
|
145
|
+
request(:modelTemplateReposition, modelName: model_name, templateName: template_name, index: index)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Adds a template to a model.
|
|
149
|
+
#
|
|
150
|
+
# @param model_name [String] Model name
|
|
151
|
+
# @param template [Hash] Template with Name, Front, Back
|
|
152
|
+
# @return [nil]
|
|
153
|
+
def add_template(model_name, template)
|
|
154
|
+
request(:modelTemplateAdd, modelName: model_name, template: template)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Removes a template from a model.
|
|
158
|
+
#
|
|
159
|
+
# @param model_name [String] Model name
|
|
160
|
+
# @param template_name [String] Template name
|
|
161
|
+
# @return [nil]
|
|
162
|
+
def remove_template(model_name, template_name)
|
|
163
|
+
request(:modelTemplateRemove, modelName: model_name, templateName: template_name)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Renames a field.
|
|
167
|
+
#
|
|
168
|
+
# @param model_name [String] Model name
|
|
169
|
+
# @param from [String] Current field name
|
|
170
|
+
# @param to [String] New field name
|
|
171
|
+
# @return [nil]
|
|
172
|
+
def rename_field(model_name, from:, to:)
|
|
173
|
+
request(:modelFieldRename, modelName: model_name, oldFieldName: from, newFieldName: to)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Moves a field to a new position.
|
|
177
|
+
#
|
|
178
|
+
# @param model_name [String] Model name
|
|
179
|
+
# @param field_name [String] Field name
|
|
180
|
+
# @param index [Integer] New position (0-based)
|
|
181
|
+
# @return [nil]
|
|
182
|
+
def reposition_field(model_name, field_name, index)
|
|
183
|
+
request(:modelFieldReposition, modelName: model_name, fieldName: field_name, index: index)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Adds a field to a model.
|
|
187
|
+
#
|
|
188
|
+
# @param model_name [String] Model name
|
|
189
|
+
# @param field_name [String] Field name
|
|
190
|
+
# @param index [Integer, nil] Position (defaults to end)
|
|
191
|
+
# @return [nil]
|
|
192
|
+
def add_field(model_name, field_name, index: nil)
|
|
193
|
+
params = { modelName: model_name, fieldName: field_name }
|
|
194
|
+
params[:index] = index if index
|
|
195
|
+
request(:modelFieldAdd, **params)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Removes a field from a model.
|
|
199
|
+
#
|
|
200
|
+
# @param model_name [String] Model name
|
|
201
|
+
# @param field_name [String] Field name
|
|
202
|
+
# @return [nil]
|
|
203
|
+
def remove_field(model_name, field_name)
|
|
204
|
+
request(:modelFieldRemove, modelName: model_name, fieldName: field_name)
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Sets font for a field.
|
|
208
|
+
#
|
|
209
|
+
# @param model_name [String] Model name
|
|
210
|
+
# @param field_name [String] Field name
|
|
211
|
+
# @param font [String] Font name
|
|
212
|
+
# @return [nil]
|
|
213
|
+
def set_field_font(model_name, field_name, font)
|
|
214
|
+
request(:modelFieldSetFont, modelName: model_name, fieldName: field_name, font: font)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# Sets font size for a field.
|
|
218
|
+
#
|
|
219
|
+
# @param model_name [String] Model name
|
|
220
|
+
# @param field_name [String] Field name
|
|
221
|
+
# @param size [Integer] Font size
|
|
222
|
+
# @return [nil]
|
|
223
|
+
def set_field_font_size(model_name, field_name, size)
|
|
224
|
+
request(:modelFieldSetFontSize, modelName: model_name, fieldName: field_name, fontSize: size)
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# Sets description for a field.
|
|
228
|
+
#
|
|
229
|
+
# @param model_name [String] Model name
|
|
230
|
+
# @param field_name [String] Field name
|
|
231
|
+
# @param description [String] Description text
|
|
232
|
+
# @return [Boolean] true on success
|
|
233
|
+
def set_field_description(model_name, field_name, description)
|
|
234
|
+
request(:modelFieldSetDescription, modelName: model_name, fieldName: field_name, description: description)
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
end
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AnkiConnect
|
|
4
|
+
class Client
|
|
5
|
+
# Methods to create, update, query, and manage notes (which generate cards).
|
|
6
|
+
module Notes
|
|
7
|
+
# Creates a new note.
|
|
8
|
+
#
|
|
9
|
+
# @param deck_name [String] Target deck
|
|
10
|
+
# @param model_name [String] Note type
|
|
11
|
+
# @param fields [Hash] Field names to values
|
|
12
|
+
# @param tags [Array<String>] Tags (optional)
|
|
13
|
+
# @param media [Hash, nil] Media to add (audio:, video:, picture: arrays)
|
|
14
|
+
# @param options [Hash, nil] Options (allowDuplicate, duplicateScope, etc.)
|
|
15
|
+
# @return [Integer, nil] Note ID on success, nil on failure
|
|
16
|
+
def add_note(deck_name:, model_name:, fields:, tags: [], media: nil, options: nil)
|
|
17
|
+
note = { deckName: deck_name, modelName: model_name, fields: fields, tags: tags }
|
|
18
|
+
note.merge!(media) if media
|
|
19
|
+
note[:options] = options if options
|
|
20
|
+
request(:addNote, note: note)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Creates multiple notes.
|
|
24
|
+
#
|
|
25
|
+
# @param notes [Array<Hash>] Array of note hashes (same keys as add_note)
|
|
26
|
+
# @return [Array<Integer, nil>] Array of note IDs (nil for failed notes)
|
|
27
|
+
def add_notes(notes)
|
|
28
|
+
request(:addNotes, notes: notes)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Checks if notes can be added.
|
|
32
|
+
#
|
|
33
|
+
# @param notes [Array<Hash>] Array of candidate note objects
|
|
34
|
+
# @param details [Boolean] If true, returns error details (default: false)
|
|
35
|
+
# @return [Array<Boolean>, Array<Hash>] Array of booleans, or hashes with canAdd and error if details=true
|
|
36
|
+
def can_add_notes(notes, details: false)
|
|
37
|
+
if details
|
|
38
|
+
request(:canAddNotesWithErrorDetail, notes: notes)
|
|
39
|
+
else
|
|
40
|
+
request(:canAddNotes, notes: notes)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Updates a note's fields, tags, or media.
|
|
45
|
+
#
|
|
46
|
+
# @param id [Integer] Note ID
|
|
47
|
+
# @param fields [Hash, nil] Field names to new values
|
|
48
|
+
# @param tags [Array<String>, nil] New tags (replaces existing)
|
|
49
|
+
# @param media [Hash, nil] Media to add (audio:, video:, picture: arrays)
|
|
50
|
+
# @return [nil]
|
|
51
|
+
def update_note(id, fields: nil, tags: nil, media: nil)
|
|
52
|
+
note = { id: id }
|
|
53
|
+
note[:fields] = fields if fields
|
|
54
|
+
note[:tags] = tags if tags
|
|
55
|
+
note.merge!(media) if media
|
|
56
|
+
request(:updateNote, note: note)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Changes a note's model type.
|
|
60
|
+
#
|
|
61
|
+
# @param id [Integer] Note ID
|
|
62
|
+
# @param model_name [String] New model name
|
|
63
|
+
# @param fields [Hash] New field values
|
|
64
|
+
# @param tags [Array<String>] New tags
|
|
65
|
+
# @return [nil]
|
|
66
|
+
def change_note_model(id, model_name:, fields:, tags:)
|
|
67
|
+
request(:updateNoteModel, note: { id: id, modelName: model_name, fields: fields, tags: tags })
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Gets tags for a note.
|
|
71
|
+
#
|
|
72
|
+
# @param note_id [Integer] Note ID
|
|
73
|
+
# @return [Array<String>] Array of tag strings
|
|
74
|
+
def get_note_tags(note_id)
|
|
75
|
+
request(:getNoteTags, note: note_id)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Adds tags to notes.
|
|
79
|
+
#
|
|
80
|
+
# @param note_ids [Array<Integer>] Array of note IDs
|
|
81
|
+
# @param tags [String, Array<String>] Tag(s) to add
|
|
82
|
+
# @return [nil]
|
|
83
|
+
def add_tags(note_ids, tags)
|
|
84
|
+
request(:addTags, notes: note_ids, tags: tags)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Removes tags from notes.
|
|
88
|
+
#
|
|
89
|
+
# @param note_ids [Array<Integer>] Array of note IDs
|
|
90
|
+
# @param tags [String, Array<String>] Tag(s) to remove
|
|
91
|
+
# @return [nil]
|
|
92
|
+
def remove_tags(note_ids, tags)
|
|
93
|
+
request(:removeTags, notes: note_ids, tags: tags)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Gets all tags in collection.
|
|
97
|
+
#
|
|
98
|
+
# @return [Array<String>] Array of all tag strings
|
|
99
|
+
def all_tags
|
|
100
|
+
request(:getTags)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Removes unused tags from collection.
|
|
104
|
+
#
|
|
105
|
+
# @return [nil]
|
|
106
|
+
def clear_unused_tags
|
|
107
|
+
request(:clearUnusedTags)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Replaces a tag with another.
|
|
111
|
+
#
|
|
112
|
+
# @param from [String] Old tag
|
|
113
|
+
# @param to [String] New tag
|
|
114
|
+
# @param note_ids [Array<Integer>, nil] Specific notes, or nil for all notes
|
|
115
|
+
# @return [nil]
|
|
116
|
+
def replace_tag(from:, to:, note_ids: nil)
|
|
117
|
+
if note_ids
|
|
118
|
+
request(:replaceTags, notes: note_ids, tag_to_replace: from, replace_with_tag: to)
|
|
119
|
+
else
|
|
120
|
+
request(:replaceTagsInAllNotes, tag_to_replace: from, replace_with_tag: to)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Searches for notes matching a query.
|
|
125
|
+
#
|
|
126
|
+
# @param query [String] Search query string
|
|
127
|
+
# @return [Array<Integer>] Array of note IDs
|
|
128
|
+
def search_notes(query)
|
|
129
|
+
request(:findNotes, query: query)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Gets detailed information about notes.
|
|
133
|
+
#
|
|
134
|
+
# @param note_ids [Array<Integer>, nil] Array of note IDs
|
|
135
|
+
# @param query [String, nil] Search query string
|
|
136
|
+
# @return [Array<Hash>] Array of note objects
|
|
137
|
+
def get_notes(note_ids: nil, query: nil)
|
|
138
|
+
params = {}
|
|
139
|
+
params[:notes] = note_ids if note_ids
|
|
140
|
+
params[:query] = query if query
|
|
141
|
+
request(:notesInfo, **params)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Gets modification times for notes.
|
|
145
|
+
#
|
|
146
|
+
# @param note_ids [Array<Integer>] Array of note IDs
|
|
147
|
+
# @return [Array<Hash>] Array of objects with noteId and mod
|
|
148
|
+
def get_notes_mod_time(note_ids)
|
|
149
|
+
request(:notesModTime, notes: note_ids)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Deletes notes and all associated cards.
|
|
153
|
+
#
|
|
154
|
+
# @param note_ids [Array<Integer>] Array of note IDs
|
|
155
|
+
# @return [nil]
|
|
156
|
+
def delete_notes(note_ids)
|
|
157
|
+
request(:deleteNotes, notes: note_ids)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Removes all empty notes.
|
|
161
|
+
#
|
|
162
|
+
# @return [nil]
|
|
163
|
+
def remove_empty_notes
|
|
164
|
+
request(:removeEmptyNotes)
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AnkiConnect
|
|
4
|
+
class Client
|
|
5
|
+
# Methods to query review counts, retrieve review history,
|
|
6
|
+
# and access collection statistics.
|
|
7
|
+
module Statistics
|
|
8
|
+
# Gets count of cards reviewed today.
|
|
9
|
+
# "Today" uses day start time as configured in Anki.
|
|
10
|
+
#
|
|
11
|
+
# @return [Integer] Count of cards reviewed today
|
|
12
|
+
def cards_reviewed_today
|
|
13
|
+
request(:getNumCardsReviewedToday)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Gets review counts by day.
|
|
17
|
+
#
|
|
18
|
+
# @return [Array<Array>] Array of [dateString, count] pairs
|
|
19
|
+
def cards_reviewed_by_day
|
|
20
|
+
request(:getNumCardsReviewedByDay)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Gets collection statistics report as HTML.
|
|
24
|
+
#
|
|
25
|
+
# @param whole_collection [Boolean] Whether to get stats for whole collection (default: true)
|
|
26
|
+
# @return [String] HTML string
|
|
27
|
+
def collection_stats_html(whole_collection: true)
|
|
28
|
+
request(:getCollectionStatsHTML, wholeCollection: whole_collection)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Gets all card reviews for a deck after a certain time.
|
|
32
|
+
#
|
|
33
|
+
# @param deck_name [String] Deck name
|
|
34
|
+
# @param after [Integer] Unix timestamp (reviews after this time, exclusive)
|
|
35
|
+
# @return [Array<Array>] Array of 9-tuples: (reviewTime, cardID, usn, buttonPressed, newInterval, previousInterval, newFactor, reviewDuration, reviewType)
|
|
36
|
+
def get_reviews(deck_name, after:)
|
|
37
|
+
request(:cardReviews, deck: deck_name, startID: after)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Gets all reviews for specific cards.
|
|
41
|
+
#
|
|
42
|
+
# @param card_ids [Array<Integer>] Array of card IDs
|
|
43
|
+
# @return [Hash] Dictionary mapping card IDs to arrays of review objects with id, usn, ease, ivl, lastIvl, factor, time, type
|
|
44
|
+
def get_reviews_for_cards(card_ids)
|
|
45
|
+
request(:getReviewsOfCards, cards: card_ids)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Gets unix time of latest review for a deck.
|
|
49
|
+
#
|
|
50
|
+
# @param deck_name [String] Deck name
|
|
51
|
+
# @return [Integer] Unix timestamp, or 0 if no reviews
|
|
52
|
+
def latest_review_time(deck_name)
|
|
53
|
+
request(:getLatestReviewID, deck: deck_name)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Inserts review records into database.
|
|
57
|
+
#
|
|
58
|
+
# @param reviews [Array<Array>] Array of 9-tuples (same format as get_reviews output)
|
|
59
|
+
# @return [nil]
|
|
60
|
+
def insert_reviews(reviews)
|
|
61
|
+
request(:insertReviews, reviews: reviews)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
data/lib/anki_connect.rb
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'anki_connect/version'
|
|
4
|
+
require_relative 'anki_connect/cards'
|
|
5
|
+
require_relative 'anki_connect/decks'
|
|
6
|
+
require_relative 'anki_connect/models'
|
|
7
|
+
require_relative 'anki_connect/notes'
|
|
8
|
+
require_relative 'anki_connect/media'
|
|
9
|
+
require_relative 'anki_connect/graphical'
|
|
10
|
+
require_relative 'anki_connect/statistics'
|
|
11
|
+
require_relative 'anki_connect/miscellaneous'
|
|
12
|
+
require_relative 'anki_connect/client'
|
|
13
|
+
|
|
14
|
+
# Ruby client for AnkiConnect, enabling external applications to interact
|
|
15
|
+
# with Anki through HTTP.
|
|
16
|
+
#
|
|
17
|
+
# @example Basic usage
|
|
18
|
+
# client = AnkiConnect::Client.new
|
|
19
|
+
# decks = client.deck_names
|
|
20
|
+
# cards = client.search_cards("deck:Default")
|
|
21
|
+
#
|
|
22
|
+
# @see https://foosoft.net/projects/anki-connect/ AnkiConnect Documentation
|
|
23
|
+
module AnkiConnect
|
|
24
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: anki_connect
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- vadik49b
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
12
|
+
description: AnkiConnect provides a simple HTTP API to communicate with Anki. This
|
|
13
|
+
Ruby gem is a wrapper around that API.
|
|
14
|
+
email:
|
|
15
|
+
- vadim@boltach.com
|
|
16
|
+
executables: []
|
|
17
|
+
extensions: []
|
|
18
|
+
extra_rdoc_files: []
|
|
19
|
+
files:
|
|
20
|
+
- LICENSE
|
|
21
|
+
- README.md
|
|
22
|
+
- lib/anki_connect.rb
|
|
23
|
+
- lib/anki_connect/cards.rb
|
|
24
|
+
- lib/anki_connect/client.rb
|
|
25
|
+
- lib/anki_connect/decks.rb
|
|
26
|
+
- lib/anki_connect/graphical.rb
|
|
27
|
+
- lib/anki_connect/media.rb
|
|
28
|
+
- lib/anki_connect/miscellaneous.rb
|
|
29
|
+
- lib/anki_connect/models.rb
|
|
30
|
+
- lib/anki_connect/notes.rb
|
|
31
|
+
- lib/anki_connect/statistics.rb
|
|
32
|
+
- lib/anki_connect/version.rb
|
|
33
|
+
homepage: https://github.com/vadik49b/anki-connect.rb
|
|
34
|
+
licenses:
|
|
35
|
+
- MIT
|
|
36
|
+
metadata:
|
|
37
|
+
homepage_uri: https://github.com/vadik49b/anki-connect.rb
|
|
38
|
+
source_code_uri: https://github.com/vadik49b/anki-connect.rb
|
|
39
|
+
rdoc_options: []
|
|
40
|
+
require_paths:
|
|
41
|
+
- lib
|
|
42
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: 3.4.0
|
|
47
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
48
|
+
requirements:
|
|
49
|
+
- - ">="
|
|
50
|
+
- !ruby/object:Gem::Version
|
|
51
|
+
version: '0'
|
|
52
|
+
requirements: []
|
|
53
|
+
rubygems_version: 3.6.9
|
|
54
|
+
specification_version: 4
|
|
55
|
+
summary: Ruby wrapper for the Anki-Connect HTTP API
|
|
56
|
+
test_files: []
|