@@ -42,26 +42,119 @@ const extractScriptContent = (text) => {
4242 return "" ; // 如果没有找到匹配,返回空字符串
4343} ;
4444
45- const getLarkProjectInfoByDetail = ( text ) => {
45+ const getLarkProjectInfoByDetail = ( text , type , id ) => {
46+
47+ // 打印响应的前面部分,看看 HTML 结构
48+
4649 const reg = / < s c r i p t \b [ ^ < ] * (?: (? ! < \/ s c r i p t > ) < [ ^ < ] * ) * < \/ s c r i p t > / gi;
4750 const scripts = text . match ( reg ) ;
51+
52+ if ( ! scripts ) {
53+ return {
54+ error : true ,
55+ data : null ,
56+ } ;
57+ }
58+
4859 let content = "" ;
60+ let foundWorkItem = 0 ;
61+
62+ // 尝试多种模式查找数据
63+
4964 for ( let i = 0 ; i < scripts . length ; i ++ ) {
50- const text = scripts [ i ] ;
51- const isContain =
52- text . includes ( "window.detail" ) &&
53- text . includes ( `/work_item/${ type } /${ id } ` ) ;
65+ const scriptText = scripts [ i ] ;
5466
55- if ( isContain ) {
56- content = text ;
67+ // 旧版:window.detail + /work_item 路径
68+ const hasWindowDetail = scriptText . includes ( "window.detail" ) ;
69+ const hasWorkItemPath = scriptText . includes ( `/work_item/${ type } /${ id } ` ) ;
70+
71+ // 新版关键字
72+ const hasWorkItem = scriptText . includes ( "work_item" ) ;
73+ const hasIdString = scriptText . includes ( id ) ;
74+
75+ // 打印包含 work_item + id 的 script
76+ if ( hasWorkItem && hasIdString ) {
77+
78+ // 检查是否包含响应数据(有 "data" 或 "result" 字段)
79+ const hasDataField = scriptText . includes ( '%22data%22%3A' ) || scriptText . includes ( '"data":' ) ;
80+ const hasPayloadOnly = scriptText . includes ( '%22payload%22%3A' ) || scriptText . includes ( '"payload":' ) ;
81+
82+ // 检查 API 类型
83+ const isDemandFetch = scriptText . includes ( 'APIDemandFetchWorkItem' ) ;
84+ const isMinimalInfo = scriptText . includes ( 'APIFetchMinimalInfoWorkItem' ) ;
85+ const isFindBasic = scriptText . includes ( 'APIFindBasicWorkItemByIDV2' ) ;
86+
87+
88+ // 只检查包含响应数据的 script(不是 payload)
89+ if ( ! hasDataField && hasPayloadOnly ) {
90+ continue ;
91+ }
92+
93+ // 优先使用 APIDemandFetchWorkItem 或 APIFindBasicWorkItemByIDV2,它们的错误信息更准确
94+ // APIFetchMinimalInfoWorkItem 即使不存在也会返回 200,所以跳过
95+ if ( isMinimalInfo && ! isDemandFetch && ! isFindBasic ) {
96+ continue ;
97+ }
98+
99+ // 检查是否有错误标记(URL 编码格式)
100+ // "code":404 编码后是 %22code%22%3A404
101+ // "code":200 编码后是 %22code%22%3A200
102+ const hasError404 = scriptText . includes ( '%22code%22%3A404' ) || scriptText . includes ( '"code":404' ) ;
103+ const hasCode200 = scriptText . includes ( '%22code%22%3A200' ) || scriptText . includes ( '"code":200' ) ;
104+
105+
106+ // 如果有 404 错误,说明这个类型不存在,跳过
107+ if ( hasError404 ) {
108+ continue ;
109+ }
110+
111+ // 检查新格式的类型标识(需要检查 URL 编码和非编码两种格式)
112+ // URL 编码: %22work_item_api_name%22%3A%22story%22 -> "work_item_api_name":"story"
113+ const hasStoryType =
114+ scriptText . includes ( '"work_item_api_name":"story"' ) ||
115+ scriptText . includes ( '"work_item_type":"story"' ) ||
116+ scriptText . includes ( '%22work_item_api_name%22%3A%22story%22' ) ||
117+ scriptText . includes ( '%22work_item_type%22%3A%22story%22' ) ;
118+
119+ const hasIssueType =
120+ scriptText . includes ( '"work_item_api_name":"issue"' ) ||
121+ scriptText . includes ( '"work_item_type":"issue"' ) ||
122+ scriptText . includes ( '%22work_item_api_name%22%3A%22issue%22' ) ||
123+ scriptText . includes ( '%22work_item_type%22%3A%22issue%22' ) ;
124+
125+
126+ // 如果检测到类型匹配且没有错误,立即返回
127+ if ( hasStoryType && type === 'story' && hasCode200 ) {
128+ return {
129+ error : false ,
130+ data : { type : 'story' } ,
131+ } ;
132+ }
133+ if ( hasIssueType && type === 'issue' && hasCode200 ) {
134+ return {
135+ error : false ,
136+ data : { type : 'issue' } ,
137+ } ;
138+ }
139+ }
140+
141+ if ( hasWorkItemPath ) foundWorkItem ++ ;
142+
143+ // 旧版匹配规则
144+ if ( hasWindowDetail && hasWorkItemPath ) {
145+ content = scriptText ;
57146 break ;
58147 }
59148 }
60- if ( ! content )
149+
150+
151+ if ( ! content ) {
61152 return {
62153 error : true ,
63154 data : null ,
64155 } ;
156+ }
157+
65158 content = content . replace ( / \n / g, "" ) ;
66159 content = content . replace ( new RegExp ( "\\\\x3C" , "g" ) , "<" ) ;
67160 content = content . replace ( new RegExp ( "\x3C" , "g" ) , "<" ) ;
@@ -134,14 +227,34 @@ const getLarkProjectInfoByPrefetchList = (text) => {
134227 } ;
135228} ;
136229
230+ // 类型缓存:记录每个 tid 对应的实际类型
231+ const typeCache = new Map ( ) ;
232+
137233async function getLarkProjectInfo ( { tid, app } ) {
138- const [ t , id ] = tid . split ( "-" ) ;
139- const type = t === "m" ? "story" : "issue" ;
234+ const [ prefix , id ] = tid . split ( "-" ) ;
235+ const prefixLower = prefix . toLowerCase ( ) ;
236+
237+ // 快速路径:保持向后兼容
238+ let type = "story" ;
239+
240+ if ( prefixLower === "m" ) {
241+ type = "story" ;
242+ } else if ( prefixLower === "f" ) {
243+ type = "issue" ;
244+ } else {
245+ // 对于其他前缀,检查缓存
246+ if ( typeCache . has ( tid ) ) {
247+ type = typeCache . get ( tid ) ;
248+ } else {
249+ type = await detectProjectType ( app , id , tid ) ;
250+ }
251+ }
252+
140253 let url = `${ LARK_DOMAIN_HOST } /${ app } /${ type } /detail/${ id } ` ;
141254 const res = await fetch ( url ) ;
142255 const text = await res . text ( ) ;
143- let info = { tid } ;
144- const detailInfo = getLarkProjectInfoByDetail ( text ) ;
256+ let info = { tid, actualType : type } ;
257+ const detailInfo = getLarkProjectInfoByDetail ( text , type , id ) ;
145258 if ( detailInfo . data ) {
146259 info = {
147260 ...info ,
@@ -157,6 +270,56 @@ async function getLarkProjectInfo({ tid, app }) {
157270 return info ;
158271}
159272
273+ // 动态检测项目类型:先尝试 story,失败后尝试 issue
274+ async function detectProjectType ( app , id , tid ) {
275+
276+ // 先尝试 story
277+ try {
278+ let url = `${ LARK_DOMAIN_HOST } /${ app } /story/detail/${ id } ` ;
279+ const res = await fetch ( url ) ;
280+ const text = await res . text ( ) ;
281+
282+ const detailInfo = getLarkProjectInfoByDetail ( text , 'story' , id ) ;
283+ if ( detailInfo . data ) {
284+ typeCache . set ( tid , "story" ) ;
285+ return "story" ;
286+ }
287+
288+ const prefetchListInfo = getLarkProjectInfoByPrefetchList ( text ) ;
289+ if ( prefetchListInfo . data ) {
290+ typeCache . set ( tid , "story" ) ;
291+ return "story" ;
292+ }
293+ } catch ( e ) {
294+ console . error ( '[Lark Background] Story request failed:' , e . message ) ;
295+ }
296+
297+ // 尝试 issue
298+ try {
299+ let url = `${ LARK_DOMAIN_HOST } /${ app } /issue/detail/${ id } ` ;
300+ const res = await fetch ( url ) ;
301+ const text = await res . text ( ) ;
302+
303+ const detailInfo = getLarkProjectInfoByDetail ( text , 'issue' , id ) ;
304+ if ( detailInfo . data ) {
305+ typeCache . set ( tid , "issue" ) ;
306+ return "issue" ;
307+ }
308+
309+ const prefetchListInfo = getLarkProjectInfoByPrefetchList ( text ) ;
310+ if ( prefetchListInfo . data ) {
311+ typeCache . set ( tid , "issue" ) ;
312+ return "issue" ;
313+ }
314+ } catch ( e ) {
315+ console . error ( '[Lark Background] Issue request failed:' , e . message ) ;
316+ }
317+
318+ // 两次都失败,默认返回 story
319+ typeCache . set ( tid , "story" ) ;
320+ return "story" ;
321+ }
322+
160323chrome . runtime . onInstalled . addListener ( ( details ) => {
161324 if ( details . reason === "install" ) {
162325 // 打开选项页
0 commit comments