🐍

PythonでAnkiの拡張機能を作ろう!

2023/03/19に公開

はじめに

Ankiという暗記帳アプリをご存知でしょうか。
Ankiを一言で表すと、「多機能で自由にカスタマイズ可能な暗記帳アプリ」です。

僕はこのアプリで英単語などを学習しているのですが、英単語の意味や例文を調べてデッキに追加する作業が面倒で、どうにか楽できないかと悩んでいました。
そこでPythonを使って、簡単に英単語を追加できる拡張機能を作ろうと思い、本記事を作成しました。

参考にしたサイト

環境

  • MacOS
  • Python 3.9
  • Anki 2.1.60

拡張機能の基本

開発する際の型サポートとして以下のパッケージをインストールします。
また、mypyを使用することが推奨されています。

pip install aqt

構成

拡張機能をAnki内で実行するためには、特定のフォルダーに拡張機能を作成する必要があります。

  • Windows: C:\Users\user1\AppData\Roaming\Anki2\addons21
  • Mac: ~/Library/Application Support/Anki2/addons21

以下、Macでの例です。

cd ~/Library/Application\ Support/Anki2/addons21
mkdir myaddon1
cd myaddon1
touch __init__.py
  • __init__.py がないとAnkiが拡張機能と認識してくれないので注意が必要です。
  • addons21フォルダがなく、代わりにaddonsフォルダがある場合はAnki 2.0以前を使用している可能性があります。

最初の例

先ほど作成した__init__.py にコードを書いていきます。
公式サイトに記載されている例は以下のとおりです。

__init__.py
# aqt からメインウィンドウオブジェクト (mw) をインポートする
from aqt import mw
# utils.py から "show info" ツールをインポートする
from aqt.utils import showInfo, qconnect
# Qt GUI ライブラリをすべてインポートする
from aqt.qt import *

# 下にメニュー項目を追加していきます。まず、メニュー項目がアクティブになったときに呼び出される関数を作成したいと思います。

def testFunction() -> None:
    # メインウィンドウに格納されている現在のコレクション内のカード枚数を取得します。
    cardCount = mw.col.cardCount()
    # メッセージボックスを表示する
    showInfo("Card count: %d" % cardCount)

# 新しいメニュー項目 "test "を作成する
action = QAction("test", mw)
# クリックされたときに testFunction を呼び出すように設定する
qconnect(action.triggered, testFunction)
# そしてツールメニューに追加する
mw.form.menuTools.addAction(action)

ファイルを保存してAnkiを開くと、メニュー欄のツールにtestという項目が追加されているはずです。
クリックするとメッセージボックスが表示されます。

英単語登録アドオンを作ろう

今回はノートの編集画面で、選択した単語の意味を挿入してくれる拡張機能を作ります。

英単語をゲット!

今回はWeblio辞書から英単語のデータを頂いて使用したいと思います。
スクレイピングをして単語の意味を取得します。

__init__.py
import requests
from bs4 import BeautifulSoup

def get_weblio_data(word: str):
    url = f"https://ejje.weblio.jp/content/{word}"
    response = requests.get(url)
    response.encoding = response.apparent_encoding
    if not response.ok:
        return

    soup = BeautifulSoup(response.text, "html.parser")
    selector = "#summary > div.summaryM.descriptionWrp > p > span.content-explanation.ej"
    if node := soup.select_one(selector):
        return node.text
print("意味:", get_weblio_data("Hello"))
# 意味: お(ー)い!、もし!、やあ!、よお!、こんにちは!、もしもし!、おや!、あら!

Ankiの編集ボタンを自作

画像の(一番右側の)ようなボタンを作成します。

ボタンの追加は簡単にできます。

__init__.py
from anki.hooks import addHook
from aqt.editor import Editor
from aqt.utils import showInfo

def onStrike(editor: Editor):
    showInfo("Click!")

def addWeblioButton(buttons, editor):
    editor._links['strike'] = onStrike
    return buttons + [editor._addButton(
        "アイコン画像の絶対パス.png",
        "strike",
        "weblio")]

addHook("setupEditorButtons", addWeblioButton)

ボタンをクリックした際に、onStrikeが呼び出されます。

選択したテキストを翻訳

AnkiのエディタはQtのWebViewでできているので、web.eval関数を使用してJavaScriptを実行できます。
evalWithCallbackは、JavaScriptの実行結果を引数の関数に渡してくれます。

def onStrike(editor: Editor):
    def callback(word: str):
        # 翻訳データを取得
        text = get_weblio_data(word) 
        html = f"{word}<br>{text}"
        html = json.dumps(html)

        # エディタのdocumentにhtml変数を埋め込みます。
        editor.web.eval(f"document.execCommand('insertHTML', false, {html});")

    # 現在選択しているテキストを取得して、callback関数に渡します。
    editor.web.evalWithCallback("window.getSelection().toString()", callback)

全体

__init__.py
import requests
from bs4 import BeautifulSoup

import json
from anki.hooks import addHook
from aqt.editor import Editor

def get_weblio_data(word: str):
    url = f"https://ejje.weblio.jp/content/{word}"
    response = requests.get(url)
    response.encoding = response.apparent_encoding
    if not response.ok:
        return

    soup = BeautifulSoup(response.text, "html.parser")
    selector = "#summary > div.summaryM.descriptionWrp > p > span.content-explanation.ej"
    if node := soup.select_one(selector):
        return node.text


def onStrike(editor: Editor):
    def callback(word: str):
        # 翻訳データを取得
        text = get_weblio_data(word) 
        html = f"{word}<br>{text}"
        html = json.dumps(html)

        # エディタのdocumentにhtml変数を埋め込みます。
        editor.web.eval(f"document.execCommand('insertHTML', false, {html});")

    # 現在選択しているテキストを取得して、callback関数に渡します。
    editor.web.evalWithCallback("window.getSelection().toString()", callback)

def addWeblioButton(buttons, editor):
    editor._links['strike'] = onStrike
    return buttons + [editor._addButton(
        "アイコン画像の絶対パス.png",
        "strike",
        "weblio")]

addHook("setupEditorButtons", addWeblioButton)


できました!
また、スクレピングの際に音節や発音記号、例文などもWeblio辞書から取得できます。

最後に

今回はPythonを使ってAnkiの拡張機能を作る方法を紹介しました。
記事に間違いや不適切な表現などがある場合は、コメントで指摘してもらえると助かります。

Discussion