Compare commits
12 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
8bfb4049dc | ||
|
6eea9f9db4 | ||
|
c89dbf2062 | ||
|
ba67c52d9b | ||
|
cc027f8ab8 | ||
|
dc96cdc76c | ||
|
6a2cc2dec1 | ||
|
172d1fb20f | ||
|
0514569621 | ||
|
81c39a2e42 | ||
|
f52e0c2e24 | ||
|
aab9346deb |
139
README.md
139
README.md
@ -2132,6 +2132,33 @@ Search parameters are passed to Anki, check the docs for more information: https
|
|||||||
```
|
```
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
#### `getActiveProfile`
|
||||||
|
|
||||||
|
* Retrieve the active profile.
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><i>Sample request:</i></summary>
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"action": "getActiveProfile",
|
||||||
|
"version": 6
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><i>Sample result:</i></summary>
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"result": "User 1",
|
||||||
|
"error": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
#### `loadProfile`
|
#### `loadProfile`
|
||||||
|
|
||||||
* Selects the profile specified in request.
|
* Selects the profile specified in request.
|
||||||
@ -3592,55 +3619,36 @@ Search parameters are passed to Anki, check the docs for more information: https
|
|||||||
#### `addNotes`
|
#### `addNotes`
|
||||||
|
|
||||||
* Creates multiple notes using the given deck and model, with the provided field values and tags. Returns an array of
|
* Creates multiple notes using the given deck and model, with the provided field values and tags. Returns an array of
|
||||||
identifiers of the created notes (notes that could not be created will have a `null` identifier). Please see the
|
identifiers of the created notes. In the event of any errors, all errors are gathered and returned.
|
||||||
documentation for `addNote` for an explanation of objects in the `notes` array.
|
* Please see the documentation for `addNote` for an explanation of objects in the `notes` array.
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary><i>Sample request:</i></summary>
|
<summary><i>Sample request:</i></summary>
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"action": "addNotes",
|
"action":"addNotes",
|
||||||
"version": 6,
|
"version":6,
|
||||||
"params": {
|
"params":{
|
||||||
"notes": [
|
"notes":[
|
||||||
{
|
{
|
||||||
"deckName": "Default",
|
"deckName":"College::PluginDev",
|
||||||
"modelName": "Basic",
|
"modelName":"non_existent_model",
|
||||||
"fields": {
|
"fields":{
|
||||||
"Front": "front content",
|
"Front":"front",
|
||||||
"Back": "back content"
|
"Back":"bak"
|
||||||
},
|
|
||||||
"tags": [
|
|
||||||
"yomichan"
|
|
||||||
],
|
|
||||||
"audio": [{
|
|
||||||
"url": "https://assets.languagepod101.com/dictionary/japanese/audiomp3.php?kanji=猫&kana=ねこ",
|
|
||||||
"filename": "yomichan_ねこ_猫.mp3",
|
|
||||||
"skipHash": "7e2c2f954ef6051373ba916f000168dc",
|
|
||||||
"fields": [
|
|
||||||
"Front"
|
|
||||||
]
|
|
||||||
}],
|
|
||||||
"video": [{
|
|
||||||
"url": "https://cdn.videvo.net/videvo_files/video/free/2015-06/small_watermarked/Contador_Glam_preview.mp4",
|
|
||||||
"filename": "countdown.mp4",
|
|
||||||
"skipHash": "4117e8aab0d37534d9c8eac362388bbe",
|
|
||||||
"fields": [
|
|
||||||
"Back"
|
|
||||||
]
|
|
||||||
}],
|
|
||||||
"picture": [{
|
|
||||||
"url": "https://upload.wikimedia.org/wikipedia/commons/thumb/c/c7/A_black_cat_named_Tilly.jpg/220px-A_black_cat_named_Tilly.jpg",
|
|
||||||
"filename": "black_cat.jpg",
|
|
||||||
"skipHash": "8d6e4646dfae812bf39651b59d7429ce",
|
|
||||||
"fields": [
|
|
||||||
"Back"
|
|
||||||
]
|
|
||||||
}]
|
|
||||||
}
|
}
|
||||||
]
|
},
|
||||||
}
|
{
|
||||||
|
"deckName":"College::PluginDev",
|
||||||
|
"modelName":"Basic",
|
||||||
|
"fields":{
|
||||||
|
"Front":"front",
|
||||||
|
"Back":"bak"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
</details>
|
</details>
|
||||||
@ -3650,8 +3658,8 @@ Search parameters are passed to Anki, check the docs for more information: https
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"result": [1496198395707, null],
|
"result":null,
|
||||||
"error": null
|
"error":"['model was not found: non_existent_model']"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
</details>
|
</details>
|
||||||
@ -4166,8 +4174,8 @@ Search parameters are passed to Anki, check the docs for more information: https
|
|||||||
|
|
||||||
#### `notesInfo`
|
#### `notesInfo`
|
||||||
|
|
||||||
* Returns a list of objects containing for each note ID the note fields, tags, note type and the cards belonging to
|
* Returns a list of objects containing for each note ID the note fields, tags, note type, modification time,the cards belonging to
|
||||||
the note.
|
the note and the profile where the note was created.
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary><i>Sample request:</i></summary>
|
<summary><i>Sample request:</i></summary>
|
||||||
@ -4191,12 +4199,49 @@ Search parameters are passed to Anki, check the docs for more information: https
|
|||||||
"result": [
|
"result": [
|
||||||
{
|
{
|
||||||
"noteId":1502298033753,
|
"noteId":1502298033753,
|
||||||
|
"profile": "User_1",
|
||||||
"modelName": "Basic",
|
"modelName": "Basic",
|
||||||
"tags":["tag","another_tag"],
|
"tags":["tag","another_tag"],
|
||||||
"fields": {
|
"fields": {
|
||||||
"Front": {"value": "front content", "order": 0},
|
"Front": {"value": "front content", "order": 0},
|
||||||
"Back": {"value": "back content", "order": 1}
|
"Back": {"value": "back content", "order": 1}
|
||||||
}
|
},
|
||||||
|
"mod": 1718377864,
|
||||||
|
"cards": [1498938915662]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"error": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
s
|
||||||
|
#### `notesModTime`
|
||||||
|
|
||||||
|
* Returns a list of objects containings for each note ID the modification time.
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><i>Sample request:</i></summary>
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"action": "notesModTime",
|
||||||
|
"version": 6,
|
||||||
|
"params": {
|
||||||
|
"notes": [1502298033753]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><i>Sample result:</i></summary>
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"result": [
|
||||||
|
{
|
||||||
|
"noteId": 1498938915662,
|
||||||
|
"mod": 1629454092
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"error": null
|
"error": null
|
||||||
|
@ -15,10 +15,11 @@
|
|||||||
|
|
||||||
import aqt
|
import aqt
|
||||||
|
|
||||||
|
required_anki_version = (24, 6, 3)
|
||||||
anki_version = tuple(int(segment) for segment in aqt.appVersion.split("."))
|
anki_version = tuple(int(segment) for segment in aqt.appVersion.split("."))
|
||||||
|
|
||||||
if anki_version < (2, 1, 45):
|
if anki_version < required_anki_version:
|
||||||
raise Exception("Minimum Anki version supported: 2.1.45")
|
raise Exception(f"Minimum Anki version supported: {required_anki_version[0]}.{required_anki_version[1]}.{required_anki_version[2]}")
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
import glob
|
import glob
|
||||||
@ -310,7 +311,7 @@ class AnkiConnect:
|
|||||||
val = note.fields[0]
|
val = note.fields[0]
|
||||||
if not val.strip():
|
if not val.strip():
|
||||||
return 1
|
return 1
|
||||||
csum = anki.utils.fieldChecksum(val)
|
csum = anki.utils.field_checksum(val)
|
||||||
|
|
||||||
# Create dictionary of deck ids
|
# Create dictionary of deck ids
|
||||||
dids = None
|
dids = None
|
||||||
@ -358,13 +359,13 @@ class AnkiConnect:
|
|||||||
|
|
||||||
def getCard(self, card_id: int) -> Card:
|
def getCard(self, card_id: int) -> Card:
|
||||||
try:
|
try:
|
||||||
return self.collection().getCard(card_id)
|
return self.collection().get_card(card_id)
|
||||||
except NotFoundError:
|
except NotFoundError:
|
||||||
self.raiseNotFoundError('Card was not found: {}'.format(card_id))
|
self.raiseNotFoundError('Card was not found: {}'.format(card_id))
|
||||||
|
|
||||||
def getNote(self, note_id: int) -> Note:
|
def getNote(self, note_id: int) -> Note:
|
||||||
try:
|
try:
|
||||||
return self.collection().getNote(note_id)
|
return self.collection().get_note(note_id)
|
||||||
except NotFoundError:
|
except NotFoundError:
|
||||||
self.raiseNotFoundError('Note was not found: {}'.format(note_id))
|
self.raiseNotFoundError('Note was not found: {}'.format(note_id))
|
||||||
|
|
||||||
@ -457,6 +458,9 @@ class AnkiConnect:
|
|||||||
def getProfiles(self):
|
def getProfiles(self):
|
||||||
return self.window().pm.profiles()
|
return self.window().pm.profiles()
|
||||||
|
|
||||||
|
@util.api()
|
||||||
|
def getActiveProfile(self):
|
||||||
|
return self.window().pm.name
|
||||||
|
|
||||||
@util.api()
|
@util.api()
|
||||||
def loadProfile(self, name):
|
def loadProfile(self, name):
|
||||||
@ -488,7 +492,15 @@ class AnkiConnect:
|
|||||||
|
|
||||||
@util.api()
|
@util.api()
|
||||||
def sync(self):
|
def sync(self):
|
||||||
self.window().onSync()
|
mw = self.window()
|
||||||
|
auth = mw.pm.sync_auth()
|
||||||
|
if not auth:
|
||||||
|
raise Exception("sync: auth not configured")
|
||||||
|
out = mw.col.sync_collection(auth, mw.pm.media_syncing_enabled())
|
||||||
|
accepted_sync_statuses = [out.NO_CHANGES, out.NORMAL_SYNC]
|
||||||
|
if out.required not in accepted_sync_statuses:
|
||||||
|
raise Exception(f"Sync status {out.required} not one of {accepted_sync_statuses} - see SyncCollectionResponse.ChangesRequired for list of sync statuses: https://github.com/ankitects/anki/blob/e41c4573d789afe8b020fab5d9d1eede50c3fa3d/proto/anki/sync.proto#L57-L65")
|
||||||
|
mw.onSync()
|
||||||
|
|
||||||
|
|
||||||
@util.api()
|
@util.api()
|
||||||
@ -556,7 +568,7 @@ class AnkiConnect:
|
|||||||
self.startEditing()
|
self.startEditing()
|
||||||
|
|
||||||
did = self.collection().decks.id(deck)
|
did = self.collection().decks.id(deck)
|
||||||
mod = anki.utils.intTime()
|
mod = anki.utils.int_time()
|
||||||
usn = self.collection().usn()
|
usn = self.collection().usn()
|
||||||
|
|
||||||
# normal cards
|
# normal cards
|
||||||
@ -601,7 +613,7 @@ class AnkiConnect:
|
|||||||
collection = self.collection()
|
collection = self.collection()
|
||||||
|
|
||||||
config['id'] = str(config['id'])
|
config['id'] = str(config['id'])
|
||||||
config['mod'] = anki.utils.intTime()
|
config['mod'] = anki.utils.int_time()
|
||||||
config['usn'] = collection.usn()
|
config['usn'] = collection.usn()
|
||||||
if int(config['id']) not in [c['id'] for c in collection.decks.all_config()]:
|
if int(config['id']) not in [c['id'] for c in collection.decks.all_config()]:
|
||||||
return False
|
return False
|
||||||
@ -730,7 +742,6 @@ class AnkiConnect:
|
|||||||
nCardsAdded = collection.addNote(ankiNote)
|
nCardsAdded = collection.addNote(ankiNote)
|
||||||
if nCardsAdded < 1:
|
if nCardsAdded < 1:
|
||||||
raise Exception('The field values you have provided would make an empty question on all cards.')
|
raise Exception('The field values you have provided would make an empty question on all cards.')
|
||||||
collection.autosave()
|
|
||||||
|
|
||||||
return ankiNote.id
|
return ankiNote.id
|
||||||
|
|
||||||
@ -809,18 +820,9 @@ class AnkiConnect:
|
|||||||
if name in ankiNote:
|
if name in ankiNote:
|
||||||
ankiNote[name] = value
|
ankiNote[name] = value
|
||||||
|
|
||||||
audioObjectOrList = note.get('audio')
|
self.addMediaFromNote(ankiNote, note)
|
||||||
self.addMedia(ankiNote, audioObjectOrList, util.MediaType.Audio)
|
|
||||||
|
|
||||||
videoObjectOrList = note.get('video')
|
self.collection().update_note(ankiNote, skip_undo_entry=True);
|
||||||
self.addMedia(ankiNote, videoObjectOrList, util.MediaType.Video)
|
|
||||||
|
|
||||||
pictureObjectOrList = note.get('picture')
|
|
||||||
self.addMedia(ankiNote, pictureObjectOrList, util.MediaType.Picture)
|
|
||||||
|
|
||||||
ankiNote.flush()
|
|
||||||
|
|
||||||
self.collection().autosave()
|
|
||||||
|
|
||||||
|
|
||||||
@util.api()
|
@util.api()
|
||||||
@ -884,11 +886,8 @@ class AnkiConnect:
|
|||||||
# Update the tags
|
# Update the tags
|
||||||
anki_note.tags = new_tags
|
anki_note.tags = new_tags
|
||||||
|
|
||||||
# Flush changes to ensure they are saved
|
# Update note to ensure changes are saved
|
||||||
anki_note.flush()
|
collection.update_note(anki_note, skip_undo_entry=True);
|
||||||
|
|
||||||
# Save changes to the collection
|
|
||||||
collection.autosave()
|
|
||||||
|
|
||||||
@util.api()
|
@util.api()
|
||||||
def updateNoteTags(self, note, tags):
|
def updateNoteTags(self, note, tags):
|
||||||
@ -942,7 +941,7 @@ class AnkiConnect:
|
|||||||
if note.has_tag(tag_to_replace):
|
if note.has_tag(tag_to_replace):
|
||||||
note.remove_tag(tag_to_replace)
|
note.remove_tag(tag_to_replace)
|
||||||
note.add_tag(replace_with_tag)
|
note.add_tag(replace_with_tag)
|
||||||
note.flush()
|
self.collection().update_note(note, skip_undo_entry=True);
|
||||||
|
|
||||||
self.window().requireReset()
|
self.window().requireReset()
|
||||||
self.window().progress.finish()
|
self.window().progress.finish()
|
||||||
@ -959,7 +958,7 @@ class AnkiConnect:
|
|||||||
if note.has_tag(tag_to_replace):
|
if note.has_tag(tag_to_replace):
|
||||||
note.remove_tag(tag_to_replace)
|
note.remove_tag(tag_to_replace)
|
||||||
note.add_tag(replace_with_tag)
|
note.add_tag(replace_with_tag)
|
||||||
note.flush()
|
self.collection().update_note(note, skip_undo_entry=True);
|
||||||
|
|
||||||
self.window().requireReset()
|
self.window().requireReset()
|
||||||
self.window().progress.finish()
|
self.window().progress.finish()
|
||||||
@ -978,7 +977,7 @@ class AnkiConnect:
|
|||||||
|
|
||||||
couldSetEaseFactors.append(True)
|
couldSetEaseFactors.append(True)
|
||||||
ankiCard.factor = easeFactors[i]
|
ankiCard.factor = easeFactors[i]
|
||||||
ankiCard.flush()
|
self.collection().update_card(ankiCard, skip_undo_entry=True)
|
||||||
|
|
||||||
return couldSetEaseFactors
|
return couldSetEaseFactors
|
||||||
|
|
||||||
@ -1008,7 +1007,7 @@ class AnkiConnect:
|
|||||||
ankiCard = self.getCard(card)
|
ankiCard = self.getCard(card)
|
||||||
for i, key in enumerate(keys):
|
for i, key in enumerate(keys):
|
||||||
setattr(ankiCard, key, newValues[i])
|
setattr(ankiCard, key, newValues[i])
|
||||||
ankiCard.flush()
|
self.collection().update_card(ankiCard, skip_undo_entry=True)
|
||||||
result.append(True)
|
result.append(True)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
result.append([False, str(e)])
|
result.append([False, str(e)])
|
||||||
@ -1688,9 +1687,11 @@ class AnkiConnect:
|
|||||||
|
|
||||||
result.append({
|
result.append({
|
||||||
'noteId': note.id,
|
'noteId': note.id,
|
||||||
|
'profile': self.window().pm.name,
|
||||||
'tags' : note.tags,
|
'tags' : note.tags,
|
||||||
'fields': fields,
|
'fields': fields,
|
||||||
'modelName': model['name'],
|
'modelName': model['name'],
|
||||||
|
'mod': note.mod,
|
||||||
'cards': self.collection().db.list('select id from cards where nid = ? order by ord', note.id)
|
'cards': self.collection().db.list('select id from cards where nid = ? order by ord', note.id)
|
||||||
})
|
})
|
||||||
except NotFoundError:
|
except NotFoundError:
|
||||||
@ -1702,6 +1703,23 @@ class AnkiConnect:
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@util.api()
|
||||||
|
def notesModTime(self, notes):
|
||||||
|
result = []
|
||||||
|
for nid in notes:
|
||||||
|
try:
|
||||||
|
note = self.getNote(nid)
|
||||||
|
result.append({
|
||||||
|
'noteId': note.id,
|
||||||
|
'mod': note.mod
|
||||||
|
})
|
||||||
|
except NotFoundError:
|
||||||
|
# Anki will give a NotFoundError if the note ID does not exist.
|
||||||
|
# Best behavior is probably to add an 'empty card' to the
|
||||||
|
# returned result, so that the items of the input and return
|
||||||
|
# lists correspond.
|
||||||
|
result.append({})
|
||||||
|
return result
|
||||||
|
|
||||||
@util.api()
|
@util.api()
|
||||||
def deleteNotes(self, notes):
|
def deleteNotes(self, notes):
|
||||||
@ -2010,11 +2028,19 @@ class AnkiConnect:
|
|||||||
@util.api()
|
@util.api()
|
||||||
def addNotes(self, notes):
|
def addNotes(self, notes):
|
||||||
results = []
|
results = []
|
||||||
|
errs = []
|
||||||
|
|
||||||
for note in notes:
|
for note in notes:
|
||||||
try:
|
try:
|
||||||
results.append(self.addNote(note))
|
results.append(self.addNote(note))
|
||||||
except:
|
except Exception as e:
|
||||||
results.append(None)
|
# I specifically chose to continue, so we gather all the errors of all notes (ie not break)
|
||||||
|
errs.append(str(e))
|
||||||
|
|
||||||
|
if errs:
|
||||||
|
# Roll back the changes so on error nothing happens
|
||||||
|
self.deleteNotes(results)
|
||||||
|
raise Exception(str(errs))
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user