Skip to content

Commit 62d20f6

Browse files
solidlimeclaude
andcommitted
feat: メディア欄バッジ・Pixiv完了バッジ・処理待ちキュー表示・Flutter自動セットアップ
Chrome 拡張: - content.js: X メディア欄 (/username/media) の各グリッドアイテムに個別ダウンロードボタン+バッジを追加 - content.js: Pixiv で loadDownloadedUrls() を呼び、ダウンロード済み URL をバッジ表示 (downloaded ✓ > queued … > 未保存 の 3 状態) - service_worker.js: pollDownloadedUrls() を追加し 1 分ごとにポーリング + URLS_UPDATED 通知 - service_worker.js: GET_URLS, DELETE_API_QUEUE_ITEM, CLEAR_API_QUEUE メッセージハンドラ追加 - popup.html/js: サーバーの処理待ちキュー (api_queue) セクション追加、個別削除・全削除対応 - manifest.json: v1.1.0 → v1.2.0 (Service Worker 強制更新) サーバー: - log_store.py: Pixiv/Imgur URL のダウンロード済み管理 (mark_downloaded_url, get_downloaded_urls 等) - log_store.py: peek_api_queue(), remove_api_url(), clear_api_queue() を追加 - discord_bot.py: Pixiv/Imgur 直接ダウンロード完了後に mark_downloaded_url() を記録 - web_setup.py: /api/history/urls, /api/history/urls/count エンドポイント追加 - web_setup.py: /api/queue/status, /api/queue/item (DELETE), /api/queue/clear エンドポイント追加 - web_setup.py: ライトボックスの sourceUrl() に Pixiv ファイル名パターンを追加 Flutter APK ビルド: - build_apk.bat: Flutter 未インストール時に winget または GitHub ZIP から自動セットアップ Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 95b2ecb commit 62d20f6

9 files changed

Lines changed: 643 additions & 68 deletions

File tree

‎client/chrome_extension/background/service_worker.js‎

Lines changed: 101 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
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 のキャッシュ */
8081
let _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 が起きていなくても実行される)
121158
chrome.alarms.create('poll-ids', { periodInMinutes: 1 });
122159
chrome.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

290385
flushQueue();
291386
pollDownloadedIds();
387+
pollDownloadedUrls();

0 commit comments

Comments
 (0)