fix: posimai_token(JWT)対応・ログインUI追加・pk_キーはフォールバックに

Made-with: Cursor
This commit is contained in:
posimai 2026-04-11 14:37:19 +09:00
parent 7d8297a243
commit 1337c280b0
1 changed files with 52 additions and 20 deletions

View File

@ -472,15 +472,21 @@
</div> </div>
</div> </div>
<!-- Posimai API Key --> <!-- 認証 / VOICEVOX -->
<div style="margin-top:20px"> <div style="margin-top:20px">
<div class="settings-group-label">Posimai API Key</div> <div class="settings-group-label">アカウント</div>
<p class="settings-field-label"> <p class="settings-field-label" id="authStatusLabel">
設定すると VPS 経由でずんだもん音声が使えます(未設定時はブラウザ音声) ログインするとずんだもん音声・カスタムRSSが使えます
</p> </p>
<input class="settings-text-input" id="apiKeyInput" type="password" <div id="authLoggedIn" style="display:none">
placeholder="posimai_api_key" autocomplete="off"> <div id="authUserLine" style="font-size:13px;color:var(--accent);margin-bottom:8px"></div>
<button class="settings-action-btn" id="apiKeySave">保存</button> <button class="settings-action-btn" id="authLogoutBtn" style="background:transparent;border:1px solid var(--border);color:var(--text2)">ログアウト</button>
</div>
<div id="authLoggedOut">
<a class="settings-action-btn" id="authLoginBtn"
style="display:inline-flex;align-items:center;gap:6px;text-decoration:none"
href="#">ログインする</a>
</div>
</div> </div>
</div> </div>
@ -562,6 +568,16 @@
const FEED_API = 'https://posimai-feed.vercel.app/api/feed'; const FEED_API = 'https://posimai-feed.vercel.app/api/feed';
const API_BASE = 'https://api.soar-enrich.com/brain/api'; const API_BASE = 'https://api.soar-enrich.com/brain/api';
const TTS_API = API_BASE + '/tts'; const TTS_API = API_BASE + '/tts';
// ── 認証ヘルパー ─────────────────────────────────────────────
// JWTposimai_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 = ['日','月','火','水','木','金','土']; const DAYS_JP = ['日','月','火','水','木','金','土'];
// ── 状態 ───────────────────────────────────────────────────── // ── 状態 ─────────────────────────────────────────────────────
@ -675,15 +691,15 @@ function preprocessText(t) {
} }
async function tryVoicevox(text) { async function tryVoicevox(text) {
const apiKey = localStorage.getItem('posimai-brief-apikey'); const token = getAuthToken();
if (!apiKey) return false; if (!token) return false;
const ctx = getAudioCtx(); const ctx = getAudioCtx();
for (let attempt = 0; attempt < 4; attempt++) { for (let attempt = 0; attempt < 4; attempt++) {
if (attempt > 0) await new Promise(r => setTimeout(r, 2000 * attempt)); if (attempt > 0) await new Promise(r => setTimeout(r, 2000 * attempt));
const res = await fetch(TTS_API, { const res = await fetch(TTS_API, {
method: 'POST', 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 }), body: JSON.stringify({ text: preprocessText(text), speaker: ttsSpeaker }),
}); });
if (res.status === 503) { if (res.status === 503) {
@ -944,14 +960,30 @@ function initSpeakerBtns() {
}); });
} }
// ── API キー保存 ────────────────────────────────────────────── // ── 認証 UI 初期化 ───────────────────────────────────────────
document.getElementById('apiKeyInput').value = localStorage.getItem('posimai-brief-apikey') || ''; function updateAuthUI() {
document.getElementById('apiKeySave').addEventListener('click', () => { const loggedIn = isLoggedIn();
const v = document.getElementById('apiKeyInput').value.trim(); document.getElementById('authLoggedIn').style.display = loggedIn ? '' : 'none';
localStorage.setItem('posimai-brief-apikey', v); document.getElementById('authLoggedOut').style.display = loggedIn ? 'none' : '';
if (typeof showToast === 'function') showToast('保存しました'); 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() { function updateDate() {
const now = new Date(); const now = new Date();
@ -967,13 +999,13 @@ async function loadFeed() {
document.getElementById('briefList').innerHTML = ''; document.getElementById('briefList').innerHTML = '';
try { try {
const apiKey = localStorage.getItem('posimai-brief-apikey'); const token = getAuthToken();
let res; let res;
if (apiKey) { if (token) {
let customFeeds = []; let customFeeds = [];
try { try {
const mediaRes = await fetch(API_BASE + '/feed/media', { const mediaRes = await fetch(API_BASE + '/feed/media', {
headers: { 'Authorization': `Bearer ${apiKey}` } headers: { 'Authorization': `Bearer ${token}` }
}); });
if (mediaRes.ok) { if (mediaRes.ok) {
const medias = await mediaRes.json(); const medias = await mediaRes.json();
@ -994,7 +1026,7 @@ async function loadFeed() {
res = await fetch(FEED_API, { res = await fetch(FEED_API, {
method: 'POST', method: 'POST',
cache: 'no-store', cache: 'no-store',
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` }, headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` },
body: JSON.stringify({ customFeeds }), body: JSON.stringify({ customFeeds }),
}); });
} catch (_) { } catch (_) {