├── License ├── README.md ├── anki-js-api-addon └── __init__.py └── images └── demo_1.png /License: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [2024] [krmanik] 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AnkiJS-API 2 | Anki JavaScript API to get cards information in reviewer window 3 | 4 | This addon is made to work with [AnkiDroid JS API](https://github.com/ankidroid/Anki-Android/wiki/AnkiDroid-Javascript-API). 5 | 6 | # Install from AnkiWeb 7 | https://ankiweb.net/shared/info/1490471827 8 | 9 | # Demo 10 | ![images](images/demo_1.png) 11 | 12 | # To get card info in reviewer for creating decks 13 | To know more about the options view 14 | [AnkiDroid JS API](https://github.com/ankidroid/Anki-Android/wiki/AnkiDroid-Javascript-API). 15 | ## New Count 16 | ```javascript 17 | pycmd("AnkiJS.ankiGetNewCardCount()", (ret) => { 18 | console.log(ret); 19 | }); 20 | ``` 21 | 22 | ## Learn Count 23 | ```javascript 24 | pycmd("AnkiJS.ankiGetLrnCardCount()", (ret) => { 25 | console.log(ret); 26 | }); 27 | ``` 28 | 29 | ## Review Count 30 | ```javascript 31 | pycmd("AnkiJS.ankiGetRevCardCount()", (ret) => { 32 | console.log(ret); 33 | }); 34 | ``` 35 | 36 | ## Mark 37 | ```javascript 38 | pycmd("AnkiJS.ankiGetCardMark()", (ret) => { 39 | console.log(ret); 40 | }); 41 | ``` 42 | 43 | ## Flag 44 | ```javascript 45 | pycmd("AnkiJS.ankiGetCardFlag()", (ret) => { 46 | console.log(ret); 47 | }); 48 | ``` 49 | 50 | ## Card Id 51 | ```javascript 52 | pycmd("AnkiJS.ankiGetCardId()", (ret) => { 53 | console.log(ret); 54 | }); 55 | ``` 56 | 57 | ## Note Id 58 | ```javascript 59 | pycmd("AnkiJS.ankiGetCardNid()", (ret) => { 60 | console.log(ret); 61 | }); 62 | ``` 63 | 64 | ## Deck Id 65 | ```javascript 66 | pycmd("AnkiJS.ankiGetCardDid()", (ret) => { 67 | console.log(ret); 68 | }); 69 | ``` 70 | 71 | ## Last modified time of card 72 | ```javascript 73 | pycmd("AnkiJS.ankiGetCardMod()", (ret) => { 74 | console.log(ret); 75 | }); 76 | ``` 77 | 78 | ## Type 79 | ```javascript 80 | pycmd("AnkiJS.ankiGetCardType()", (ret) => { 81 | console.log(ret); 82 | }); 83 | ``` 84 | 85 | ## Queue 86 | ```javascript 87 | pycmd("AnkiJS.ankiGetCardQueue()", (ret) => { 88 | console.log(ret); 89 | }); 90 | ``` 91 | 92 | ## Left 93 | ```javascript 94 | pycmd("AnkiJS.ankiGetCardLeft()", (ret) => { 95 | console.log(ret); 96 | }); 97 | ``` 98 | 99 | ## Due 100 | ```javascript 101 | pycmd("AnkiJS.ankiGetCardDue()", (ret) => { 102 | console.log(ret); 103 | }); 104 | ``` 105 | 106 | ## Interval 107 | ```javascript 108 | pycmd("AnkiJS.ankiGetCardInterval()", (ret) => { 109 | console.log(ret); 110 | }); 111 | ``` 112 | 113 | ## Factor 114 | ```javascript 115 | pycmd("AnkiJS.ankiGetCardFactor()", (ret) => { 116 | console.log(ret); 117 | }); 118 | ``` 119 | 120 | ## Reps 121 | ```javascript 122 | pycmd("AnkiJS.ankiGetCardReps()", (ret) => { 123 | console.log(ret); 124 | }); 125 | ``` 126 | 127 | ## Lapses 128 | ```javascript 129 | pycmd("AnkiJS.ankiGetCardLapses()", (ret) => { 130 | console.log(ret); 131 | }); 132 | ``` 133 | 134 | 135 | ## Original Due 136 | ```javascript 137 | pycmd("AnkiJS.ankiGetCardODue()", (ret) => { 138 | console.log(ret); 139 | }); 140 | ``` 141 | 142 | ## Deck ID of home deck if filtered 143 | ```javascript 144 | pycmd("AnkiJS.ankiGetCardODid()", (ret) => { 145 | console.log(ret); 146 | }); 147 | ``` 148 | 149 | 150 | ## Next Time 1 151 | ```javascript 152 | pycmd("AnkiJS.ankiGetNextTime1()", (ret) => { 153 | console.log(ret); 154 | }); 155 | ``` 156 | 157 | ## Next Time 2 158 | ```javascript 159 | pycmd("AnkiJS.ankiGetNextTime2()", (ret) => { 160 | console.log(ret); 161 | }); 162 | ``` 163 | 164 | ## Next Time 3 165 | ```javascript 166 | pycmd("AnkiJS.ankiGetNextTime3()", (ret) => { 167 | console.log(ret); 168 | }); 169 | ``` 170 | 171 | ## Next Time 4 172 | ```javascript 173 | pycmd("AnkiJS.ankiGetNextTime4()", (ret) => { 174 | console.log(ret); 175 | }); 176 | ``` 177 | 178 | ## Search in browser 179 | 180 | When quotes are needed in the search, e.g. "deck:My Deck" due to spaces in a deck name, they'll need to be escaped as the argument itself must be wrapped with single or double quotes. 181 | 182 | ```javascript 183 | pycmd("AnkiJS.ankiSearchCard('\"deck:My Deck\" MyField:*foo*')"); 184 | ``` 185 | -------------------------------------------------------------------------------- /anki-js-api-addon/__init__.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Tuple 2 | 3 | import aqt 4 | from aqt import gui_hooks, mw 5 | from anki.scheduler.v3 import Scheduler as V3Scheduler 6 | import re 7 | 8 | command_prefix = "AnkiJS." 9 | 10 | 11 | def on_webview_did_receive_js_message( 12 | handled: Tuple[bool, Any], message: str, context: Any 13 | ): 14 | # Run only on the reviewer's webview 15 | if not isinstance(context, aqt.reviewer.Reviewer): 16 | return handled 17 | 18 | if message.startswith(command_prefix): 19 | ret = None 20 | try: 21 | new, lrn, rev = mw.col.sched.counts(mw.reviewer.card) 22 | cmd = message[len(command_prefix):].strip() 23 | ret = None 24 | if cmd == "ankiGetNewCardCount()": 25 | ret = new 26 | 27 | elif cmd == "ankiGetLrnCardCount()": 28 | ret = lrn 29 | 30 | elif cmd == "ankiGetRevCardCount()": 31 | ret = rev 32 | 33 | elif cmd == "ankiGetCardMark()": 34 | ret = mw.reviewer.card.note().hasTag("marked") 35 | 36 | elif cmd == "ankiGetCardFlag()": 37 | ret = mw.reviewer.card.userFlag() 38 | 39 | elif cmd == "ankiGetCardId()": 40 | ret = mw.reviewer.card.id 41 | 42 | elif cmd == "ankiGetCardNid()": 43 | ret = mw.reviewer.card.nid 44 | 45 | elif cmd == "ankiGetCardDid()": 46 | ret = mw.reviewer.card.did 47 | 48 | elif cmd == "ankiGetCardMod()": 49 | ret = mw.reviewer.card.mod 50 | 51 | elif cmd == "ankiGetCardType()": 52 | ret = mw.reviewer.card.type 53 | 54 | elif cmd == "ankiGetCardQueue()": 55 | ret = mw.reviewer.card.queue 56 | 57 | elif cmd == "ankiGetCardLeft()": 58 | ret = mw.reviewer.card.left 59 | 60 | elif cmd == "ankiGetCardDue()": 61 | ret = mw.reviewer.card.due 62 | 63 | elif cmd == "ankiGetCardInterval()": 64 | ret = mw.reviewer.card.ivl 65 | 66 | elif cmd == "ankiGetCardFactor()": 67 | ret = mw.reviewer.card.factor 68 | 69 | elif cmd == "ankiGetCardReps()": 70 | ret = mw.reviewer.card.reps 71 | 72 | elif cmd == "ankiGetCardLapses()": 73 | ret = mw.reviewer.card.lapses 74 | 75 | elif cmd == "ankiGetCardODue()": 76 | ret = mw.reviewer.card.odue 77 | 78 | elif cmd == "ankiGetCardODid()": 79 | ret = mw.reviewer.card.odid 80 | 81 | elif ( 82 | cmd == "ankiGetNextTime1()" 83 | or cmd == "ankiGetNextTime2()" 84 | or cmd == "ankiGetNextTime3()" 85 | or cmd == "ankiGetNextTime4()" 86 | ): 87 | i = int(cmd[len("ankiGetNextTime"):][0]) 88 | if v3 := context._v3: 89 | assert isinstance(mw.col.sched, V3Scheduler) 90 | labels = mw.col.sched.describe_next_states(v3.states) 91 | ret = labels[i - 1] 92 | else: 93 | ret = mw.col.sched.nextIvlStr(mw.reviewer.card, i, True) or " " 94 | 95 | elif cmd.startswith("ankiSearchCard(") and cmd.endswith(")"): 96 | try: 97 | search_term = re.search( 98 | r'ankiSearchCard\(["\']{1}(.+?)["\']{1}\)', cmd 99 | ).group(1) 100 | except AttributeError: 101 | search_term = None 102 | browser = aqt.dialogs.open("Browser", mw.window()) 103 | browser.activateWindow() 104 | if search_term is not None: 105 | browser.form.searchEdit.lineEdit().setText(search_term) 106 | if hasattr(browser, "onSearch"): 107 | browser.onSearch() 108 | else: 109 | browser.onSearchActivated() 110 | ret = mw.findCards(search_term) 111 | 112 | except Exception as e: 113 | ret = repr(e) 114 | finally: 115 | return (True, ret) 116 | else: 117 | return handled 118 | 119 | 120 | gui_hooks.webview_did_receive_js_message.append(on_webview_did_receive_js_message) 121 | -------------------------------------------------------------------------------- /images/demo_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krmanik/AnkiJS-API/a0b620d38b7ef3ae4a8f01b34f0f00c1123ab685/images/demo_1.png --------------------------------------------------------------------------------