復習で熟知になったカードのノートにタグを付けるアドオンを作る Anki アドオン作成のチュートリアルです。Anki の標準フックがない場所にカスタムのフックを設置する方法にも触れます。最後にアドオンを AnkiWeb に公開する手順を紹介します。

はじめに

Anki は、アドオンを作成することで、標準機能を拡張したり、変更したりすることが出来ます。このアドオンの作成を簡単にするために、Anki はフック (Hook) という機能を提供しています。フックを使うと標準機能の中に簡単に外部の関数を組み込むことが出来ます。

この記事では更に進んで Anki の標準のフックがない場所にカスタムのフックを追加し、機能拡張するアドオンを作成する方法を簡単に説明します。復習後、熟知になったカードのノートにタグを付けるアドオンを開発します。オフィシャルドキュメント「Anki 2.0 アドオンの作成」を補足する内容です。

AnkiWeb に公開済みの Аnki アドオン (Add-on) Mature Tag (Code: 17741639) を例にとって解説します。

完成品をインストールして動作させることが出来ますし、ソースコードを見ることも出来ます。最後に作成したアドオンを AnkiWeb の共有アドオン一覧に登録する方法を紹介します。

アドオンのインストール方法は、Ankiの共有リソースを使ってみるで紹介しています。

また、ソースコードの表示方法は、インストールが完了した後、メニューバーの[ツール]-[アドオン]-[Mature_Tag]-[編集…​]を選択してください。

前提知識

この記事の内容を理解するには、次の知識が必要です。

  • Anki の操作方法 特に、Anki というアプリケーションの機能を十分理解した上での操作方法の習熟は必須です。

  • Python による開発経験 他の開発言語の経験でも構いませんが、自分で資料を読み Python の開発方法を学べる能力は必要です。

  • Anki 2.0 アドオンの作成の内容理解 本文中のサンプルコードを実際に動かしてアドオンを作成する経験があると理解に役立ちます。

この記事の最後に Anki アドオン開発のおさらい という項目を設けて、必要な知識をまとめています。

つくるアドオンの要件

まず最初にアドオンの要件を決めておきます。

「復習が終わったら、新たに設定した復習間隔を調べ、熟知なら "Mature" というタグをノートに追加し、それ以外なら "Mature" を削除します。」

モジュールのインポート

最初に必要なモジュールをインポートします。 Anki の復習で解答した時の処理は sched.py の中のクラス Scheduler のメソッド answerCard で記述しています。

from anki.sched import Scheduler

残念ながら復習に関する処理の中にフック (Hook) は存在しません。 そこで独自のフックをこのメソッドの最後に追加します。 フックの設置、呼び出しに必要なモジュールをインポートします。

from anki.hooks import addHook, runHook, wrap

フックを設置すると、複数の関数がそのの場所で実行できるようになります。 今回の例のように一つの関数しか実行しないのであれば、wrap() 関数だけを利用して単純に書くこともできます。その書き換え例は完成品の後に示します。

復習の解答後に処理するフックを設置する

まず、独自のフック "answeredRevCard" を追加する関数 newAnswerCard() を定義します。 runHook() はフックを新たに設置する標準の関数です。

def newAnswerCard(self, card, ease):
    runHook('answeredRevCard', self, card)

次に標準の wrap() 関数を使って、answerCard の後に newAnswerCard の内容を追加する処理を記述します。

Scheduler.answerCard = wrap(Scheduler.answerCard, newAnswerCard)

すると answerCard の最後に次の行が追加されます。

runHook('answeredRevCard', self, card)

これで、復習で解凍した後に処理を呼び出すフックを設置できました。

タグを追加、削除する

復習が終わったら、復習間隔を調べ 21 日以上であったら、"Mature" タグを追加し、それ以外の場合は削除する関数 matureCheck() を定義します。 熟知の基準日数は変数 threshold に、設定するタグ文字列は変数 MatureTag に設定しました。 タグをノートに追加するには Note クラスのメソッド addTag() を、削除するには delTag() を使います。

# Threshold interval for tagging
threshold = 21
# Tag string for mature note
MatureTag = u"Mature"

def matureCheck(self, card):
    f = card.note()
    if (card.ivl >= threshold):
        f.addTag(MatureTag)
    else:
        f.delTag(MatureTag)
    f.flush()
    return True

最後に addHook() 関数を使って上で作成したカスタムフック "answeredRevCard" で matureCheck() 関数を呼び出す設定をします。

addHook("answeredRevCard", matureCheck)

これで、完成です。完成品は次の通りです。

Mature_Tag.py
from anki.hooks import addHook, runHook, wrap
from anki.sched import Scheduler

# Threshold interval for tagging
threshold = 21
# Tag string for mature note
MatureTag = u"Mature"

def matureCheck(self, card):
    f = card.note()
    if (card.ivl >= threshold):
        f.addTag(MatureTag)
    else:
        f.delTag(MatureTag)
    f.flush()
    return True

def newAnswerCard(self, card, ease):
    runHook('answeredRevCard', self, card)

Scheduler.answerCard = wrap(Scheduler.answerCard, newAnswerCard)

addHook("answeredRevCard", matureCheck)

もっとかんたんに書く

フックを設置せずに wrap() 関数を使う方法を紹介しましょう。直接 matureCheck を指定します。

変更箇所の差分表示 (- で始まる行を削除、+ で始まる行を追加)
@@ -1,4 +1,4 @@
-from anki.hooks import addHook, runHook, wrap
+from anki.hooks import wrap
 from anki.sched import Scheduler

 # Threshold interval for tagging
@@ -6,7 +6,7 @@
 # Tag string for mature note
 MatureTag = u"Mature"

-def matureCheck(self, card):
+def matureCheck(self, card, ease):
     f = card.note()
     if (card.ivl >= threshold):
         f.addTag(MatureTag)
@@ -15,9 +15,4 @@
     f.flush()
     return True

-def newAnswerCard(self, card, ease):
-    runHook('anseweredRevCard', self, card)
-
-Scheduler.answerCard = wrap(Scheduler.answerCard, newAnswerCard)
-
-addHook("anseweredRevCard", matureCheck)
+Scheduler.answerCard = wrap(Scheduler.answerCard, matureCheck)

AnkiWeb に公開済みの Аnki アドオン (Add-on) Mature Tag (Code: 17741639) は、この簡略版を公開しています。

ファイルの配置

完成したファイル Mature_Tag.py を動作させるには、Documents/Anki/addons フォルダの中に保存します。Anki を再起動すると、この Python スクリプトファイルを読み込みます。

アドオンが読み込まれるとメニューバーに [ツール]-[アドオン]-[Mature_Tag] という項目が追加されます。

AnkiWeb の共有アドオン一覧に登録する

作成したアドオンを AnkiWeb に公開して、アドオン一覧に登録する方法を紹介します。 まず、共有アドオン一覧を開き、AnkiWeb にサインインします。

アドオン一覧
図 1. 共有アドオン一覧

登録フォームは画面右上の [Upload Add-on] ボタンをクリックすると表示します。

登録フォーム
図 2. アドオン登録フォーム

このフォームでは次のように項目入力します。

  • Title: アドオンの名前を入力します。アドオン一覧に表示する名前にになります。

  • File: 登録したいアドオンの Python スクリプトを選択します。今回の例では Mature_Tag.py です。

  • Description アドオンユーザーのためにアドオンの機能や使い方の説明を入力します。

入力が済んだら [Upload] を押すと登録できます。特に AnkiWeb に障害がなければ即座に公開されます。 登録したアドオンのページに移動します。

公開したアドオンのページの下にある [Update] ボタンを押すと登録内容の編集ができ、[Remove] ボタンを押すとアドオンを削除することができます。

アドオンページ 編集、削除ボタン
図 3. アドオンページ 編集、削除ボタン

まとめ

「Anki 2.0 アドオンの作成」で説明しているとおり、Anki が標準で用意しているフックを利用すると Anki の機能の修正や追加が簡単になります。フックが用意されていない箇所にも、この記事で紹介した方法で自分でカスタムのフックを自由に追加することができます。フックの使い方になれると Anki アドオン作成するを能力が向上し、Anki 自身の機能についても理解を深めることができます。

遺補: Anki アドオン開発のおさらい

Anki のアドオンは、Python スクリプトで記述し、Documents/Anki/addons フォルダの中に保存します。 Anki に Python インタープリタが含まれているため、アドオンの開発に Python のインストールは必要ありません。

Anki は起動時に addons フォルダの中の .py ファイルを読み込みます。 従って、新たにアドオンファイルを追加した場合、再起動して初めて機能するようになります。 なお、シフトキーを押したまま Anki を起動すると、アドオンファイルの読み込みは行いません。

Anki には、WordPress のようにフック (Hook) という機能を提供していて、標準機能を拡張したり、変更したりするアドオン開発が容易に出来ます。Anki 自身もたくさんのフックを利用しています。

アドオンをつくるために Anki が用意している関数は次の通りです。

表 1. アドオン開発用関数
関数名 説明

runHook

フックを実行する。値は返さない。

runFilter

フィルターを実行し値を返す。

addHook

フックを追加する。

remHook

フックを削除する。

wrap

既存の関数を上書きする。第3引数でオリジナルの関数の位置を指定。既定はオリジナルの後にカスタム関数を実行する。

フックを見つけるには、Anki のソースコードを "runHook"、"runFilter" で検索します。Anki 2.0.36 現在 49 のフックが存在します。

フックの使い方を詳しく知るには、Anki 2.0 アドオンの作成、および anki/hooks.py をお読みください。

更新情報

2014/01/17: 初出
2016/05/04: 更新: サンプルファイルの保存場所を変更
2016/05/31: 更新: カスタムフックを設置しない方法について加筆