-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
AnkiDroid Javascript API
This page is an advanced feature, designed for deck developers
This api allows deck developer to add functionality to cards that can call native functions defined in AnkiDroid. This can be used to design new layouts for buttons, cards info, top bar card counts, mark, flag etc.
Additional information:
- Forum thread about this JS API: https://forums.ankiweb.net/t/js-api-discussion/44907/2
- AbstractFlashcardViewer.java
- js-api.js
- AnkiDroid JS API Test Deck
- Anki (NOT AnkiDroid!) Addon that delivers many of the same functions in Anki Desktop. https://github.com/jhhr/AnkiJS-API . Or, with one less function (search in browser) in the Addon Store: https://ankiweb.net/shared/info/1490471827
- Extra-JS-API-functionality-by-silidev
The API must be initialized before use. You must provide a developer contact before using the API. Your contact details are shown to users if breaking changes are made to the API.
The v2.18 api version is 0.0.3
. 2.17 was 0.0.2
.
<script>
// A debug console is strongly recommended: https://github.com/ankidroid/Anki-Android/wiki/Development-Guide#html-javascript-inspection
var jsApiContract = { version: "0.0.3", developer: /* your email here */ };
var api = new AnkiDroidJS(jsApiContract);
</script>
API responses return an API Result wrapped in a Promise. Pseudocode:
type ApiResult = { success: boolean, value: boolean | string | number | any[] | any }
type ApiResponse = Promise<ApiResultData>
On success, response.value
is usable.
<script>
// A debug console is strongly recommended: https://github.com/ankidroid/Anki-Android/wiki/Development-Guide#html-javascript-inspection
var jsApiContract = { version: "0.0.3", developer: /* your email here */ };
var api = new AnkiDroidJS(jsApiContract);
api.ankiGetCardDid().then(response => console.log(response)); // {success: true, value: '1707102058557'}
// or use await in an IIFE (https://developer.mozilla.org/en-US/docs/Glossary/IIFE):
(async function() {
console.log(await api.ankiGetCardDid()); // {success: true, value: '1707102058557'}
})();
// the response may not need to be accessed
api.ankiShowToast("Hello Anki!");
</script>
Expect API failures, these are most likely to occur when functions are not available in the Previewer (e.g. ankiGetETA()
), but will also occur when breaking API changes are made.
When a failure occurs, type-safe default values are provided in ApiResult.value
, along with { success: false }
invalidApi.ankiGetCardDid("").then(x => console.log(x)) // {success: false, value: '-1'}
-
boolean
=>false
-
number
=>-1
-
string
=>"unknown"
-
array
=>[]
-
object
=>{}
A list of all methods can be obtained with console.log(Object.keys(jsApiList).sort().join("\r\n"))
ankiAnswerEase1
ankiAnswerEase2
ankiAnswerEase3
ankiAnswerEase4
ankiBuryCard
ankiBuryNote
ankiEnableHorizontalScrollbar
ankiEnableVerticalScrollbar
ankiGetCardDid
ankiGetCardDue
ankiGetCardFactor
ankiGetCardFlag
ankiGetCardId
ankiGetCardInterval
ankiGetCardLapses
ankiGetCardLeft
ankiGetCardMark
ankiGetCardMod
ankiGetCardNid
ankiGetCardODid
ankiGetCardODue
ankiGetCardQueue
ankiGetCardReps
ankiGetCardType
ankiGetDeckName
ankiGetETA
ankiGetLrnCardCount
ankiGetNewCardCount
ankiGetNextTime1
ankiGetNextTime2
ankiGetNextTime3
ankiGetNextTime4
ankiGetRevCardCount
ankiIsActiveNetworkMetered
ankiIsDisplayingAnswer
ankiIsInFullscreen
ankiIsInNightMode
ankiIsTopbarShown
ankiMarkCard
ankiResetProgress
ankiSearchCard
ankiSearchCardWithCallback
ankiSetCardDue
ankiShowAnswer
ankiShowNavigationDrawer
ankiShowOptionsMenu
ankiShowToast
ankiSttSetLanguage
ankiSttStart
ankiSttStop
ankiSuspendCard
ankiSuspendNote
ankiToggleFlag
ankiTtsFieldModifierIsAvailable
ankiTtsIsSpeaking
ankiTtsSetLanguage
ankiTtsSetPitch
ankiTtsSetSpeechRate
ankiTtsSpeak
ankiTtsStop
showAnswer()
When a card is shown, only the question is shown at first. So use this to perform show answer click through JS.
<button onclick="showAnswer();">Show Answer From JavaScript</button>
The following buttons can be called when available, there is case where only Again
and Hard
present so that can also be handled using JavaScript code.
The following function will be called when buttons available on screen.
buttonAnswerEase1()
Perform Again
button click
<button onclick="buttonAnswerEase1();">Again From JS</button>
buttonAnswerEase2()
Perform Hard
button click
<button onclick="buttonAnswerEase2();">Hard From JS</button>
buttonAnswerEase3()
Perform Good
button click
<button onclick="buttonAnswerEase3();">Good From JS</button>
buttonAnswerEase4()
Perform Easy
button click
<button onclick="buttonAnswerEase4();">Easy From JS</button>
api.ankiSetCardDue(int)
Reschedule card with x days. This will set the interval of the card to x (cf this issue)
<button onclick="api.ankiSetCardDue(5);">Reschedule card in 5 days</button>
api.ankiMarkCard()
Adds a tag called "Marked" the current note, so it can be easily found in a search.
<button onclick="api.ankiMarkCard();">Mark</button>
api.ankiToggleFlag()
Adds a colored marker to the card, or toggles it off. Flags will appear during study, and it can be easily found in a search.
Pass the arguments "none"
, "red"
, "orange"
, "green"
, "blue"
for flagging respective flag.
none,
red,
orange,
green,
blue,
For flagging red in current card.
<button onclick="api.ankiToggleFlag('red');">Red Flag</button>
api.ankiBuryCard();
boolean
Bury current Cards showing in reviewer UI
<button onclick="api.ankiBuryCard();">Bury Card</button>
api.ankiBuryNote();
Bury current Notes showing in reviewer UI
boolean
<button onclick="api.ankiBuryNote();">Bury Note</button>
api.ankiSuspendCard();
boolean
Suspend current Cards showing in reviewer UI
<button onclick="api.ankiSuspendCard();">Suspend Card</button>
api.ankiSuspendNote();
boolean
Suspend current Notes showing in reviewer UI
<button onclick="api.ankiSuspendNote();">Suspend Note</button>
api.ankiAddTagToCard();
Open tags dialog to add tags to current Notes
<button onclick="api.ankiAddTagToCard();">Show Tag Dialog to add tag</button>
ankiShowOptionsMenu()
In full screen, a button can be added to show options menu.
<button onclick="ankiShowOptionsMenu()">Show Options Menu</button>
ankiShowNavDrawer()
In full screen, a button can be added to show side navigation drawer.
<button onclick="ankiShowNavDrawer()">Show Navigation Drawer</button>
ankiShowToast(message)
Show Android Toast message
ankiShowToast("This message will shown in reviewer");
api.ankiIsDisplayingAnswer()
Boolean
Return true
if reviewer showing answer else false
console.log(api.ankiIsDisplayingAnswer());
api.ankiIsInFullscreen()
Boolean
Return fullscreen status in webview
console.log(api.ankiIsInFullscreen());
api.ankiIsTopbarShown()
Boolean
It can be used to show / hide custom topbar design. See #6747 for more.
console.log(api.ankiIsTopbarShown());
api.ankiIsInNightMode()
Boolean
Get night mode status. It can used to toggle between custom css
based on the status.
console.log(api.ankiIsInNightMode());
api.ankiIsActiveNetworkMetered()
Boolean
Status about device connected to metered connection. It can be used stop loading heavy assets.
console.log(api.ankiIsActiveNetworkMetered());
Add functions to Front / Back side
of card to get info.
api.ankiGetNewCardCount()
int
Return number of new card count
console.log(api.ankiGetNewCardCount());
api.ankiGetLrnCardCount()
int
Return number of learn card count
console.log(api.ankiGetLrnCardCount());
api.ankiGetRevCardCount()
int
Return number of review card count
console.log(api.ankiGetRevCardCount());
api.ankiGetETA()
int
Return remaining time to complete review
console.log(api.ankiGetETA());
api.ankiGetCardMark()
true/false
Return current card marked or not. Return boolean value of mark status, true for marked, false for unmarked
console.log(api.ankiGetCardMark());
api.ankiGetCardFlag()
int
Return int value of flag
none,
red,
orange,
green,
blue
console.log(api.ankiGetCardFlag());
api.ankiGetCardId()
long
Returns the ID of the card. Example: 1477380543053
console.log(api.ankiGetCardId());
api.ankiGetCardNid()
long
Returns the ID of the note which generated the card. Example: 1590418157630
console.log(api.ankiGetCardNid());
api.ankiGetCardDid()
long
Returns the ID of the deck which contains the card. Example: 1595967594978
console.log(api.ankiGetCardDid());
api.ankiGetDeckName()
String
Return deck name currently showing in webview/reviewer
console.log(api.ankiGetDeckName());
api.ankiGetCardMod()
long
Returns the last modified time as a Unix timestamp in seconds. Example: 1477384099
If a card was never modified, this does NOT give the time when it was added, but some other unknown value, which does not seem to be in relation to to the added time. Maybe it is like the return value of the "cardDue" API method which returns "note id or random int" for new cards.
console.log(api.ankiGetCardMod());
api.ankiGetCardType()
int
Returns card type
0 = new
1 = learning
2 = review
3 = relearning
console.log(api.ankiGetCardType());
api.ankiGetCardQueue()
int
-3 = user buried(In scheduler 2),
-2 = sched buried (In scheduler 2),
-2 = buried(In scheduler 1),
-1 = suspended,
0 = new, 1=learning, 2=review (as for type)
3 = in learning, next rev in at least a day after the previous review
4 = preview
console.log(api.ankiGetCardQueue());
api.ankiGetCardLeft()
int
-- of the form a*1000+b, with:
-- b the number of reps left till graduation
-- a the number of reps left today
console.log(api.ankiGetCardLeft());
api.ankiGetCardDue()
long
Due is used differently for different card types:
new: note id or random int
due: integer day, relative to the collection's creation time
learning: integer timestamp
console.log(api.ankiGetCardDue());
api.ankiGetCardInterval()
int
interval (used in SRS algorithm). Negative = seconds, positive = days
console.log(api.ankiGetCardInterval());
api.ankiGetCardFactor()
int
Return ease factor of the card in permille (parts per thousand)
console.log(api.ankiGetCardFactor());
api.ankiGetCardReps()
int
Return number of reviews made on current card
console.log(api.ankiGetCardReps());
api.ankiGetCardLapses()
int
Return number of times the card went from a "was answered correctly"
console.log(api.ankiGetCardLapses());
api.ankiGetCardODue()
long
original due: In filtered decks, it's the original due date that the card had before moving to filtered.
-- If the card lapsed in scheduler1, then it's the value before the lapse. (This is used when switching to scheduler 2. At this time, cards in learning becomes due again, with their previous due date)
-- In any other case it's 0.
console.log(api.ankiGetCardODue());
api.ankiGetCardODid()
long
Returns the ID of the home deck for the card if it is filtered, or 0
if not filtered. Example: 1595967594978
console.log(api.ankiGetCardODid());
api.ankiGetNextTime1()
api.ankiGetNextTime2()
api.ankiGetNextTime3()
api.ankiGetNextTime4()
String
Return time for next review. Time at answer buttons (Again/Hard/Good/Easy/). It can be used to hide button if returned string is empty.
console.log(api.ankiGetNextTime1());
console.log(api.ankiGetNextTime2());
console.log(api.ankiGetNextTime3());
console.log(api.ankiGetNextTime4());
This has a bug in v2.18. I use this to correct:
public static async nextTimeStringForButton(i: number):Promise<string> {
if (Anki.mock)
return "1mocked"
const returnValueFromApi = await Anki.nextTimeStringForButtonRaw(i)
let corrected: JsApiString
const retType = typeof returnValueFromApi
if (retType==="string") {
corrected = JSON.parse(returnValueFromApi)
} else if (retType==="object")
corrected = returnValueFromApi.value
else
throw new Error("nextTimeStringForButton: unexpected return type")
if (!corrected.success)
printDebug("JS API returned success===false")
return corrected.value
}
private static wrap = (value: unknown) => {
return {value: value}
}
private static nextTimeStringForButtonRaw = async (i: number) => {
if (Anki.mock)
return this.wrap("2mock")
return await (await Anki.getApi())["ankiGetNextTime" + i]();
}
api.ankiSearchCard(query)
From reviewer UI, open Card browser and search for cards using the query
<button onclick="api.ankiSearchCard('deck:\"test\" {{hanzi}}')">Search this in Card browser</button>
View more examples in the PR #9247
Synthesizes speech from text for immediate playback. The JS API is implemented on top of Android Text to Speech.
api.ankiTtsSetLanguage(Locale)
loc
Locale: The locale describing the language to be used
int
Code indicating the support status for the locale
0 Denotes the language is available for the language by the locale, but not the country and variant.
1 Denotes the language is available for the language and country specified by the locale, but not the variant.
2 Denotes the language is available exactly as specified by the locale.
-1 Denotes the language data is missing.
-2 Denotes the language is not supported.
Read more TextToSpeech#setLanguage
api.ankiTtsSetLanguage('en-US');
Change en-US
to desired language.
api.ankiTtsSpeak(text)
text
CharSequence: The string of text to be spoken. No longer than getMaxSpeechInputLength()
characters
queueMode
int: The queuing strategy to use, QUEUE_ADD
or QUEUE_FLUSH
int
-1 ERROR
0 SUCCESS
Speaks the text using the specified queuing strategy and speech parameters.
Read More TextToSpeech#speak(str)
api.ankiTtsSpeak(text)
If you add 1
to the second argument, it will wait until the previous Speak ends before speaking
api.ankiTtsSpeak(text, 1)
api.ankiTtsSetSpeechRate(float)
speechRate
float: Speech rate. 1.0 is the normal speech rate, lower values slow down the speech (0.5 is half the normal speech rate), greater values accelerate it (2.0 is twice the normal speech rate).
int
-1 ERROR
0 SUCCESS
Read More TextToSpeech#setSpeechRate(float)
api.ankiTtsSetSpeechRate(0.8)
api.ankiTtsSetPitch(float)
pitch
float: Speech pitch. 1.0 is the normal pitch, lower values lower the tone of the synthesized voice, greater values increase it.
int
-1 ERROR
0 SUCCESS
Read more TextToSpeech#setPitch(float)
api.ankiTtsSetPitch(1.1)
api.ankiTtsIsSpeaking()
boolean
true
if the TTS engine is speaking
Read moreTextToSpeech#isSpeaking()
let isSpeaking = api.ankiTtsIsSpeaking();
api.ankiTtsStop()
int
-1 ERROR
0 SUCCESS
Interrupts the current utterance (whether played or rendered to file) and discards other utterances in the queue
Read more TextToSpeech#stop()
api.ankiTtsStop()
View more in PR #8812
If want to hide card's button / text in current card when reviewing on Anki Desktop / AnkiMobile then adding all code to if
block can hide the things.
Note: Using this may give some problem when using AnkiWeb in Android Chrome, so to make available some functionality only to AnkiDroid app then wv
in navigator.userAgent
can be used.
var UA = navigator.userAgent;
var isMobile = /Android/i.test(UA);
var isAndroidWebview = /wv/i.test(UA);
if (isMobile) {
// Here all AnkiDroid defined functions call.
// It will be hidden or not called on AnkiDesktop / AnkiMobile
if (isAndroidWebview) {
// Available only to AnkiDroid app only.
} else {
// Available to Chrome only (AnkiWeb opened in Android Chrome)
}
}
For more view Window.navigator and Navigator userAgent Property
if (document.documentElement.classList.contains("android")) {
// Hidden on AnkiDesktop / AnkiMobile
} else {
// Available to AnkiDesktop / AnkiMobile
}
Note: After AnkiDroid 2.12. Turn on fullscreen and also hide topbar from reviewer settings. More better implementation can be done for this.
Front/Back HTML
<div class="progress" id="progress">
<div class="progress-bar" id="bar"></div>
</div>
<!-- anki-persistence -->
<script>
// v0.5.2 - https://github.com/SimonLammer/anki-persistence/blob/62463a7f63e79ce12f7a622a8ca0beb4c1c5d556/script.js
if (void 0 === window.Persistence) { var _persistenceKey = "github.com/SimonLammer/anki-persistence/", _defaultKey = "_default"; if (window.Persistence_sessionStorage = function () { var e = !1; try { "object" == typeof window.sessionStorage && (e = !0, this.clear = function () { for (var e = 0; e < sessionStorage.length; e++) { var t = sessionStorage.key(e); 0 == t.indexOf(_persistenceKey) && (sessionStorage.removeItem(t), e--) } }, this.setItem = function (e, t) { void 0 == t && (t = e, e = _defaultKey), sessionStorage.setItem(_persistenceKey + e, JSON.stringify(t)) }, this.getItem = function (e) { return void 0 == e && (e = _defaultKey), JSON.parse(sessionStorage.getItem(_persistenceKey + e)) }, this.removeItem = function (e) { void 0 == e && (e = _defaultKey), sessionStorage.removeItem(_persistenceKey + e) }) } catch (e) { } this.isAvailable = function () { return e } }, window.Persistence_windowKey = function (e) { var t = window[e], i = !1; "object" == typeof t && (i = !0, this.clear = function () { t[_persistenceKey] = {} }, this.setItem = function (e, i) { void 0 == i && (i = e, e = _defaultKey), t[_persistenceKey][e] = i }, this.getItem = function (e) { return void 0 == e && (e = _defaultKey), t[_persistenceKey][e] || null }, this.removeItem = function (e) { void 0 == e && (e = _defaultKey), delete t[_persistenceKey][e] }, void 0 == t[_persistenceKey] && this.clear()), this.isAvailable = function () { return i } }, window.Persistence = new Persistence_sessionStorage, Persistence.isAvailable() || (window.Persistence = new Persistence_windowKey("py")), !Persistence.isAvailable()) { var titleStartIndex = window.location.toString().indexOf("title"), titleContentIndex = window.location.toString().indexOf("main", titleStartIndex); titleStartIndex > 0 && titleContentIndex > 0 && titleContentIndex - titleStartIndex < 10 && (window.Persistence = new Persistence_windowKey("qt")) } }
</script>
<!----------->
<script>
var cardCount = parseInt(api.ankiGetNewCardCount()) + parseInt(api.ankiGetLrnCardCount()) + parseInt(api.ankiGetRevCardCount());
var totalCardCount = 1;
if (Persistence.isAvailable()) {
totalCardCount = Persistence.getItem("total");
if (totalCardCount == null) {
totalCardCount = cardCount; // count set to total card count at first card, it will not change for current session
Persistence.setItem("total", totalCardCount);
}
}
// progress bar percentage
var per = Math.trunc(100 - cardCount * 100 / totalCardCount);
document.getElementById("bar").style.width = per + "%";
</script>
Card CSS
.progress {
width: 100%;
border-radius: 2px;
background-color: #ddd;
}
.progress-bar {
width: 1%;
height: 12px;
border-radius: 2px;
background-color: limegreen;
}
Note: After AnkiDroid 2.12. Turn on fullscreen and also hide topbar from reviewer settings.
Add this to front/Back Side
<div style="display:inline;" class="card-count">
<table style="width:28%; margin: 0 auto;">
<tbody>
<tr>
<tr id="card_count_dot" class="card-count-dot">
<td style="color:#2196f3;">•</td>
<td style="color:#ea2322;">•</td>
<td style="color:#4caf50;">•</td>
</tr>
<tr class="card-count-num">
<td style="color:#2196f3;" id="newCard"></td>
<td style="color:#ea2322;" id="learnCard"></td>
<td style="color:#4caf50;" id="reviewCard"></td>
</tr>
</tbody>
</table>
</div>
<div id="deck_title" class="title">{{Subdeck}}</div>
<div class="time-left" id="timeID"> </div>
<script>
var UA = navigator.userAgent;
var isMobile = /Android/i.test(UA);
var isAndroidWebview = /wv/i.test(UA);
if (isMobile) {
// Here all AnkiDroid defined functions call.
// It will be hidden or not called on AnkiDesktop / AnkiMobile
if (isAndroidWebview) {
// Available only to AnkiDroid app only.
document.getElementById("newCard").innerText = api.ankiGetNewCardCount();
document.getElementById("learnCard").innerText = api.ankiGetLrnCardCount();
document.getElementById("reviewCard").innerText = api.ankiGetRevCardCount();
var t = api.ankiGetETA();
document.getElementById("timeID").innerHTML = t + " mins.";
}
} else {
document.getElementById("card_count_dot").style.display = "none";
document.getElementById("deck_title").style.display = "none";
}
</script>
Add this to Card CSS
/*card counts, title, time*/
.card-count {
top: 0;
right: 4px;
position: absolute;
display: none;
}
.card-count-num {
color: black;
font-size: 18px;
}
.card-count-dot {
font-weight: bold;
font-size: 24px;
}
.card-count-text {
font-weight: light;
font-size: 14px;
}
.title {
top: 14px;
left: 14px;
position: absolute;
font-size: 20px;
color: grey;
font-weight: bold;
}
.time-left {
top: 36px;
left: 14px;
position: absolute;
font-size: 14px;
text-align: left;
font-weight: bold;
color: teal;
}
This was too hard to maintain here, so I moved it here: https://github.com/ankidroid/Anki-Android/wiki/Extra-JS-API-functionality-by-silidev
The implementation of above functionality can be found in this github repo. Anki Custom Card Layout
#6521 apiVersioning and developerContact implementation for AnkiDroid functions call from WebView
#6377 apiVersioning and developerContact implementation for AndkiDroid functions call from WebView
#6307 Make available current card's and deck's details in WebView
#6388 Get next time for review in WebView
#6393 Get Cards info (Review, Lapses, Interval, Due, Queue) in WebView
#6766 JS API: Add Remaining Card Properties
#6784 Javascript API: Add ankiIsActiveNetworkMetered
#6747 Get Topbar shown status in card
#6387 Show options menu & navigation drawer using WebView
#6567 Night mode status in Card
#6470 Get value of fullscreen status in JavaScript
#6886 Automatically flip card if there is no cloze hint + detect ankidroid
#6386 Show Toast using JavaScript function in WebView
#8199 JS API to know if answer is displaying or question
#8500 Get deck name using JS API
#9247 JS API to open card browser and search with query
#9245 New JS API for bury & suspend card and bury & suspend note and tag card
#8812 New JavaScript api for TTS
#14564 convert synchronous js api to asynchronous and remove js interface