示例插件

本页提供两个完整的示例插件,帮助你快速理解 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。

以示例为模板

  1. 复制示例目录,重命名为你的插件 ID(如 com.yourname.myplugin
  2. 修改 plugin.json 中的 idnameversion 等信息
  3. main.lua 中编写插件逻辑
  4. 更新 locales/ 下的国际化文件

了解更多

开发者文档
使用帮助
使用说明 脚本菜单 常见问题
脚本开发
开发指南
插件开发
快速开始 开发指南 示例插件
API 参考
概览 API 查询 插件信息 日志 Finder 上下文 插件设置 国际化
UI 与交互
对话框 进度条 系统通知 选择器 WebView 状态栏 Dock
文件与路径
文件操作 路径工具 Finder 操作 废纸篓 扩展属性 元数据 文件监听
数据格式
JSON Plist CSV XML PDF 图片
文本与编码
字符串 正则表达式 日期时间 颜色 加密编码
系统
Shell 命令 进程管理 应用管理 系统信息 AppleScript 快捷指令
系统信息
网络信息 电源/电池 屏幕/外观 音频控制 蓝牙设备 位置服务
网络
HTTP 请求 WebSocket URL 工具
输入与剪贴板
键盘模拟 鼠标模拟 全局热键 剪贴板 窗口管理
存储
SQLite Keychain UserDefaults
媒体
文字识别 二维码
工具
归档 类型标识 分享 定时器 防休眠 并发/协程