diff --git a/index.html b/index.html index 2d08b58..25ae3eb 100644 --- a/index.html +++ b/index.html @@ -472,15 +472,21 @@ - +
-
Posimai API Key
-

- 設定すると VPS 経由でずんだもん音声が使えます(未設定時はブラウザ音声)。 +

アカウント
+

+ ログインするとずんだもん音声・カスタムRSSが使えます。

- - + +
+ ログインする +
@@ -562,6 +568,16 @@ const FEED_API = 'https://posimai-feed.vercel.app/api/feed'; const API_BASE = 'https://api.soar-enrich.com/brain/api'; const TTS_API = API_BASE + '/tts'; + +// ── 認証ヘルパー ───────────────────────────────────────────── +// JWT(posimai_token)を優先、なければ旧来の pk_ キーにフォールバック +function getAuthToken() { + return localStorage.getItem('posimai_token') || localStorage.getItem('posimai-brief-apikey') || ''; +} +function isLoggedIn() { return !!localStorage.getItem('posimai_token'); } +function getLoginUrl() { + return 'https://posimai.soar-enrich.com/login?redirect=' + encodeURIComponent(location.href); +} const DAYS_JP = ['日','月','火','水','木','金','土']; // ── 状態 ───────────────────────────────────────────────────── @@ -675,15 +691,15 @@ function preprocessText(t) { } async function tryVoicevox(text) { - const apiKey = localStorage.getItem('posimai-brief-apikey'); - if (!apiKey) return false; + const token = getAuthToken(); + if (!token) return false; const ctx = getAudioCtx(); for (let attempt = 0; attempt < 4; attempt++) { if (attempt > 0) await new Promise(r => setTimeout(r, 2000 * attempt)); const res = await fetch(TTS_API, { method: 'POST', - headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` }, + headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify({ text: preprocessText(text), speaker: ttsSpeaker }), }); if (res.status === 503) { @@ -944,14 +960,30 @@ function initSpeakerBtns() { }); } -// ── API キー保存 ────────────────────────────────────────────── -document.getElementById('apiKeyInput').value = localStorage.getItem('posimai-brief-apikey') || ''; -document.getElementById('apiKeySave').addEventListener('click', () => { - const v = document.getElementById('apiKeyInput').value.trim(); - localStorage.setItem('posimai-brief-apikey', v); - if (typeof showToast === 'function') showToast('保存しました'); +// ── 認証 UI 初期化 ─────────────────────────────────────────── +function updateAuthUI() { + const loggedIn = isLoggedIn(); + document.getElementById('authLoggedIn').style.display = loggedIn ? '' : 'none'; + document.getElementById('authLoggedOut').style.display = loggedIn ? 'none' : ''; + if (loggedIn) { + try { + const payload = JSON.parse(atob(localStorage.getItem('posimai_token').split('.')[1])); + document.getElementById('authUserLine').textContent = 'ログイン中: ' + (payload.userId || ''); + } catch (_) {} + } + document.getElementById('authLoginBtn').href = getLoginUrl(); + // 音声バッジも更新 + updateVoiceBadge(!isLoggedIn() && !localStorage.getItem('posimai-brief-apikey')); +} + +document.getElementById('authLogoutBtn').addEventListener('click', () => { + localStorage.removeItem('posimai_token'); + updateAuthUI(); + if (typeof showToast === 'function') showToast('ログアウトしました'); }); +updateAuthUI(); + // ── 日付表示 ───────────────────────────────────────────────── function updateDate() { const now = new Date(); @@ -967,13 +999,13 @@ async function loadFeed() { document.getElementById('briefList').innerHTML = ''; try { - const apiKey = localStorage.getItem('posimai-brief-apikey'); + const token = getAuthToken(); let res; - if (apiKey) { + if (token) { let customFeeds = []; try { const mediaRes = await fetch(API_BASE + '/feed/media', { - headers: { 'Authorization': `Bearer ${apiKey}` } + headers: { 'Authorization': `Bearer ${token}` } }); if (mediaRes.ok) { const medias = await mediaRes.json(); @@ -994,7 +1026,7 @@ async function loadFeed() { res = await fetch(FEED_API, { method: 'POST', cache: 'no-store', - headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` }, + headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify({ customFeeds }), }); } catch (_) {