开发者文档
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[] // 所有匹配内容
}ExtractedLink
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