11import { Octokit } from "@octokit/rest" ;
22import { JSDOM } from "jsdom" ;
33import fs , { readFile } from "fs/promises" ;
4- import path from "path" ;
4+ import path , { join } from "path" ;
55import { fileURLToPath } from "url" ;
66import { execSync , spawnSync } from "child_process" ;
7+ import { visualizeTextDiff } from "./text-diff-visualizer" ;
78
89const __dirname = path . dirname ( fileURLToPath ( import . meta. url ) ) ;
910
1011const GITHUB_TOKEN = process . env . GITHUB_TOKEN ;
1112const OPENROUTER_API_KEY = process . env . OPENROUTER_API_KEY ;
13+ const IMGBB_API_KEY = process . env . IMGBB_API_KEY || "" ;
1214const REPO_OWNER = process . env . GITHUB_REPOSITORY_OWNER || "owner" ;
1315const REPO_NAME = process . env . GITHUB_REPOSITORY ?. split ( "/" ) [ 1 ] || "cppdoc" ;
1416const LABEL = "migrate-cppref-page" ;
@@ -51,7 +53,7 @@ function hasPRReference(title: string): boolean {
5153 return / \[ # \d + \] / . test ( title ) ;
5254}
5355
54- async function fetchPageContent ( url : string ) : Promise < { html : string ; title : string ; url : string } > {
56+ async function fetchPageContent ( url : string ) : Promise < { html : string ; title : string ; url : string ; innerText : string } > {
5557 const response = await fetch ( url ) ;
5658 if ( ! response . ok ) {
5759 throw new Error ( `Failed to fetch ${ url } : ${ response . status } ` ) ;
@@ -67,6 +69,7 @@ async function fetchPageContent(url: string): Promise<{ html: string; title: str
6769 html : contentElement . innerHTML ,
6870 title : headingElement ?. textContent ?. trim ( ) || "" ,
6971 url,
72+ innerText : contentElement . textContent ?. trim ( ) || "" ,
7073 } ;
7174}
7275
@@ -196,7 +199,7 @@ ${html}
196199}
197200
198201// https://cppreference.com/w/cpp/comments => src/content/docs/cpp/comments.mdx
199- function getRelativePath ( url : string ) : string {
202+ function getRelativeMDXPath ( url : string ) : string {
200203 const match = url . match ( / h t t p s ? : \/ \/ .* ?c p p r e f e r e n c e \. c o m \/ w \/ ( .+ ) \. h t m l $ / ) ;
201204 if ( ! match ) {
202205 throw new Error ( `无法从URL解析路径: ${ url } ` ) ;
@@ -205,10 +208,19 @@ function getRelativePath(url: string): string {
205208 return `src/content/docs/${ relative } .mdx` ;
206209}
207210
208- function getLocalPath ( url : string ) : string {
211+ function getRelativeHTMLPath ( url : string ) : string {
212+ const match = url . match ( / h t t p s ? : \/ \/ .* ?c p p r e f e r e n c e \. c o m \/ w \/ ( .+ ) \. h t m l $ / ) ;
213+ if ( ! match ) {
214+ throw new Error ( `无法从URL解析路径: ${ url } ` ) ;
215+ }
216+ const relative = match [ 1 ] ; // "cpp/comments"
217+ return `dist/${ relative } /index.html` ;
218+ }
219+
220+ function getLocalMDXPath ( url : string ) : string {
209221 return path . join (
210222 __dirname ,
211- ".." , getRelativePath ( url )
223+ ".." , getRelativeMDXPath ( url )
212224 ) ;
213225}
214226
@@ -223,17 +235,54 @@ description: Auto‑generated from cppreference
223235 console . log ( `写入 ${ filePath } ` ) ;
224236}
225237
226- async function createPullRequest ( issue : { number : number ; title : string } , filePath : string , url : string ) : Promise < number > {
238+ // curl --location --request POST "https://api.imgbb.com/1/upload?expiration=600&key=YOUR_CLIENT_API_KEY" --form "image=R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
239+ async function uploadImageToImgBB ( imageBuffer : Buffer ) : Promise < string > {
240+ const formData = new FormData ( ) ;
241+ formData . append ( "image" , new Blob ( [ new Uint8Array ( imageBuffer ) ] ) , "diff.svg" ) ;
242+
243+ const response = await fetch ( `https://api.imgbb.com/1/upload?expiration=600&key=${ IMGBB_API_KEY } ` , {
244+ method : "POST" ,
245+ body : formData ,
246+ } ) ;
247+
248+ if ( ! response . ok ) {
249+ const error = await response . text ( ) ;
250+ throw new Error ( `ImgBB API error: ${ error } ` ) ;
251+ }
252+
253+ const data = await response . json ( ) as { data : { url : string } } ;
254+ return data . data . url ;
255+ }
256+
257+ async function createPullRequest ( issue : { number : number ; title : string } , filePath : string , url : string , originalInnerText : string ) : Promise < number > {
227258 const branchName = `migrate/${ issue . number } -${ Date . now ( ) . toString ( 36 ) } ` ;
228259 const page = url . split ( "/w/" ) . pop ( ) ;
229260 const pageName = page ? page . replace ( ".html" , "" ) : "unknown" ;
230261 const prTitle = `feat: migrate ${ pageName } from cppref [#${ issue . number } ]` ;
231262 const commitMessage = prTitle ;
263+
264+ const newInnerText = await readFile ( getRelativeHTMLPath ( url ) , "utf8" ) . then ( ( data ) => {
265+ const dom = new JSDOM ( data ) ;
266+ const contentElement = dom . window . document . querySelector ( "main" ) ;
267+ return contentElement ?. textContent ?. trim ( ) || "" ;
268+ } ) . catch ( ( ) => "" ) ;
269+
270+ let imageUrl = null
271+ if ( originalInnerText && newInnerText ) {
272+ const svg = visualizeTextDiff ( originalInnerText , newInnerText ) ;
273+ if ( svg ) {
274+ imageUrl = await uploadImageToImgBB ( svg ) ;
275+ console . log ( `上传文本差异图像到 ImgBB: ${ imageUrl } ` ) ;
276+ }
277+ }
278+
232279 const prBody = `> 由 ${ MODEL_NAME } 自 ${ url } 自动迁移
233280>
234- > 📝 [编辑此页面](${ getRelativePath ( url ) } )
281+ > 📝 [编辑此页面](${ getRelativeMDXPath ( url ) } )
235282
236283<small>Close #${ issue . number } </small>
284+
285+ ${ imageUrl ? `` : "(无文本差异图像)" }
237286` ;
238287
239288 const { execSync } = await import ( "child_process" ) ;
@@ -322,12 +371,12 @@ async function main() {
322371 }
323372
324373 console . log ( ` 获取 ${ url } ` ) ;
325- const { html, title } = await retry ( ( ) => fetchPageContent ( url ) , 3 , 2000 ) ;
374+ const { html, title, innerText } = await retry ( ( ) => fetchPageContent ( url ) , 3 , 2000 ) ;
326375
327376 console . log ( ` 转换HTML为MDX...` ) ;
328377 const mdx = await retry ( ( ) => convertToMDX ( html , title , url ) , 3 , 2000 ) ;
329378
330- const filePath = getLocalPath ( url ) ;
379+ const filePath = getLocalMDXPath ( url ) ;
331380 console . log ( ` 写入 ${ filePath } ` ) ;
332381 await writeMDXFile ( filePath , mdx , title ) ;
333382
@@ -338,7 +387,7 @@ async function main() {
338387 }
339388
340389 console . log ( ` 创建PR...` ) ;
341- const prNumber = await createPullRequest ( issue , filePath , url ) ;
390+ const prNumber = await createPullRequest ( issue , filePath , url , innerText ) ;
342391
343392 console . log ( ` 更新issue...` ) ;
344393 await updateIssue ( issue , prNumber ) ;
0 commit comments