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 (_) {