为 Motrix 添加剪贴板监听功能(实战)

Motrix 添加剪贴板监听功能。这个实现分为几个关键部分:

1. 安装依赖

首先在 Motrix 项目目录安装必要的依赖:

npm install ffi-napi ref-napi ref-struct-napi clipboardy
# 或者使用 yarn
yarn add ffi-napi ref-napi ref-struct-napi clipboardy

2. 主进程代码修改

在 Motrix 的主进程文件(通常是 src/main/index.jssrc/main/index.ts)中添加以下代码:

// 导入必要的模块
import { BrowserWindow, clipboard } from 'electron'
import ffi from 'ffi-napi'
import ref from 'ref-napi'
import StructType from 'ref-struct-napi'

// 定义 Windows API 类型
const HWND = ref.refType(ref.types.void)
const BOOL = ref.types.bool
const UINT = ref.types.uint32
const WPARAM = ref.types.uint32
const LPARAM = ref.types.uint32
const LRESULT = ref.types.uint32

// Windows 消息常量
const WM_CLIPBOARDUPDATE = 0x031D

// 加载 user32.dll
const user32 = ffi.Library('user32.dll', {
  'AddClipboardFormatListener': [BOOL, [HWND]],
  'RemoveClipboardFormatListener': [BOOL, [HWND]],
  'GetClipboardSequenceNumber': [UINT, []]
})

class ClipboardMonitor {
  constructor(mainWindow) {
    this.mainWindow = mainWindow
    this.isMonitoring = false
    this.lastSequenceNumber = 0
    this.downloadUrlPatterns = [
      // HTTP/HTTPS/FTP 下载链接
      /https?:\/\/[^\s<>"']+\.(zip|rar|7z|exe|msi|dmg|apk|iso|tar\.gz|tar\.bz2|mp4|mp3|pdf)/i,
      // 磁力链接
      /magnet:\?xt=urn:[a-z0-9]+:[a-zA-Z0-9]+/i,
      // BT种子文件链接
      /http.+\/announce/i,
      // ED2K链接
      /ed2k:\/\/\|file\|[^\|]+\|/i,
      // Thunder链接
      /thunder:\/\/[a-zA-Z0-9]+/i,
      // 通用URL(如果有文件扩展名或常见下载参数)
      /https?:\/\/[^\s<>"']+\?.*\b(?:download|file|id)=[^&\s]+/i,
      // 包含常见文件扩展名的链接(即使没有协议前缀)
      /(?:https?:\/\/)?[^\s<>"']+\.(zip|rar|7z|exe|msi|dmg|apk|iso)(?:\?[^\s]*)?/i
    ]
  }

  // 开始监听
  start() {
    if (this.isMonitoring) return
    
    const hwnd = this.getWindowHandle()
    if (!hwnd) {
      console.error('无法获取窗口句柄')
      return
    }

    // 注册剪贴板监听
    const result = user32.AddClipboardFormatListener(hwnd)
    if (result) {
      this.isMonitoring = true
      console.log('剪贴板监听已启动')
      
      // 监听窗口消息
      this.setupMessageListener()
    } else {
      console.error('启动剪贴板监听失败')
    }
  }

  // 停止监听
  stop() {
    if (!this.isMonitoring) return
    
    const hwnd = this.getWindowHandle()
    if (hwnd) {
      user32.RemoveClipboardFormatListener(hwnd)
      this.isMonitoring = false
      console.log('剪贴板监听已停止')
    }
  }

  // 获取窗口原生句柄
  getWindowHandle() {
    if (!this.mainWindow || this.mainWindow.isDestroyed()) {
      return null
    }
    
    const handleBuffer = this.mainWindow.getNativeWindowHandle()
    if (handleBuffer) {
      // 将Buffer转换为指针
      return ref.address(handleBuffer)
    }
    return null
  }

  // 设置消息监听
  setupMessageListener() {
    if (!this.mainWindow || this.mainWindow.isDestroyed()) return

    // 监听窗口消息
    this.mainWindow.hookWindowMessage(WM_CLIPBOARDUPDATE, (wParam, lParam) => {
      this.handleClipboardUpdate()
    })
  }

  // 处理剪贴板更新
  handleClipboardUpdate() {
    try {
      // 获取剪贴板序列号,避免重复处理
      const currentSeq = user32.GetClipboardSequenceNumber()
      if (currentSeq === this.lastSequenceNumber) {
        return
      }
      this.lastSequenceNumber = currentSeq

      // 读取剪贴板文本
      const clipboardText = clipboard.readText()
      if (!clipboardText) return

      // 提取下载链接
      const downloadLinks = this.extractDownloadLinks(clipboardText)
      
      if (downloadLinks.length > 0) {
        this.showDownloadDialog(downloadLinks)
      }
    } catch (error) {
      console.error('处理剪贴板更新时出错:', error)
    }
  }

  // 提取下载链接
  extractDownloadLinks(text) {
    const links = new Set() // 使用Set去重
    
    // 按行分割文本
    const lines = text.split(/\r?\n/)
    
    for (const line of lines) {
      // 跳过空行
      if (!line.trim()) continue
      
      // 检查是否匹配下载链接模式
      for (const pattern of this.downloadUrlPatterns) {
        const matches = line.match(pattern)
        if (matches) {
          // 返回完整的匹配结果
          links.add(matches[0])
          break
        }
      }
    }
    
    return Array.from(links)
  }

  // 显示下载对话框
  showDownloadDialog(links) {
    if (links.length === 1) {
      // 单个链接,直接创建下载任务
      this.createDownloadTask(links[0])
    } else if (links.length > 1) {
      // 多个链接,询问用户
      const result = this.mainWindow.webContents.executeJavaScript(`
        new Promise((resolve) => {
          const links = ${JSON.stringify(links)}
          const message = '检测到 ' + links.length + ' 个下载链接:\\n\\n' + 
                         links.join('\\n') + '\\n\\n是否全部添加?'
          
          if (confirm(message)) {
            resolve('all')
          } else {
            resolve('single')
          }
        })
      `)
      
      result.then(choice => {
        if (choice === 'all') {
          links.forEach(link => this.createDownloadTask(link))
        } else {
          // 让用户选择其中一个
          this.mainWindow.webContents.executeJavaScript(`
            new Promise((resolve) => {
              const links = ${JSON.stringify(links)}
              let list = '请选择要下载的链接:\\n'
              links.forEach((link, index) => {
                list += (index + 1) + '. ' + link + '\\n'
              })
              const choice = prompt(list, '1')
              resolve(parseInt(choice) - 1)
            })
          `).then(index => {
            if (index >= 0 && index < links.length) {
              this.createDownloadTask(links[index])
            }
          })
        }
      })
    }
  }

  // 创建下载任务
  createDownloadTask(url) {
    if (!this.mainWindow || this.mainWindow.isDestroyed()) return
    
    // 发送给渲染进程创建下载任务
    this.mainWindow.webContents.send('create-download-task', {
      url: url,
      autoStart: true
    })
    
    // 显示窗口(如果最小化或隐藏)
    if (this.mainWindow.isMinimized()) {
      this.mainWindow.restore()
    }
    this.mainWindow.show()
    this.mainWindow.focus()
  }
}

// 在 Motrix 主窗口创建后初始化
function setupClipboardMonitor(mainWindow) {
  // 创建监听器实例
  const monitor = new ClipboardMonitor(mainWindow)
  
  // 启动监听
  monitor.start()
  
  // 监听窗口关闭事件
  mainWindow.on('closed', () => {
    monitor.stop()
  })
  
  return monitor
}

// 导出供 Motrix 主进程使用
export { setupClipboardMonitor, ClipboardMonitor }

3. 在 Motrix 主进程中集成

在 Motrix 的主进程入口文件(通常是 src/main/index.js)中找到创建主窗口的位置:

// 在创建 BrowserWindow 之后
function createMainWindow() {
  const mainWindow = new BrowserWindow({
    // ... 现有配置
  })
  
  // 设置剪贴板监听
  if (process.platform === 'win32') {
    // 延迟启动,确保窗口完全创建
    setTimeout(() => {
      const { setupClipboardMonitor } = require('./clipboard-monitor')
      setupClipboardMonitor(mainWindow)
    }, 1000)
  } else {
    // macOS 和 Linux 可以使用替代方案
    setupCrossPlatformClipboardMonitor(mainWindow)
  }
  
  return mainWindow
}

4. 跨平台兼容版本

对于 macOS 和 Linux,可以使用轮询方式实现简单的剪贴板监听:

// 跨平台剪贴板监听(轮询方式)
class CrossPlatformClipboardMonitor {
  constructor(mainWindow) {
    this.mainWindow = mainWindow
    this.lastText = ''
    this.interval = null
    this.checkInterval = 1000 // 每秒检查一次
    this.downloadUrlPatterns = [
      // ... 同样的正则表达式
    ]
  }
  
  start() {
    if (this.interval) return
    
    this.lastText = clipboard.readText()
    this.interval = setInterval(() => {
      this.checkClipboard()
    }, this.checkInterval)
    
    console.log('跨平台剪贴板监听已启动')
  }
  
  stop() {
    if (this.interval) {
      clearInterval(this.interval)
      this.interval = null
      console.log('跨平台剪贴板监听已停止')
    }
  }
  
  checkClipboard() {
    try {
      const currentText = clipboard.readText()
      if (currentText && currentText !== this.lastText) {
        this.lastText = currentText
        this.handleClipboardChange(currentText)
      }
    } catch (error) {
      console.error('检查剪贴板时出错:', error)
    }
  }
  
  handleClipboardChange(text) {
    // ... 同样的链接提取和下载逻辑
  }
}

5. 渲染进程处理

在渲染进程中(例如 src/renderer/components/ 中的某个组件),添加事件监听:

// 在渲染进程中监听创建下载任务事件
import { ipcRenderer } from 'electron'

export default {
  mounted() {
    ipcRenderer.on('create-download-task', this.handleNewDownloadTask)
  },
  
  beforeDestroy() {
    ipcRenderer.removeListener('create-download-task', this.handleNewDownloadTask)
  },
  
  methods: {
    handleNewDownloadTask(event, taskInfo) {
      // 调用 Motrix 现有的新建任务方法
      this.$store.dispatch('createTask', {
        url: taskInfo.url,
        // ... 其他选项
      })
      
      // 可选:显示通知
      this.$notify({
        title: '检测到下载链接',
        message: `已添加: ${taskInfo.url.substring(0, 50)}...`,
        type: 'success'
      })
    }
  }
}

6. 配置选项

最后,可以在 Motrix 的设置界面添加开关选项:

// 在设置状态管理中添加配置
const settings = {
  // ... 现有设置
  monitorClipboard: true,
  monitorClipboardInterval: 1000,
  autoCreateTask: true,
  showNotification: true
}

// 在设置界面添加复选框
// <el-checkbox v-model="settings.monitorClipboard">启用剪贴板监听</el-checkbox>
// <el-checkbox v-model="settings.autoCreateTask">自动创建下载任务</el-checkbox>

使用说明

  1. 将以上代码添加到 Motrix 源码对应位置
  2. 安装依赖后重新构建 Motrix
  3. 启动应用后,复制任何包含下载链接的文本,Motrix 会自动检测并弹出下载对话框

这个实现包含了以下特性:

  • Windows 原生监听(实时响应)
  • 跨平台备用方案(轮询)
  • 智能链接提取(支持多种链接格式)
  • 多链接处理(可选择全部下载或单个选择)
  • 配置选项(可开关)

需要我详细解释某个部分,或者帮你调整正则表达式来匹配更多下载链接类型吗?

已有 19 条评论

    1. vulkanvega_heEn vulkanvega_heEn

      In the bustling world of online gaming, the vulkanvegas app android casino stands out as a versatile platform for players seeking excitement and reliability. It delivers smooth navigation, secure payments, and a rich library of games that appeal to both newcomers and seasoned bettors. Users appreciate the quick setup process and the intuitive interface that makes it easy to find favorite titles and new releases alike.

      The app provides a solid range of slots, table games, and live dealer experiences that are designed to simulate the thrill of a real casino. With high-quality graphics and responsive controls, players can enjoy immersive sessions on the go. The Android version emphasizes fast loading times and stable performance, even on devices with modest specs. Innovative features, such as boosted bonuses and personalized recommendations, further enhance the gaming journey.

      For beginners, tutorials and helpful tips are readily available within the VulkanVegas ecosystem, helping newcomers learn rules and strategies. The platform also emphasizes responsible gambling, offering tools for setting limits and monitoring activity. Regular updates ensure compatibility with the latest Android versions and security improvements. Customer support is reachable through multiple channels, providing timely assistance whenever needed.

      Security and fairness lie at the core of the vulkanvegas app android casino, with encryption protocols and certified random number generators ensuring trusted play. Users can deposit and withdraw using a variety of methods, backed by robust fraud detection and transparent terms. The app maintains a clear privacy policy and strong data protection measures to safeguard personal information. Overall, the Vulkan Vegas Android app offers a compelling blend of accessibility, entertainment, and safety for casino enthusiasts on mobile devices.
      vulkanbet casino app [url=https://vulkan-vegas.nz/app]https://vulkan-vegas.nz/app/[/url]

    2. 黑 方 黑 方 作者

      have you

    3. Motrix真爱粉 Motrix真爱粉

      用了Motrix三年了,一直希望能自动监听剪贴板。今天看到这篇文章激动坏了,马上动手改源码。希望楼主能持续维护,等官方版本更新的时候也能同步更新。

    4. TypeScript党 TypeScript党

      代码要是用TS写就更好了,加上类型定义能避免很多运行时错误。不过楼主可能是为了演示方便用了JS。期待后续能出个TS版本。

    5. 性能控 性能控

      轮询间隔1000ms在低配电脑上没问题,但笔记本为了省电,建议改成3000ms,或者在应用失焦时暂停监听。可以加个电量状态判断。

    6. 安卓开发者 安卓开发者

      之前用Flutter写过类似的下载器,发现剪贴板监听在移动端权限限制很多。还是桌面端自由,Electron真香。

    7. Mac用户 Mac用户

      macOS上用轮询方案,发现复制大文本时会卡一下,可能是clipboard.readText()太频繁了。建议加个防抖,或者判断剪贴板序列号(macOS也能用)。

    8. BugHunter BugHunter

      正则表达式那块有个小问题:匹配通用URL时,`\b(?:download|file|id)=`会把一些不是下载链接的也匹配上。建议再加个文件扩展名的判断,或者让用户自己配置关键词。

    9. 老司机 老司机

      回复Electron小白:hookWindowMessage是Electron的BrowserWindow实例方法,在渲染进程不可用。文档确实比较少提,属于底层API,用来监听原生窗口消息的。

    10. Electron小白 Electron小白

      请教一下,hookWindowMessage这个方法我在文档里没见过,是Electron的API吗?还是Node.js的?搜了一下没找到相关文档。

    11. 设计师小明 设计师小明

      作为一个不会写代码的设计师,虽然看不懂具体实现,但看楼主写得这么详细,感觉很靠谱。希望Motrix官方能采纳这个PR,真的需要剪贴板监听功能。