MoeCopy AI LogoMoeCopy AI
开发者文档

API 参考

MoeCopy AI 的技术 API 文档和开发者参考

本文档详细介绍 MoeCopy AI 的内部 API、消息通信机制和扩展接口。

消息通信 API

MoeCopy AI 使用 Chrome Extension 的消息传递机制进行组件间通信。

消息格式

所有消息遵循统一格式:

interface Message {
  action: string
  [key: string]: any
}

核心消息类型

scrapeContent

从当前页面提取内容:

// 请求
{
  action: "scrapeContent",
  options?: {
    mode?: ExtractionMode
    customSelectors?: Partial<Record<SelectorType, string>>
  }
}

// 响应
interface ScrapeResponse {
  success: boolean
  data?: ScrapedContent
  error?: string
}

testSelector

测试 CSS 选择器:

// 请求
interface SelectorTestRequest {
  action: "testSelector"
  selector: string
}

// 响应
interface SelectorTestResponse {
  matches: number
  content?: string
  error?: string
}

openOptionsPage

打开扩展选项页面:

{
  action: "openOptionsPage"
}

openSidePanel

v0.2.0 新增

打开侧边栏面板:

{
  action: "openSidePanel"
}

extractLinksFromPage

v0.2.0 新增

从页面指定元素中提取链接:

// 请求
interface ExtractLinksRequest {
  action: "extractLinksFromPage"
  selector: string // CSS 选择器
}

// 响应
interface ExtractLinksResponse {
  success: boolean
  links?: ExtractedLink[]
  elementInfo?: SelectedElementInfo
  error?: string
}

scrapeViaTab

v0.2.0 新增

通过后台标签页抓取指定 URL 的内容:

// 请求
interface ScrapeViaTabRequest {
  action: "scrapeViaTab"
  url: string
  timeout?: number
}

// 响应
interface ScrapeViaTabResponse {
  success: boolean
  data?: {
    title: string
    content: string
    url: string
  }
  error?: string
}

clickNextPage

v0.2.0 新增

点击下一页按钮并提取新链接:

// 请求
interface ClickNextPageRequest {
  action: "clickNextPage"
  xpath: string // 下一页按钮的 XPath
  linkContainerSelector: string // 链接容器的选择器
}

// 响应
interface ClickNextPageResponse {
  success: boolean
  links?: ExtractedLink[]
  hasNextPage?: boolean
  error?: string
}

数据结构

ScrapedContent

提取内容的完整结构:

interface ScrapedContent {
  // 基础信息
  title: string
  url: string
  author: string
  publishDate: string

  // 内容
  articleContent: string // 原始 HTML
  cleanedContent: string // 清理后的文本

  // 元数据
  metadata: Record<string, string>

  // 图片
  images: ImageInfo[]

  // 选择器结果(调试用)
  selectorResults?: Record<SelectorType, SelectorResultItem[]>
}

ImageInfo

图片信息结构:

interface ImageInfo {
  src: string // 图片 URL
  alt: string // 替代文本
  title: string // 标题
  index: number // 在文档中的索引
}

SelectorResultItem

选择器匹配结果:

interface SelectorResultItem {
  selector: string // 使用的选择器
  content: string // 第一个匹配内容
  allContent?: string[] // 所有匹配内容
}
v0.2.0 新增

提取的链接信息:

interface ExtractedLink {
  url: string // 链接地址
  text: string // 链接文本
  index: number // 在列表中的索引
}

SelectedElementInfo

v0.2.0 新增

选中元素的信息:

interface SelectedElementInfo {
  tagName: string // 标签名
  className: string // 类名
  id: string // ID
  linkCount: number // 包含的链接数量
  outerHTML: string // 外层 HTML
  selector?: string // 用于定位元素的 CSS 选择器
}

BatchScrapeResult

v0.2.0 新增

单页抓取结果:

interface BatchScrapeResult {
  url: string
  success: boolean
  title: string
  content: string
  error?: string
  method: ScrapeStrategyType // 'fetch' | 'background-tabs' | 'current-tab'
}

BatchProgress

v0.2.0 新增

批量抓取进度:

interface BatchProgress {
  total: number
  completed: number
  current: {
    url: string
    status: PageScrapeStatus // 'pending' | 'fetching' | 'extracting' | 'success' | 'failed'
  } | null
  results: Array<{
    url: string
    status: "success" | "failed"
    title?: string
    error?: string
  }>
  startTime: number
  isPaused: boolean
}

ExtractedContent

v0.2.0 新增

内容提取结果(多格式):

interface ExtractedContent {
  html: string // element.outerHTML (完整 HTML)
  markdown: string // 转换后的 Markdown
  text: string // element.textContent (纯文本)
  elementInfo: SelectedElementInfo
}

NextPageButtonInfo

v0.2.0 新增

下一页按钮信息:

interface NextPageButtonInfo {
  xpath: string // XPath 路径
  text: string // 按钮文本
  description?: string // 描述
}

存储 API

设置存储

使用 Chrome 同步存储保存用户设置:

import { Storage } from "@plasmohq/storage"

const storage = new Storage()

// 保存设置
await storage.set("settings", {
  extractionMode: "hybrid",
  showFloatButton: true
})

// 读取设置
const settings = await storage.get("settings")

本地存储

AI 聊天历史使用本地存储:

// 保存聊天历史
interface AiChatHistoryItem {
  id: string
  timestamp: number
  url: string
  prompt: string
  content: string
  processedPrompt?: string
  usage?: {
    total_tokens?: number
    prompt_tokens?: number
    completion_tokens?: number
  }
}

// 存储操作
const history = (await storage.get("aiChatHistory")) || { items: [] }
history.items.push(newItem)
await storage.set("aiChatHistory", history)

工具函数

文本处理

// 清理 HTML
export function cleanHtml(html: string): string

// 提取纯文本
export function extractText(element: Element): string

// 格式化日期
export function formatDate(dateString: string): string

选择器工具

// 查找元素
export function findElements(
  selectors: string[],
  root?: Document | Element
): Element[]

// 获取元素文本
export function getElementText(
  element: Element,
  options?: { trim?: boolean }
): string

模板引擎

支持变量替换的简单模板引擎:

export function processTemplate(
  template: string,
  variables: Record<string, any>
): string

// 使用示例
const result = processTemplate("标题:{{title}}\n作者:{{author}}", {
  title: "示例文章",
  author: "张三"
})

AI 服务接口

初始化 AI 客户端

import { createAiClient } from "~/utils/ai-service"

const client = createAiClient({
  apiKey: "your-api-key",
  apiUrl: "https://api.openai.com/v1",
  model: "gpt-4o-mini"
})

生成文本

const stream = await generateText({
  prompt: "总结这篇文章",
  content: articleContent,
  systemPrompt: "你是一个专业的内容摘要助手"
})

// 处理流式响应
for await (const chunk of stream) {
  console.log(chunk)
}

使用统计

interface UsageStats {
  promptTokens: number
  completionTokens: number
  totalTokens: number
}

const { text, usage } = await generateTextWithUsage(options)

抓取策略 API

v0.2.0 新增

批量抓取支持三种策略,每种策略适用于不同场景。

策略类型

type ScrapeStrategyType = "fetch" | "background-tabs" | "current-tab"
策略描述并发支持适用场景
fetch使用 Fetch API 直接获取支持静态页面,速度快
background-tabs在后台标签页中加载支持JS 渲染页面,需要登录态
current-tab在当前标签页中依次访问不支持需要监控过程,调试用

创建策略实例

import { createScrapeStrategy } from "~utils/scrape-strategies"

const strategy = createScrapeStrategy("fetch")

// 执行抓取
const result = await strategy.scrape(url, options)

策略接口

interface ScrapeStrategy {
  scrape(url: string, options?: ScrapeOptions): Promise<BatchScrapeResult>
}

interface ScrapeOptions {
  timeout?: number
  signal?: AbortSignal
}

获取策略信息

import { getStrategyInfo } from "~utils/scrape-strategies"

const info = getStrategyInfo("background-tabs")
// { name: '后台标签页', description: '...', supportsConcurrency: true }

链接过滤 API

v0.2.0 新增

过滤类型

import type { FilterTarget, FilterMode } from "~constants/link-filter-presets"

type FilterTarget = "url" | "text" | "both"
type FilterMode = "exclude" | "include"

预设规则

import { LINK_FILTER_PRESETS } from "~constants/link-filter-presets"

interface PresetFilter {
  id: string
  name: string
  pattern: string // 正则表达式
  target: FilterTarget
  mode: FilterMode
  description: string
}

内置预设:

  • exclude-images - 排除图片链接
  • exclude-anchors - 排除锚点链接
  • exclude-pagination - 排除分页链接
  • exclude-auth - 排除登录/注册
  • exclude-assets - 排除静态资源
  • exclude-media - 排除媒体链接
  • include-docs - 仅保留文档链接
  • include-html - 仅保留网页链接

使用过滤 Hook

import { useLinkFilter } from "~hooks/useLinkFilter"

const {
  pattern,
  target,
  mode,
  isValid,
  error,
  filteredLinks,
  setPattern,
  setTarget,
  setMode,
  applyPreset,
  clear
} = useLinkFilter(links)

ZIP 导出 API

v0.2.0 新增

导出为 ZIP

import { exportAsZip, generateZipFilename } from "~utils/zip-exporter"

const blob = await exportAsZip(results, {
  includeIndex: true,
  filenameFormat: "title", // 'title' | 'url' | 'index'
  maxFilenameLength: 50
})

// 下载 ZIP 文件
const filename = generateZipFilename() // 'batch-scrape-2025-12-31.zip'
downloadBlob(blob, filename)

导出选项

interface ZipExportOptions {
  includeIndex: boolean // 是否包含 index.md 索引文件
  filenameFormat: "title" | "url" | "index" // 文件命名方式
  maxFilenameLength: number // 文件名最大长度
}

ZIP 文件结构

batch-scrape-2025-12-31.zip
├── index.md           # 汇总索引(可选)
└── docs/
    ├── 001-文章标题.md
    ├── 002-另一篇文章.md
    └── ...

提取器 API

创建提取器实例

import { createExtractor } from "~/utils/extractor"

const extractor = createExtractor({
  mode: "hybrid",
  customSelectors: {
    content: ".custom-content",
    title: "h1.custom-title"
  },
  readabilityConfig: {
    charThreshold: 500
  }
})

提取内容

const result = await extractor.extract(document)

// 结果包含
{
  title: string
  content: string
  author: string
  publishDate: string
  images: ImageInfo[]
  metadata: Record<string, string>
}

日志系统

配置日志

import { logger } from "~/utils/logger"

// 设置日志级别
logger.setLevel("debug")

// 使用日志
logger.debug("调试信息", { data })
logger.info("一般信息")
logger.error("错误信息", error)

日志格式

interface LogEntry {
  level: string
  timestamp: number
  message: string
  data?: any
  error?: Error
}

错误处理

错误类型

// 基础错误类
export class MoeCopyError extends Error {
  code: string
  details?: any
}

// 具体错误类型
export class ExtractionError extends MoeCopyError
export class SelectorError extends MoeCopyError
export class ApiError extends MoeCopyError

错误处理示例

try {
  const content = await extractor.extract(document)
} catch (error) {
  if (error instanceof ExtractionError) {
    // 处理提取错误
    logger.error("提取失败", error)
  } else if (error instanceof ApiError) {
    // 处理 API 错误
    logger.error("API 调用失败", error)
  }
}

扩展生命周期

Background Script

// background.ts
chrome.runtime.onInstalled.addListener(() => {
  // 安装或更新时执行
  initializeExtension()
})

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  // 处理消息
  handleMessage(message, sender, sendResponse)
  return true // 异步响应
})

Content Script

// contents/scraper.ts
// 页面加载完成后注入
window.addEventListener("load", () => {
  initializeScraper()
})

// 监听来自扩展的消息
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.action === "scrapeContent") {
    const content = scrapeCurrentPage()
    sendResponse(content)
  }
})

性能优化

缓存机制

// 简单的内存缓存
const cache = new Map<string, CacheEntry>()

interface CacheEntry {
  data: any
  expires: number
}

export function getCached<T>(key: string): T | null {
  const entry = cache.get(key)
  if (entry && entry.expires > Date.now()) {
    return entry.data
  }
  return null
}

防抖和节流

// 防抖函数
export function debounce<T extends (...args: any[]) => any>(
  func: T,
  wait: number
): T

// 节流函数
export function throttle<T extends (...args: any[]) => any>(
  func: T,
  limit: number
): T

开发工具

类型定义

完整的 TypeScript 类型定义位于:

  • constants/types.ts - 核心类型(包含批量抓取、内容提取相关类型)
  • constants/link-filter-presets.ts - 链接过滤预设和类型
  • utils/scrape-strategies/types.ts - 抓取策略类型

测试工具

// 模拟 Chrome API
import { mockChrome } from "~/test-utils"

beforeEach(() => {
  global.chrome = mockChrome()
})

// 测试提取器
it("should extract content", async () => {
  const extractor = createExtractor()
  const result = await extractor.extract(mockDocument)
  expect(result.title).toBe("Test Title")
})

版本兼容性

Chrome Manifest V3

MoeCopy AI 完全兼容 Manifest V3:

  • 使用 Service Worker 替代 Background Page
  • 遵循新的权限模型
  • 支持新的 API 限制

贡献指南

欢迎贡献代码!请查看 GitHub 仓库 了解:

  • 代码规范
  • 提交指南
  • 问题反馈
Edit on GitHub

Last updated on

On this page