示例插件
本页提供两个完整的示例插件,帮助你快速理解 iRightMenu Pro 插件的开发模式。
hello-world — 最小示例
一个最简插件,演示基本的插件结构、菜单注册和系统通知。
适用场景:入门学习,理解插件基本结构。
目录结构
com.example.hello-world/
├── plugin.json # 插件清单
├── main.lua # 主脚本
└── locales/ # 国际化
├── en.json
└── zh-Hans.json
plugin.json
{
"id": "com.example.hello-world",
"name": "{{plugin.name}}",
"version": "1.0.0",
"author": "Your Name",
"description": "{{plugin.description}}",
"locale": "en",
"main": "main.lua",
"menus": [
{
"id": "greet",
"title": "{{menu.greet}}",
"description": "Show a greeting notification",
"enabled": true,
"showOnFiles": true,
"showOnFolders": true
},
{
"id": "count",
"title": "{{menu.count}}",
"description": "Count selected items",
"enabled": true,
"showOnFiles": true,
"showOnFolders": true,
"minFiles": 1
}
]
}
main.lua
-- 创建插件类
local HelloWorld = Plugin:extend()
-- 初始化:注册菜单处理函数
function HelloWorld:init()
app.log.info("[HelloWorld] Plugin initialized")
-- 注册与 plugin.json 中 menus[].id 对应的处理函数
self:registerHandler("greet", self.showGreeting)
self:registerHandler("count", self.countItems)
end
-- 处理函数:显示问候通知
function HelloWorld:showGreeting(context)
app.notification.show(
"Hello!",
"Welcome to iRightMenu Pro plugins!"
)
end
-- 处理函数:统计选中项数量
function HelloWorld:countItems(context)
local files = context.selectedFiles
local count = #files
local message
if count == 1 then
message = "1 item selected"
else
message = count .. " items selected"
end
app.notification.show("Count", message)
end
-- 返回插件类(引擎自动实例化)
return HelloWorld
国际化文件
locales/zh-Hans.json
{
"plugin.name": "你好世界",
"plugin.description": "一个简单的示例插件",
"menu.greet": "打招呼",
"menu.count": "统计数量"
}
file-processor — 进阶示例
一个功能更完整的插件,演示插件设置、进度条、文件操作、多菜单项、错误处理和国际化。
适用场景:学习进阶功能,作为生产级插件的起点。
目录结构
com.example.file-processor/
├── plugin.json
├── main.lua
└── locales/
├── en.json
└── zh-Hans.json
plugin.json
{
"id": "com.example.file-processor",
"name": "{{plugin.name}}",
"version": "1.0.0",
"author": "Your Name",
"description": "{{plugin.description}}",
"locale": "en",
"main": "main.lua",
"settings": {
"title": "{{settings.title}}",
"fields": [
{
"key": "output_format",
"type": "select",
"label": "{{settings.output_format}}",
"default": "txt",
"options": ["txt", "json", "csv"]
},
{
"key": "include_hidden",
"type": "checkbox",
"label": "{{settings.include_hidden}}",
"default": false
},
{
"key": "output_dir",
"type": "folder",
"label": "{{settings.output_dir}}",
"default": ""
}
]
},
"menus": [
{
"id": "list_files",
"title": "{{menu.list_files}}",
"description": "Generate a file list from selected items",
"enabled": true,
"showOnFiles": true,
"showOnFolders": true,
"minFiles": 1
},
{
"id": "calculate_hash",
"title": "{{menu.calculate_hash}}",
"description": "Calculate MD5 hash of selected files",
"enabled": true,
"showOnFiles": true,
"showOnFolders": false,
"minFiles": 1
}
]
}
settings 字段类型:
text(文本)、checkbox(开关)、select(下拉选择)、folder(文件夹选择器)。 插件通过app.settings.get(key, default)读取用户配置。
main.lua
local FileProcessor = Plugin:extend()
function FileProcessor:init()
self:registerHandler("list_files", self.listFiles)
self:registerHandler("calculate_hash", self.calculateHash)
end
-- ========================================
-- 生成文件列表
-- ========================================
function FileProcessor:listFiles(context)
local files = context.selectedFiles
if #files == 0 then
app.dialog.alert("Error", "No files selected")
return
end
-- 读取插件设置
local format = app.settings.get("output_format", "txt")
local includeHidden = app.settings.get("include_hidden", false)
local outputDir = app.settings.get("output_dir", "")
if outputDir == "" then
outputDir = context.currentDirectory
end
local outputFile = app.path.join(outputDir, "file-list." .. format)
-- 显示进度条
app.progress.show(app.i18n.t("progress.scanning"), {
message = app.i18n.t("progress.preparing"),
indeterminate = true
})
-- 收集文件信息
local fileList = {}
for _, path in ipairs(files) do
local basename = app.path.basename(path)
if includeHidden or not basename:match("^%.") then
table.insert(fileList, {
name = basename,
path = path,
size = app.file.size(path) or 0,
isDir = app.path.isDirectory(path)
})
end
end
app.progress.hide()
-- 根据设置格式化输出
local content
if format == "json" then
content = app.json.stringify(fileList, true)
elseif format == "csv" then
content = self:formatAsCSV(fileList)
else
content = self:formatAsTxt(fileList)
end
-- 写入文件
local success = app.file.write(outputFile, content)
if success then
app.notification.show(
app.i18n.t("notify.success"),
app.i18n.t("notify.file_created", outputFile)
)
app.finder.reveal(outputFile)
else
app.dialog.alert(
app.i18n.t("notify.error"),
app.i18n.t("notify.write_failed")
)
end
end
-- ========================================
-- 计算 MD5 哈希
-- ========================================
function FileProcessor:calculateHash(context)
local files = context.selectedFiles
-- 过滤目录,只保留文件
local regularFiles = {}
for _, path in ipairs(files) do
if app.path.isFile(path) then
table.insert(regularFiles, path)
end
end
if #regularFiles == 0 then
app.dialog.alert("Error", "No regular files selected")
return
end
-- 带进度的哈希计算
app.progress.show(app.i18n.t("progress.hashing"), {
message = "",
indeterminate = false
})
local results = {}
for i, path in ipairs(regularFiles) do
local percent = (i / #regularFiles) * 100
local filename = app.path.basename(path)
app.progress.update(percent, filename)
local hash = app.crypto.md5File(path)
if hash then
table.insert(results, { name = filename, hash = hash })
end
end
app.progress.hide()
-- 显示结果
if #results > 0 then
local message = ""
for _, r in ipairs(results) do
message = message .. r.name .. "\n" .. r.hash .. "\n\n"
end
app.dialog.alert(app.i18n.t("dialog.hash_results"), message)
end
end
-- ========================================
-- 工具函数
-- ========================================
function FileProcessor:formatAsTxt(fileList)
local lines = {}
for _, info in ipairs(fileList) do
local sizeStr = self:formatSize(info.size)
local typeStr = info.isDir and "[DIR]" or "[FILE]"
table.insert(lines, string.format("%s %s %s", typeStr, sizeStr, info.name))
end
return table.concat(lines, "\n")
end
function FileProcessor:formatAsCSV(fileList)
local lines = {"name,path,size,type"}
for _, info in ipairs(fileList) do
local typeStr = info.isDir and "directory" or "file"
table.insert(lines, string.format('"%s","%s",%d,%s',
info.name, info.path, info.size, typeStr))
end
return table.concat(lines, "\n")
end
function FileProcessor:formatSize(bytes)
if bytes < 1024 then
return string.format("%d B", bytes)
elseif bytes < 1024 * 1024 then
return string.format("%.1f KB", bytes / 1024)
elseif bytes < 1024 * 1024 * 1024 then
return string.format("%.1f MB", bytes / (1024 * 1024))
else
return string.format("%.2f GB", bytes / (1024 * 1024 * 1024))
end
end
return FileProcessor
国际化文件
locales/zh-Hans.json
{
"plugin.name": "文件处理器",
"plugin.description": "处理文件并生成报告",
"settings.title": "文件处理器设置",
"settings.output_format": "输出格式",
"settings.include_hidden": "包含隐藏文件",
"settings.output_dir": "输出目录",
"menu.list_files": "生成文件列表",
"menu.calculate_hash": "计算 MD5 哈希",
"progress.scanning": "扫描文件",
"progress.preparing": "准备中...",
"progress.hashing": "计算哈希值",
"notify.success": "成功",
"notify.error": "错误",
"notify.file_created": "文件已创建:{0}",
"notify.write_failed": "写入文件失败",
"dialog.hash_results": "MD5 哈希值"
}
快速测试
将示例插件复制到插件目录即可测试:
cp -r hello-world ~/Library/Group\ Containers/group.net.ymlab.iRightMenu.pro/Plugins/com.example.hello-world
然后在 iRightMenu Pro 中重新加载插件,或重启 Finder。
以示例为模板
- 复制示例目录,重命名为你的插件 ID(如
com.yourname.myplugin) - 修改
plugin.json中的id、name、version等信息 - 在
main.lua中编写插件逻辑 - 更新
locales/下的国际化文件