Anki插件开发指南:扩展核心功能的完整教程

Anki插件开发指南:扩展核心功能的完整教程

【免费下载链接】anki Anki's shared backend and web components, and the Qt frontend 【免费下载链接】anki 项目地址: https://gitcode.com/GitHub_Trending/an/anki

概述:为什么需要Anki插件?

Anki作为一款强大的间隔重复记忆软件,其核心功能已经非常完善。但每个用户的学习需求都是独特的——你可能需要特殊的卡片模板、自定义的复习算法、与外部工具的集成,或者特定的数据可视化方式。这就是Anki插件(Add-ons)的价值所在。

通过插件开发,你可以:

  • 🎯 个性化学习体验:定制符合个人学习习惯的功能
  • 🔧 扩展核心功能:添加Anki原生不支持的特性
  • 🤖 自动化流程:减少重复性操作,提高学习效率
  • 📊 深度数据分析:获取更详细的学习统计和洞察

开发环境准备

基础要求

# 确保已安装Python 3.8+
python --version

# 安装必要的开发工具
pip install black flake8 mypy

项目结构分析

Anki插件采用标准的Python包结构:

mermaid

插件核心文件详解

1. manifest.json - 插件清单文件

{
  "package": "my_awesome_addon",
  "name": "我的超强插件",
  "mod": 1735714800,
  "conflicts": ["conflicting_addon_id"],
  "min_point_version": 231001000,
  "max_point_version": -231200000,
  "human_version": "1.2.3",
  "homepage": "https://example.com/addon",
  "update_enabled": true
}

字段说明表:

字段类型必需说明
packagestring插件文件夹名称,必须唯一
namestring显示给用户的插件名称
modnumber最后修改时间戳
conflictsarray冲突插件ID列表
min_point_versionnumber支持的最小Anki版本
max_point_versionnumber支持的最大Anki版本(负值表示不支持更高版本)
human_versionstring人类可读的版本号
homepagestring插件主页URL
update_enabledboolean是否启用自动更新

2. init.py - 插件入口文件

# Copyright: Your Name
# License: GNU AGPL, version 3 or later

from __future__ import annotations

import os
from typing import Any

from anki import hooks
from aqt import gui_hooks, mw
from aqt.qt import QAction, QMenu
from aqt.utils import showInfo, tooltip

# 插件配置管理
def get_config() -> dict[str, Any]:
    """获取插件配置"""
    config = mw.addonManager.getConfig(__name__)
    if config is None:
        # 默认配置
        return {
            "enable_feature": True,
            "max_cards": 100,
            "auto_sync": False
        }
    return config

def update_config(new_config: dict[str, Any]) -> None:
    """更新插件配置"""
    mw.addonManager.writeConfig(__name__, new_config)

# GUI钩子示例
def setup_browser_menu() -> None:
    """设置浏览器菜单"""
    def on_browser_menu(browser) -> None:
        action = QAction("我的插件功能", browser)
        action.triggered.connect(lambda: my_browser_function(browser))
        browser.form.menuEdit.addAction(action)
    
    gui_hooks.browser_will_show_context_menu.append(on_browser_menu)

def my_browser_function(browser) -> None:
    """自定义浏览器功能"""
    selected_notes = browser.selectedNotes()
    if selected_notes:
        showInfo(f"已选择 {len(selected_notes)} 个笔记")
    else:
        tooltip("请先选择笔记")

# 主菜单集成
def setup_main_menu() -> None:
    """设置主菜单"""
    action = QAction("我的插件", mw)
    action.triggered.connect(show_main_dialog)
    mw.form.menuTools.addAction(action)

def show_main_dialog() -> None:
    """显示主对话框"""
    from .dialog import MainDialog
    dialog = MainDialog(mw)
    dialog.exec()

# 初始化插件
def init_addon() -> None:
    """初始化插件"""
    try:
        setup_main_menu()
        setup_browser_menu()
        
        # 注册配置更新回调
        mw.addonManager.setConfigUpdatedAction(__name__, on_config_updated)
        
        print(f"插件 {__name__} 初始化成功")
    except Exception as e:
        print(f"插件初始化失败: {e}")

def on_config_updated(config: dict[str, Any]) -> None:
    """配置更新回调"""
    print("配置已更新:", config)

# 启动时加载插件
if mw is not None:
    init_addon()
else:
    print("Anki未启动,插件延迟加载")

钩子系统深度解析

Anki提供了强大的钩子(Hooks)系统,允许插件在特定时刻介入执行流程。

GUI钩子分类

mermaid

常用钩子示例

# 复习界面钩子
def on_show_question(text: str, card: Card, kind: str) -> str:
    """修改显示的问题文本"""
    if "关键内容" in text:
        return text + "<br><small>自定义提示</small>"
    return text

gui_hooks.reviewer_will_show_question.append(on_show_question)

# 编辑器钩子
def on_editor_did_init(editor: Editor) -> None:
    """编辑器初始化时添加自定义按钮"""
    button = editor.addButton("icon.png", "自定义功能", custom_editor_function)
    # 保存按钮引用以便后续使用
    editor.custom_button = button

gui_hooks.editor_did_init.append(on_editor_did_init)

# 配置文件钩子
def on_profile_did_open() -> None:
    """配置文件加载完成"""
    print("用户配置文件已加载")
    # 执行初始化操作

gui_hooks.profile_did_open.append(on_profile_did_open)

配置系统开发

配置架构设计

# config.schema.json - 配置验证架构
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "enable_feature": {
      "type": "boolean",
      "title": "启用功能",
      "default": true,
      "description": "是否启用主要功能"
    },
    "max_items": {
      "type": "integer",
      "title": "最大项目数",
      "minimum": 1,
      "maximum": 1000,
      "default": 100,
      "description": "允许处理的最大项目数量"
    },
    "color_scheme": {
      "type": "string",
      "title": "颜色方案",
      "enum": ["default", "dark", "light", "custom"],
      "default": "default",
      "description": "选择界面颜色方案"
    }
  },
  "required": ["enable_feature"]
}

# config.md - 配置说明文档
## 插件配置说明

### 基本设置
- **启用功能**: 控制插件的主要功能开关
- **最大项目数**: 限制处理的项目数量,避免性能问题

### 高级设置  
- **颜色方案**: 选择适合你偏好的界面样式

### 注意事项
⚠️ 修改配置后需要重启Anki才能完全生效

配置对话框实现

from typing import Any

from aqt.qt import QDialog, QVBoxLayout, QCheckBox, QSpinBox, QComboBox
from aqt.utils import disable_help_button

class ConfigDialog(QDialog):
    def __init__(self, parent=None) -> None:
        super().__init__(parent)
        self.setWindowTitle("插件配置")
        self.setup_ui()
        self.load_config()
        disable_help_button(self)
    
    def setup_ui(self) -> None:
        layout = QVBoxLayout()
        
        self.enable_checkbox = QCheckBox("启用主要功能")
        layout.addWidget(self.enable_checkbox)
        
        self.max_items_spinbox = QSpinBox()
        self.max_items_spinbox.setRange(1, 1000)
        self.max_items_spinbox.setSuffix(" 个项目")
        layout.addWidget(self.max_items_spinbox)
        
        self.color_combo = QComboBox()
        self.color_combo.addItems(["default", "dark", "light", "custom"])
        layout.addWidget(self.color_combo)
        
        self.setLayout(layout)
    
    def load_config(self) -> None:
        config = get_config()
        self.enable_checkbox.setChecked(config.get("enable_feature", True))
        self.max_items_spinbox.setValue(config.get("max_items", 100))
        self.color_combo.setCurrentText(config.get("color_scheme", "default"))
    
    def save_config(self) -> None:
        new_config = {
            "enable_feature": self.enable_checkbox.isChecked(),
            "max_items": self.max_items_spinbox.value(),
            "color_scheme": self.color_combo.currentText()
        }
        update_config(new_config)

高级功能开发

卡片处理流水线

mermaid

批量操作实现

from concurrent.futures import ThreadPoolExecutor, as_completed
from typing import List

def batch_process_notes(note_ids: List[int]) -> None:
    """批量处理笔记"""
    total = len(note_ids)
    processed = 0
    
    with ThreadPoolExecutor(max_workers=4) as executor:
        # 提交处理任务
        future_to_note = {
            executor.submit(process_single_note, note_id): note_id 
            for note_id in note_ids
        }
        
        # 处理完成的任务
        for future in as_completed(future_to_note):
            note_id = future_to_note[future]
            try:
                result = future.result()
                processed += 1
                progress = processed / total * 100
                print(f"处理进度: {progress:.1f}%")
            except Exception as e:
                print(f"处理笔记 {note_id} 时出错: {e}")

def process_single_note(note_id: int) -> None:
    """处理单个笔记"""
    note = mw.col.get_note(note_id)
    if note:
        # 应用自定义处理逻辑
        modified = apply_custom_logic(note)
        if modified:
            mw.col.update_note(note)
            return True
    return False

调试与测试

调试技巧

import logging
import traceback

# 设置插件专用日志
logger = logging.getLogger("my_addon")
logger.setLevel(logging.DEBUG)

def debug_wrapper(func):
    """调试装饰器"""
    def wrapper(*args, **kwargs):
        try:
            logger.debug(f"调用 {func.__name__} with args: {args}, kwargs: {kwargs}")
            result = func(*args, **kwargs)
            logger.debug(f"{func.__name__} 返回: {result}")
            return result
        except Exception as e:
            logger.error(f"{func.__name__} 出错: {e}")
            logger.error(traceback.format_exc())
            raise
    return wrapper

# 使用装饰器
@debug_wrapper
def risky_operation(data):
    # 可能出错的操作
    return data.process()

单元测试框架

import unittest
from unittest.mock import Mock, patch

class TestAddon(unittest.TestCase):
    def setUp(self):
        """测试设置"""
        self.config = {
            "enable_feature": True,
            "max_items": 100
        }
    
    @patch('aqt.mw')
    def test_config_loading(self, mock_mw):
        """测试配置加载"""
        mock_mw.addonManager.getConfig.return_value = self.config
        result = get_config()
        self.assertEqual(result, self.config)
    
    def test_note_processing(self):
        """测试笔记处理逻辑"""
        mock_note = Mock()
        mock_note.fields = ["字段1", "字段2"]
        
        result = process_single_note(mock_note)
        self.assertTrue(result)
        self.assertEqual(mock_note.fields[0], "处理后的字段1")

发布与分发

打包脚本

#!/bin/bash
# build_addon.sh

ADDON_NAME="my_awesome_addon"
VERSION="1.0.0"
OUTPUT_FILE="${ADDON_NAME}-${VERSION}.ankiaddon"

# 清理旧文件
rm -f "$OUTPUT_FILE"

# 创建临时目录
TEMP_DIR=$(mktemp -d)

# 复制必要文件
cp -r __init__.py manifest.json config.json config.md config.schema.json src/ "$TEMP_DIR/"

# 创建ZIP包
cd "$TEMP_DIR"
zip -r "$OUTPUT_FILE" .
mv "$OUTPUT_FILE" "$OLDPWD/"

# 清理
cd "$OLDPWD"
rm -rf "$TEMP_DIR"

echo "插件已打包: $OUTPUT_FILE"

版本管理策略

mermaid

最佳实践与注意事项

性能优化建议

场景问题解决方案
批量处理内存占用过高分批次处理,使用生成器
界面操作UI卡顿使用后台线程,QTimer延迟执行
数据库访问查询缓慢建立索引,批量操作
网络请求超时失败设置超时时间,重试机制

兼容性考虑

def check_compatibility():
    """检查Anki版本兼容性"""
    from anki.utils import int_version
    
    current_version = int_version()
    required_version = 231001000  # Anki 23.10+
    
    if current_version < required_version:
        showWarning("此插件需要Anki 23.10或更高版本")
        return False
    
    # 检查其他依赖
    try:
        import some_required_module
    except ImportError:
        showWarning("缺少必要依赖: some_required_module")
        return False
    
    return True

总结

通过本教程,你已经掌握了Anki插件开发的核心技能:

  1. 理解了插件架构 - 从manifest配置到钩子系统
  2. 学会了配置管理 - 灵活的配置系统和用户界面
  3. 掌握了高级技巧 - 批量处理、多线程、错误处理
  4. 了解了发布流程 - 从开发到分发的完整流程

记住成功的插件开发关键在于:

  • ✅ 充分理解用户需求
  • ✅ 遵循Anki的最佳实践
  • ✅ 提供清晰的文档和配置
  • ✅ 保持良好的兼容性和性能

现在就开始你的第一个Anki插件项目吧!从一个小功能开始,逐步扩展,你会发现为Anki生态贡献自己的力量既有趣又有价值。

【免费下载链接】anki Anki's shared backend and web components, and the Qt frontend 【免费下载链接】anki 项目地址: https://gitcode.com/GitHub_Trending/an/anki

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值