惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

W
WeLiveSecurity
The Last Watchdog
The Last Watchdog
Application and Cybersecurity Blog
Application and Cybersecurity Blog
G
Google Developers Blog
博客园 - 叶小钗
雷峰网
雷峰网
人人都是产品经理
人人都是产品经理
博客园_首页
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
博客园 - 三生石上(FineUI控件)
Help Net Security
Help Net Security
Cloudbric
Cloudbric
AI
AI
N
News | PayPal Newsroom
博客园 - 聂微东
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 【当耐特】
Forbes - Security
Forbes - Security
美团技术团队
Stack Overflow Blog
Stack Overflow Blog
SecWiki News
SecWiki News
H
Heimdal Security Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
MyScale Blog
MyScale Blog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
P
Proofpoint News Feed
S
Security @ Cisco Blogs
Google DeepMind News
Google DeepMind News
V
V2EX
大猫的无限游戏
大猫的无限游戏
阮一峰的网络日志
阮一峰的网络日志
S
Security Affairs
L
LangChain Blog
The Hacker News
The Hacker News
F
Full Disclosure
aimingoo的专栏
aimingoo的专栏
Hacker News - Newest:
Hacker News - Newest: "LLM"
腾讯CDC
Webroot Blog
Webroot Blog
A
About on SuperTechFans
H
Hacker News: Front Page
Cyberwarzone
Cyberwarzone
WordPress大学
WordPress大学
L
LINUX DO - 热门话题
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Attack and Defense Labs
Attack and Defense Labs
M
MIT News - Artificial intelligence

博客园 - 一名程序媛呀

httpx 传参总报错?这次把 GET、POST、文件上传到响应处理的坑给你一次填平 从卡顿到丝滑:FastAPI 调用外部 API 的正确姿势(httpx 实战) 还在 XHR、Fetch 和 Axios 之间纠结?我踩过的坑,希望你一个都不用碰到 你的REST接口还在“过度投喂”数据吗?——FastAPI + GraphQL实战避坑指南 Uvicorn、Gunicorn 傻傻分不清?FastAPI 生产部署避坑指南 Termux里的二进制和脚本,到底怎么运行才不踩坑?Termux-service 保活妙招! 刚部署的 LibreTranslate 频频翻车?我掏出了 20 年前的 StarDict 词典,用 FastAPI 搭了个本地词典翻译 API 别再用网页翻译看源码了!你的私人翻译神器LibreTranslate,部署避坑指南来了 掏出手机就能搭个 WebDAV 同步服务器?这操作有点香 别只盯着GitBook了!这个文档神器让你的笔记秒变网站 写爬虫时用了代理还被封?Python 代理的那些隐藏坑,我替你踩明白了 FastAPI 身份验证总踩坑?这份 FastAPI Users “避坑指南”请收好 旧手机别扔!用 Termux 搭个私人云盘,比网盘香多了 你的FastAPI又在服务器上“跑不起来”了?来,今天咱把打包这件事彻底聊透 写页面时别再把 Element Plus 整个搬进来啦!Vue3按需加载的坑我帮你踩平了 前端包管理咋选?我从npm叛逃到pnpm的血泪史(附避坑指南) 聊聊 fetch 使用中我踩过的那些坑和正确打开方式 FastApiAdmin 后端接口开发好了,前端管理界面怎么调用与显示? 给 FastApiAdmin 加个“会议纪要”模块,我把后端二次开发的坑踩了个遍 我用了FastApiAdmin后,连夜把踩过的坑都整理出来了 告别 Typora 后的新欢:我把所有笔记迁移到了 Obsidian 这个“第二大脑” 你的Agent API还在裸奔?从认证到沙箱,我用FastAPI搭了几道防线 让 FastAPI Agent 思考不阻塞:手把手教你实现异步任务与后台处理方案 让FastAPI Agent真正记住你:聊聊会话记忆与持久化存储的落地实践 FastAPI Agent 函数调用实战:我让 AI 学会了“自己动手查天气“ 初探:用 FastAPI 搭建你的第一个 AI Agent 接口 FastAPI 少有人提的实用技巧:把 Depends 依赖提到路由层,代码少写60% FastAPI 生产环境静态文件完全指南:从 /favicon.ico 404 到 HSTS 混合内容,一次全根治 用了loguru我才明白,Python日志还能这么写 FastAPI 后台任务:BackgroundTasks 的使用场景与注意事项 FastAPI配置管理避坑指南:从硬编码到 .env 与 pydantic_settings 类,连路由用法都给你捋清楚 FastAPI 文件上传避坑全指南:分块存盘、类型校验与安全兜底 FastAPI + Pydantic 模型终极实战手册:从能跑就行到固若金汤,这些技巧你一定用得上 FastAPI + SQLAlchemy 2.0 通用CRUD操作手册 —— 从同步到异步,一次讲透 FastAPI订单防超卖实战:从数据库锁到Saga分布式事务,这一篇给你理清了 FastAPI 生产环境避坑指南:用 Alembic 管理数据库迁移,别再手动改表结构了! FastAPI服务半夜又挂了?先别急着重启,查查你的数据库连接池“池子”是不是漏了 FastAPI数据库ORM怎么选?我肝了三个Demo后,终于不再纠结了 Vue 3 组件通信,别只会用 Props 和 Emits 了,这几个狠活儿你得看看 Vue 3 组合式 API 香是香,但从Vue2迁移时你可别像我当初一样踩进这 3 个深坑里 我用fastapi-scaff搭了个项目,两天工期缩到两小时,老板以为我开挂了 FastAPI+Vue:文件分片上传+秒传+断点续传,这坑我帮你踩平了! FastAPI自动生成的API文档太丑?我花了一晚上把它改成了客户愿意付费的样子 告别手写 API 胶水代码:FastAPI 与 Vue 的“契约自动机” OpenAPI 实战 FastAPI + Vue 前后端分离实战:我的项目结构“避坑指南” FastAPI + Celery 实战:异步任务里调用 Redis 和数据库的全解析,及生产级组织方案 FastAPI里玩转Redis和数据库的正确姿势,别让异步任务把你坑哭了! FastAPI + Celery 实战:异步任务的坑与解法,我帮你踩了一遍 FastAPI子应用挂载:别再让root_path坑你一夜 FastAPI项目半夜报警吵醒你?聊聊告警这事儿怎么搞! 别再数据线了!用FastAPI 5分钟搭个局域网文件+剪贴板神器 FastAPI单元测试实战:别等上线被喷才后悔,TestClient用对了真香! FastAPI状态共享秘籍:别再让中间件、依赖和路由“各自为政”了! FastAPI实战:WebSocket vs Socket.IO,这回真给我整明白了! 从0到1,FastAPI + PostgreSQL + Tortoise ORM 实战避坑指南 FastAPI + PostgreSQL 实战:给应用装上“缓存”和“日志”翅膀 FastAPI + PostgreSQL 实战:从入门到不踩坑,一次讲透
Anki插件开发必知必会:钩子函数与右键菜单定制
一名程序媛呀 · 2026-07-10 · via 博客园 - 一名程序媛呀

今天在使用Anki背单词时,编辑笔记内容的时候,想查看下某个单词是否已经添加了,每次都要去点开浏览器窗口再手动输入再搜索,
有时候是在卡片浏览器界面中编辑笔记内容,这时候重新搜索的时候,刚才编辑的笔记就要重新找,真真的不方便,

看到安装的有些插件可以添加自定义右键菜单项,想着是不是我也可以写一个这样的插件,来添加一些功能,方便操作。这样就不用每次都得切来切去,打断心流状态,还烦得不行。

说干就干,去搜索官方帮助文档,开始鼓捣Anki插件开发。

结果,刚开始就被浇了一盆凉水,官方文档的开头有这么一句话:
Because of our limited resources, no official support is available for add-on writing.

啥意思,“资源有限,插件开发这块没啥官方说明”,只有个大概简介,想研究,自己看源码!
我嘞个豆呀,这玩意这么火,这块还要自己研究?
好吧,谁让咱有这需求呢,接下来就是漫长页痛苦的Debug时间了……

好消息是,我能写这篇文章,肯定是跑通了功能,添加了菜单上去了,这里分享给有需求的小伙伴,至少知道往哪走,往哪努力吧,让你少掉几根头发!

🎯 核心摘要:本文能帮你解决什么

看完这篇,你能自己写出这样的Anki插件:

在卡片浏览器菜单中,添加“一键查单词”等菜单,点击后触发自定义逻辑。
核心就是两个东西——钩子函数菜单项定义

我会尽量用比喻的方式把它们的协作关系给你讲明白,后面也会给出可直接复用的代码骨架。

👩‍💻我是爱折腾的一名程序媛,喜欢研究全栈开发的各种实践,热爱分享踩坑后的收获与思考,也享受用代码写出各种实用小工具解决问题的快乐。

如果你也在技术这条路上向前走,关注我,愿我们能彼此陪伴,一起成为更好的自己 🌱

🗺️ 主要内容脉络

🔹 为啥要自定义右键菜单——场景共鸣

🔹 钩子函数到底是个啥——生活化比喻

🔹 菜单定义的核心代码骨架

🔹 实战:给浏览器卡片右键加上“查词典”

🔹 必知必看的注意事项与奇技淫巧

🧩 第一部分:那个让人抓狂的下午

起因的话,开头已经说过了,还有个需求就是在复习英语卡片时,遇到个生僻词,想查在线词典。
正常操作是:选中文本 → 右键复制 → 切到浏览器 → 粘贴搜索 → 再切回来。重复几次,耐心直接清零了,关键是费时呀!

当时我就想,如果能在Anki里右键直接调接口查词,还能顺手把释义填进字段,该多爽。
前几天咱们写的 FastAPI 自定义本地词典是不是也可以发光发热!

其实Anki插件体系还是很灵活的,关键是把“什么时候触发”和“触发后干什么”串起来。
前者靠钩子,后者靠你写的函数,而右键菜单或顶部菜单只是触发方式之一。

🎯 第二部分:核心原理——钩子就是厨房里的传菜铃

别被“钩子”这词吓到。把它想象成餐厅后厨的传菜铃:
厨师(Anki)做完一道工序,就“叮”地按一下铃(触发钩子),服务员(你的插件)听到后去上菜(执行自定义代码)。

其实就是这么简单,Anki内置了一堆钩子点,比如:

🔹 卡片浏览器加载完菜单时(browser.setupMenus

🔹 复习界面显示问题前(reviewer.didShowQuestion

🔹 卡片添加、删除、修改时……

我们定义右键菜单,其实就是告诉Anki:
“嗨,在浏览器右键菜单那个钩子触发时,帮我塞进去一个‘查词典’选项。”这背后对应的钩子叫 browser.setupMenus

官方文档虽然列出了,但新手容易忽略的是,你必须在这个钩子回调里去调用菜单对象的 addAction,否则菜单项死活不会出现。

还有一个大坑提醒:老版本Anki的钩子命名不太一样,比如 setupEditorButtonsbrowser.setupMenus 容易搞混。
如果你用的 Anki 版本 >= 2.1.55,认准 browser.setupMenus 就行。版本太低的话,赶紧升吧,好多好用的钩子都没有。

再说一点就是,Anki就算升级到了新版本,还是要注意下它里面的有些属性和函数,因为版本差异,就是死活找不到,这时候,你只能去源码里“移至定义”,慢慢找了,结合出错信息提示,会快一些!

⚙️ 第三部分:实战演示——三步给菜单加“查词典”功能

好,咱们直接上代码骨架。假设你已经在插件文件夹里新建了 __init__.py

第一步:导入必要的模块

from aqt import mw

from aqt.browser import Browser

from aqt.qt import QAction

from anki.hooks import addHook

import webbrowser

这里 mw 是主窗口对象,各种全局资源都通过它访问。

第二步:定义菜单添加函数,并绑定钩子

def setup_context_menu(browser: Browser):

    # 创建一个QAction,这就是菜单项

    action = QAction("🔍 一键查词典", browser)

    # 当用户点击时,执行查询函数

    action.triggered.connect(lambda: lookup_word(browser))

    # 把action塞进浏览器菜单项里

    browser.form.menuEdit.addAction(action)

addHook("browser.setupMenus", setup_context_menu)

注意啊,browser.form.menuEdit 是浏览器顶栏“编辑”菜单的对象。如果你想加到其他菜单项下,要看下源码里的命名,不然会报错,找不到对象。

如果要添加到右键菜单项中,我在卡片浏览器源码里没找到相应钩子,不过我需要的是在笔记编辑界面中添加右键菜单项,这个钩子函数我找到了,
就最下面代码里的editor_will_show_context_menu,使用方式上,和上面有点不同,但意思大差不差!

gui_hooks.editor_will_show_context_menu.append(setup_editor_context_menu)

第三步:实现查词函数

def lookup_word(browser: Browser):

    # 获取浏览器当前选中的文本

    selected = browser.current_card

    if not selected:

        return

    # 这里只是示例,实际可从卡片字段取词

    word = selected.note()["Front"]

    webbrowser.open(f"https://dict.youdao.com/search?q={word}")

这个例子是直接用电脑默认浏览器来查词典。
你完全可以结合 FastAPI 接口,把查询结果写回卡片字段,比如调个API后修改 note()["Back"]note().flush()

你可能会问:“那我怎么知道有啥钩子能用?”
最好的办法不是死记硬背,因为它可能因为版本不同,就变了,而是去Anki源码里搜 runHookaddHook 等关键词,看它本身是怎么调用的,
或者在插件开发文档里找 Hooks Reference

另外,用 aqt.utils.showInfo 打日志是最朴素的调试法,插件控制台不弹错误时,全靠它保命。

🔧 第四部分:注意事项与进阶思考

🔹 千万别在主线程里干重活
像调API、大量IO操作,记得用 mw.taskman.run_in_background 包起来,否则Anki直接卡死给你看。

🔹 菜单对象生命周期
不要在钩子外面预先创建 QAction 然后反复添加,每次 setupMenus 触发时都可能重建菜单,你只管每次新建并加入,系统会管理销毁,否则容易出重复菜单项。

🔹 多版本兼容
如果你想让插件同时兼容新旧Anki,可以加个 if hasattr(Browser, 'form') 之类的判断,但不如直接声明最低版本要求,省心。

再说个容易翻车的点:修改了代码后,记得重启Anki,网上说的什么“开发者模式”下的“重载插件”功能,我找了半天,我这个版本里也没有找到,还是重启来的干脆,还省了缓存干扰。

💬 来个小结

其实自定义Anki插件就是个搭积木的过程——钩子告诉你“哪里能插”,菜单定义告诉它“长什么样”,你的函数决定“插进去干什么”。
一旦捅破这层窗户纸,你会发现Anki的扩展性强得离谱。

好了,今天就聊到这啦!老样子,如果你觉得对你有那么一...点点收获,那就顺手点击收藏加个关注吧,防止找不到,也让我看到我的辛苦总结能帮到各位!
如果你在编写插件时还遇到什么解决不了的问题,也欢迎评论区里聊聊,我和你一起分析,咱们一起学习,一起进步、成长!🎯