From b8b26373dddc6a4bfae22fc650b464dd2337c70c Mon Sep 17 00:00:00 2001 From: posimai Date: Wed, 18 Mar 2026 00:07:40 +0900 Subject: [PATCH] chore: update claude-settings.json (memory-push hook) and server.js backup --- server.js | 52 ++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/server.js b/server.js index 34525c4b..4e911625 100644 --- a/server.js +++ b/server.js @@ -69,10 +69,8 @@ const pool = new Pool({ const genAI = process.env.GEMINI_API_KEY ? new GoogleGenerativeAI(process.env.GEMINI_API_KEY) : null; -// Together 専用インスタンス(GEMINI_TOGETHER_API_KEY 優先、なければ共用キーにフォールバック) -const genAITogether = process.env.GEMINI_TOGETHER_API_KEY - ? new GoogleGenerativeAI(process.env.GEMINI_TOGETHER_API_KEY) - : genAI; +// Together 専用インスタンス(メインキーを共用) +const genAITogether = genAI; // ── API Key 認証 ────────────────────────── // API_KEYS="pk_maita_abc:maita,pk_partner_def:partner" @@ -1316,31 +1314,44 @@ ${excerpt} try { const jinaRes = await fetch(`https://r.jina.ai/${url}`, { headers: { 'Accept': 'text/plain', 'User-Agent': 'Posimai/1.0' }, - signal: AbortSignal.timeout(10000), + signal: AbortSignal.timeout(30000), }); if (!jinaRes.ok) throw new Error(`Jina ${jinaRes.status}`); const fullContent = await jinaRes.text(); + // Jina Reader のレスポンス先頭から "Title: ..." を抽出 + const titleMatch = fullContent.match(/^Title:\s*(.+)/m); + const jinaTitle = titleMatch ? titleMatch[1].trim().slice(0, 300) : null; + await pool.query( - `UPDATE together_shares SET full_content=$1 WHERE id=$2`, - [fullContent, shareId] + `UPDATE together_shares SET full_content=$1, title=COALESCE(title, $2) WHERE id=$3`, + [fullContent, jinaTitle, shareId] ); let summary = null; + let tags = []; if (genAI && fullContent) { // 最初の ## 見出し以降を本文とみなし 4000 字を Gemini に渡す const bodyStart = fullContent.search(/^#{1,2}\s/m); const excerpt = (bodyStart >= 0 ? fullContent.slice(bodyStart) : fullContent).slice(0, 4000); const model = genAITogether.getGenerativeModel({ model: 'gemini-2.5-flash' }); - const prompt = `以下の記事を1〜2文の日本語で要約してください。読者が読む価値があるかを判断できる内容にしてください。\n\n${excerpt}\n\n要約(1〜2文のみ):`; - const timeoutP = new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), 12000)); + const prompt = `以下の記事を分析して、JSONのみを返してください(コードブロック不要)。\n\n{"summary":"1〜2文の日本語要約","tags":["タグ1","タグ2","タグ3"]}\n\n- summary: 読者が読む価値があるかを判断できる1〜2文\n- tags: 内容を表す日本語タグを2〜4個(例: AI, テクノロジー, ビジネス, 健康, 旅行, 料理, スポーツ, 政治, 経済, エンタメ, ゲーム, 科学, デザイン)\n\n記事:\n${excerpt}`; + const timeoutP = new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), 30000)); const result = await Promise.race([model.generateContent(prompt), timeoutP]); - summary = result.response.text().trim().slice(0, 300); + const raw = result.response.text().trim(); + try { + const parsed = JSON.parse(raw); + summary = (parsed.summary || '').slice(0, 300); + tags = Array.isArray(parsed.tags) ? parsed.tags.slice(0, 4).map(t => String(t).slice(0, 20)) : []; + } catch { + // JSON パース失敗時は全文を要約として扱う + summary = raw.slice(0, 300); + } } await pool.query( - `UPDATE together_shares SET summary=$1, archive_status='done' WHERE id=$2`, - [summary, shareId] + `UPDATE together_shares SET summary=$1, tags=$2, archive_status='done' WHERE id=$3`, + [summary, tags, shareId] ); } catch (e) { console.error('[together archive]', shareId, e.message); @@ -1448,6 +1459,23 @@ ${excerpt} } }); + // DELETE /together/share/:id — 自分の投稿を削除 + r.delete('/together/share/:id', async (req, res) => { + const { username } = req.body || {}; + if (!username) return res.status(400).json({ error: 'username は必須です' }); + try { + const result = await pool.query( + 'DELETE FROM together_shares WHERE id=$1 AND shared_by=$2 RETURNING id', + [req.params.id, username] + ); + if (result.rows.length === 0) return res.status(403).json({ error: '削除できません' }); + res.json({ ok: true }); + } catch (e) { + console.error('[together/share DELETE]', e.message); + res.status(500).json({ error: e.message }); + } + }); + // GET /together/feed/:groupId — フィード(リアクション・コメント数付き) r.get('/together/feed/:groupId', async (req, res) => { try {