88 * - ダウンロード済み ID のポーリング → 全タブへ IDS_UPDATED を通知
99 * (content.js からの直接 SSE 接続は HTTPS ページからの Mixed Content でブロックされるため、
1010 * Service Worker が HTTP でポーリングして中継する)
11+ * - ダウンロード済み URL のポーリング (Pixiv / Imgur 等 tweet_id なしサイト用)
1112 */
1213
1314'use strict' ;
@@ -74,7 +75,7 @@ async function checkHealth() {
7475 }
7576}
7677
77- // ── ダウンロード済み ID ポーリング ─────── ─────────────────────────────────────
78+ // ── ダウンロード済み tweet ID ポーリング ─────────────────────────────────────
7879
7980/** ポーリングで取得したダウンロード済み tweet ID のキャッシュ */
8081let _cachedIds = [ ] ;
@@ -117,10 +118,49 @@ async function pollDownloadedIds() {
117118 }
118119}
119120
121+ // ── ダウンロード済み URL ポーリング (Pixiv / Imgur 等) ───────────────────────
122+
123+ /** ポーリングで取得したダウンロード済み URL のキャッシュ */
124+ let _cachedUrls = [ ] ;
125+
126+ /** 最後に確認したダウンロード済み URL 件数 (変化検出用。-1 = 未取得) */
127+ let _cachedUrlCount = - 1 ;
128+
129+ /**
130+ * サーバーからダウンロード済み URL リストをポーリングして _cachedUrls を更新する。
131+ * カウントが変化していれば全 Pixiv タブに URLS_UPDATED を通知する。
132+ */
133+ async function pollDownloadedUrls ( ) {
134+ try {
135+ const base = await getServerUrl ( ) ;
136+
137+ const countRes = await fetchWithTimeout ( `${ base } /api/history/urls/count` , { } , 3000 ) ;
138+ if ( countRes . status !== 200 ) return ;
139+ const { count } = await countRes . json ( ) ;
140+ if ( count === _cachedUrlCount ) return ;
141+
142+ const urlsRes = await fetchWithTimeout ( `${ base } /api/history/urls` , { } , 10000 ) ;
143+ if ( urlsRes . status !== 200 ) return ;
144+ _cachedUrls = await urlsRes . json ( ) ;
145+ _cachedUrlCount = count ;
146+
147+ // Pixiv タブに通知 (将来的に他のサイトも追加する場合はここを拡張する)
148+ const tabs = await chrome . tabs . query ( { url : [ '*://www.pixiv.net/*' ] } ) ;
149+ for ( const tab of tabs ) {
150+ chrome . tabs . sendMessage ( tab . id , { type : 'URLS_UPDATED' , urls : _cachedUrls } ) . catch ( ( ) => { } ) ;
151+ }
152+ } catch {
153+ // サーバー未接続など: 静かにスキップ
154+ }
155+ }
156+
120157// 1 分ごとにポーリング (Manifest V3 alarm: Service Worker が起きていなくても実行される)
121158chrome . alarms . create ( 'poll-ids' , { periodInMinutes : 1 } ) ;
122159chrome . alarms . onAlarm . addListener ( ( alarm ) => {
123- if ( alarm . name === 'poll-ids' ) pollDownloadedIds ( ) ;
160+ if ( alarm . name === 'poll-ids' ) {
161+ pollDownloadedIds ( ) ;
162+ pollDownloadedUrls ( ) ;
163+ }
124164} ) ;
125165
126166// ── 結果ログ ──────────────────────────────────────────────────────────────────
@@ -166,8 +206,9 @@ chrome.runtime.onMessage.addListener((msg, _sender, sendResponse) => {
166206 accepted : data . accepted || [ ] ,
167207 rejected : data . rejected || [ ] ,
168208 } ) ;
169- // ダウンロードキューに追加できたので ID キャッシュを即時更新する
209+ // ダウンロードキューに追加できたので ID・URL キャッシュを即時更新する
170210 pollDownloadedIds ( ) ;
211+ pollDownloadedUrls ( ) ;
171212 sendResponse ( { ok : true , data } ) ;
172213 } catch ( e ) {
173214 for ( const u of urls ) await enqueueOffline ( u ) ;
@@ -195,6 +236,7 @@ chrome.runtime.onMessage.addListener((msg, _sender, sendResponse) => {
195236 // オンライン時のみ履歴件数とサーバー処理ログを取得する
196237 let historyCount = null ;
197238 let serverLogs = [ ] ;
239+ let apiQueue = [ ] ;
198240 if ( online ) {
199241 try {
200242 const r = await fetchWithTimeout ( `${ serverUrl } /api/history/count` , { } , 3000 ) ;
@@ -204,9 +246,13 @@ chrome.runtime.onMessage.addListener((msg, _sender, sendResponse) => {
204246 const r = await fetchWithTimeout ( `${ serverUrl } /api/logs/recent` , { } , 3000 ) ;
205247 if ( r . status === 200 ) serverLogs = await r . json ( ) ;
206248 } catch { /* ignore */ }
249+ try {
250+ const r = await fetchWithTimeout ( `${ serverUrl } /api/queue/status` , { } , 3000 ) ;
251+ if ( r . status === 200 ) apiQueue = await r . json ( ) ;
252+ } catch { /* ignore */ }
207253 }
208254 const resultLog = logData [ KEY_RESULT_LOG ] || [ ] ;
209- sendResponse ( { ok : true , online, queue, serverUrl, historyCount, resultLog, serverLogs } ) ;
255+ sendResponse ( { ok : true , online, queue, serverUrl, historyCount, resultLog, serverLogs, apiQueue } ) ;
210256 break ;
211257 }
212258
@@ -218,6 +264,13 @@ chrome.runtime.onMessage.addListener((msg, _sender, sendResponse) => {
218264 break ;
219265 }
220266
267+ case 'GET_URLS' : {
268+ // Pixiv / Imgur 等のダウンロード済み URL を取得してから返す
269+ await pollDownloadedUrls ( ) ;
270+ sendResponse ( { ok : true , urls : _cachedUrls } ) ;
271+ break ;
272+ }
273+
221274 case 'SET_SERVER_URL' : {
222275 // URL に scheme がなければ補完する
223276 let url = ( msg . url || '' ) . trim ( ) . replace ( / \/ $ / , '' ) ;
@@ -226,12 +279,54 @@ chrome.runtime.onMessage.addListener((msg, _sender, sendResponse) => {
226279 }
227280 await chrome . storage . local . set ( { [ KEY_SERVER ] : url } ) ;
228281 // サーバー URL が変わったのでキャッシュをリセットして再取得する
229- _cachedCount = - 1 ;
282+ _cachedCount = - 1 ;
283+ _cachedUrlCount = - 1 ;
230284 pollDownloadedIds ( ) ;
285+ pollDownloadedUrls ( ) ;
231286 sendResponse ( { ok : true , url } ) ;
232287 break ;
233288 }
234289
290+ case 'DELETE_API_QUEUE_ITEM' : {
291+ // サーバーの直接ダウンロードキューから指定 URL を削除する
292+ try {
293+ const base = await getServerUrl ( ) ;
294+ const res = await fetchWithTimeout (
295+ `${ base } /api/queue/item` ,
296+ {
297+ method : 'DELETE' ,
298+ headers : { 'Content-Type' : 'application/json' } ,
299+ body : JSON . stringify ( { url : msg . url } ) ,
300+ } ,
301+ 5000 ,
302+ ) ;
303+ const data = await res . json ( ) ;
304+ if ( ! res . ok ) throw new Error ( data . error || `HTTP ${ res . status } ` ) ;
305+ sendResponse ( { ok : true } ) ;
306+ } catch ( e ) {
307+ sendResponse ( { ok : false , error : String ( e ) } ) ;
308+ }
309+ break ;
310+ }
311+
312+ case 'CLEAR_API_QUEUE' : {
313+ // サーバーの直接ダウンロードキューを全件削除する
314+ try {
315+ const base = await getServerUrl ( ) ;
316+ const res = await fetchWithTimeout (
317+ `${ base } /api/queue/clear` ,
318+ { method : 'POST' } ,
319+ 5000 ,
320+ ) ;
321+ const data = await res . json ( ) ;
322+ if ( ! res . ok ) throw new Error ( data . error || `HTTP ${ res . status } ` ) ;
323+ sendResponse ( { ok : true , deleted : data . deleted } ) ;
324+ } catch ( e ) {
325+ sendResponse ( { ok : false , error : String ( e ) } ) ;
326+ }
327+ break ;
328+ }
329+
235330 case 'EXPORT_HISTORY' : {
236331 try {
237332 const base = await getServerUrl ( ) ;
@@ -289,3 +384,4 @@ chrome.webNavigation.onHistoryStateUpdated.addListener((details) => {
289384
290385flushQueue ( ) ;
291386pollDownloadedIds ( ) ;
387+ pollDownloadedUrls ( ) ;
0 commit comments