fix: posimai_token(JWT)対応・ログインUI追加・pk_キーはフォールバックに
Made-with: Cursor
This commit is contained in:
parent
7d8297a243
commit
1337c280b0
72
index.html
72
index.html
|
|
@ -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';
|
||||||
|
|
||||||
|
// ── 認証ヘルパー ─────────────────────────────────────────────
|
||||||
|
// 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 = ['日','月','火','水','木','金','土'];
|
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 (_) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue