mcmeszi commited on
Commit
545432b
·
verified ·
1 Parent(s): 384ae40

Update server.js

Browse files
Files changed (1) hide show
  1. server.js +107 -42
server.js CHANGED
@@ -19,23 +19,18 @@ app.use(express.static("public"));
19
 
20
  /* ------------------------------------------------------------------
21
  1) Helyi KJV betöltése és NORMALIZÁLÁSA
22
- A `kjv.json`‑nak sokféle struktúrája lehet (thiagobodruk, aruljohn, stb.).
23
- Minden verziót egységes, könnyen indexelhető formára (Book → Chapter[] → Verse[]) alakítunk.
24
  ------------------------------------------------------------------ */
25
- let LOCAL_BIBLE = null; // { Genesis: [ ["v1", "v2", ...], ... ], Exodus: ... }
26
 
27
  function normaliseBook(raw) {
28
- /* nyers könyvobjektum [ [verseStrs…] ] */
29
- if (Array.isArray(raw)) {
30
- // már jó: [["In the beginning", …], …]
31
- return raw;
32
- }
33
  if (raw.chapters && Array.isArray(raw.chapters)) {
34
- // pl. { chapters:[ { verses:["…"] }, … ] }
35
  return raw.chapters.map(ch => Array.isArray(ch) ? ch : ch.verses?.map(v => (v.text ?? v).trim()));
36
  }
 
37
  if (raw.verses && Array.isArray(raw.verses)) {
38
- // thiagobodruk: flat verses list [{chapter,verse,text} …]
39
  const tmp = [];
40
  raw.verses.forEach(v => {
41
  const c = v.chapter - 1;
@@ -54,58 +49,61 @@ try {
54
  const normalised = {};
55
  for (const [book, data] of Object.entries(raw)) {
56
  const norm = normaliseBook(data);
57
- if (!norm) throw new Error(`Ismeretlen struktúra a(z) ${book} könyvnél`);
58
  normalised[book] = norm;
59
  }
60
  LOCAL_BIBLE = normalised;
61
  console.log("📖 KJV memória‑cache kész (", Object.keys(LOCAL_BIBLE).length, "könyv ).");
62
  }
63
  } catch (e) {
64
- console.warn("⚠️ Helyi kjv.json nem olvasható vagy ismeretlen formátum; marad a Bible‑API", e.message);
65
  LOCAL_BIBLE = null;
66
  }
67
 
68
  /* ------------------------------------------------------------------
69
- 2) Book név → ID (Bible‑API /data végpont)
70
  ------------------------------------------------------------------ */
71
- const BOOK_IDS = { Genesis: "GEN", Exodus: "EXO", Leviticus: "LEV", Numbers: "NUM", Deuteronomy: "DEU", Joshua: "JOS", Judges: "JDG", Ruth: "RUT", "1 Samuel": "1SA", "2 Samuel": "2SA", "1 Kings": "1KI", "2 Kings": "2KI", "1 Chronicles": "1CH", "2 Chronicles": "2CH", Ezra: "EZR", Nehemiah: "NEH", Esther: "EST", Job: "JOB", Psalms: "PSA", Proverbs: "PRO", Ecclesiastes: "ECC", "Song of Solomon": "SNG", Isaiah: "ISA", Jeremiah: "JER", Lamentations: "LAM", Ezekiel: "EZK", Daniel: "DAN", Hosea: "HOS", Joel: "JOL", Amos: "AMO", Obadiah: "OBA", Jonah: "JON", Micah: "MIC", Nahum: "NAM", Habakkuk: "HAB", Zephaniah: "ZEP", Haggai: "HAG", Zechariah: "ZEC", Malachi: "MAL", Matthew: "MAT", Mark: "MRK", Luke: "LUK", John: "JHN", Acts: "ACT", Romans: "ROM", "1 Corinthians": "1CO", "2 Corinthians": "2CO", Galatians: "GAL", Ephesians: "EPH", Philippians: "PHP", Colossians: "COL", "1 Thessalonians": "1TH", "2 Thessalonians": "2TH", "1 Timothy": "1TI", "2 Timothy": "2TI", Titus: "TIT", Philemon: "PHM", Hebrews: "HEB", James: "JAS", "1 Peter": "1PE", "2 Peter": "2PE", "1 John": "1JN", "2 John": "2JN", "3 John": "3JN", Jude: "JUD", Revelation: "REV" };
72
- const toBookId = n => BOOK_IDS[n] || n.slice(0, 3).toUpperCase();
73
 
74
  /* ------------------------------------------------------------------
75
- 3) Rate‑limiter Bible‑API‑hoz (token‑bucket 15/30 s)
76
  ------------------------------------------------------------------ */
77
  let tokens = 15;
78
- setInterval(() => (tokens = 15), 30_000);
79
- const limitedFetch = async (url, opts) => {
80
  while (tokens === 0) await delay(200);
81
  tokens--;
82
  const r = await fetch(url, opts);
83
  if (r.status === 429) {
84
- const retry = +r.headers.get("Retry-After") * 1000 || 5000;
85
  await delay(retry);
86
  return limitedFetch(url, opts);
87
  }
88
  return r;
89
- };
90
 
91
  /* ------------------------------------------------------------------
92
- 4) Egyszerű mem‑cache
93
  ------------------------------------------------------------------ */
94
- const verseCache = new Map(); // key {text, exp}
95
- const metaCache = new Map(); // book {chapters[], exp}
96
- const getCache = (map, k) => { const v = map.get(k); return v && v.exp > Date.now() ? v.data : undefined; };
97
- const setCache = (map, k, d, ttl = 3_600_000) => map.set(k, { data: d, exp: Date.now() + ttl });
 
 
 
 
 
98
 
99
  /* ------------------------------------------------------------------
100
- 5) Helyi vagy online vers‑fetch
101
  ------------------------------------------------------------------ */
102
  function getLocalVerse(book, ch, v) {
103
- const b = LOCAL_BIBLE?.[book];
104
- return b?.[ch - 1]?.[v - 1] || null;
105
  }
106
  function getLocalChapterCounts(book) {
107
- const arr = LOCAL_BIBLE?.[book];
108
- return arr ? arr.map(ch => ch.length) : null;
109
  }
110
 
111
  async function fetchEnglishVerse(book, ch, v) {
@@ -113,14 +111,14 @@ async function fetchEnglishVerse(book, ch, v) {
113
  const cached = getCache(verseCache, ref);
114
  if (cached) return cached;
115
 
116
- // 1) helyi
117
  const local = getLocalVerse(book, ch, v);
118
  if (local) { setCache(verseCache, ref, local); return local; }
119
 
120
- // 2) online
121
  const url = `https://bible-api.com/${encodeURIComponent(ref)}?translation=kjv`;
122
  const r = await limitedFetch(url);
123
- if (!r.ok) throw new Error(`Bible-API verse error ${r.status}`);
124
  const j = await r.json();
125
  const txt = j.text.trim();
126
  setCache(verseCache, ref, txt);
@@ -128,17 +126,29 @@ async function fetchEnglishVerse(book, ch, v) {
128
  }
129
 
130
  /* ------------------------------------------------------------------
131
- 6) /api/meta helyi előnyben, különben API, végül brute-force
132
  ------------------------------------------------------------------ */
 
 
 
 
 
 
 
 
 
 
 
 
133
  async function fetchMetaChapters(book) {
134
- const c1 = getCache(metaCache, book);
135
- if (c1) return c1;
136
 
137
- // a) helyi JSON
138
  const local = getLocalChapterCounts(book);
139
  if (local?.length) { setCache(metaCache, book, local); return local; }
140
 
141
- // b) Bible-API /data
142
  try {
143
  const url = `https://bible-api.com/data/kjv/${toBookId(book)}`;
144
  const r = await limitedFetch(url);
@@ -146,8 +156,63 @@ async function fetchMetaChapters(book) {
146
  const j = await r.json();
147
  let chapters = [];
148
  if (Array.isArray(j.chapters)) {
149
- chapters = j.chapters.map(c => parseInt(c.verses_count ?? c.verse_count ?? (Array.isArray(c.verses) ? c.verses.length : 0) ?? (c.verse_ids?.length ?? 0), 10));
150
  } else if (j.chapters && typeof j.chapters === "object") {
151
- chapters = Object.keys(j.chapters).sort((a,b)=>a-b).map(k => {
152
- const o = j.chapters[k];
153
- return parseInt(o.verses_count ?? o.verse_count ?? (Array.isArray(o.verses) ? o.verses.length : 0) ?? (
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
 
20
  /* ------------------------------------------------------------------
21
  1) Helyi KJV betöltése és NORMALIZÁLÁSA
 
 
22
  ------------------------------------------------------------------ */
23
+ let LOCAL_BIBLE = null; // { Book: [ [v1,v2,], ] }
24
 
25
  function normaliseBook(raw) {
26
+ // már kapunk tömböt? => kész
27
+ if (Array.isArray(raw)) return raw;
28
+ // aruljohn: { chapters:[ { verses:["…"] }, … ] }
 
 
29
  if (raw.chapters && Array.isArray(raw.chapters)) {
 
30
  return raw.chapters.map(ch => Array.isArray(ch) ? ch : ch.verses?.map(v => (v.text ?? v).trim()));
31
  }
32
+ // thiagobodruk: { verses:[ {chapter,verse,text}, … ] }
33
  if (raw.verses && Array.isArray(raw.verses)) {
 
34
  const tmp = [];
35
  raw.verses.forEach(v => {
36
  const c = v.chapter - 1;
 
49
  const normalised = {};
50
  for (const [book, data] of Object.entries(raw)) {
51
  const norm = normaliseBook(data);
52
+ if (!norm) throw new Error(`Ismeretlen struktúra: ${book}`);
53
  normalised[book] = norm;
54
  }
55
  LOCAL_BIBLE = normalised;
56
  console.log("📖 KJV memória‑cache kész (", Object.keys(LOCAL_BIBLE).length, "könyv ).");
57
  }
58
  } catch (e) {
59
+ console.warn("⚠️ Helyi kjv.json nem olvasható vagy hibás Bible‑API fallback", e.message);
60
  LOCAL_BIBLE = null;
61
  }
62
 
63
  /* ------------------------------------------------------------------
64
+ 2) Book név → ID (Bible‑API /data)
65
  ------------------------------------------------------------------ */
66
+ const BOOK_IDS = { Genesis:"GEN", Exodus:"EXO", Leviticus:"LEV", Numbers:"NUM", Deuteronomy:"DEU", Joshua:"JOS", Judges:"JDG", Ruth:"RUT", "1 Samuel":"1SA", "2 Samuel":"2SA", "1 Kings":"1KI", "2 Kings":"2KI", "1 Chronicles":"1CH", "2 Chronicles":"2CH", Ezra:"EZR", Nehemiah:"NEH", Esther:"EST", Job:"JOB", Psalms:"PSA", Proverbs:"PRO", Ecclesiastes:"ECC", "Song of Solomon":"SNG", Isaiah:"ISA", Jeremiah:"JER", Lamentations:"LAM", Ezekiel:"EZK", Daniel:"DAN", Hosea:"HOS", Joel:"JOL", Amos:"AMO", Obadiah:"OBA", Jonah:"JON", Micah:"MIC", Nahum:"NAM", Habakkuk:"HAB", Zephaniah:"ZEP", Haggai:"HAG", Zechariah:"ZEC", Malachi:"MAL", Matthew:"MAT", Mark:"MRK", Luke:"LUK", John:"JHN", Acts:"ACT", Romans:"ROM", "1 Corinthians":"1CO", "2 Corinthians":"2CO", Galatians:"GAL", Ephesians:"EPH", Philippians:"PHP", Colossians:"COL", "1 Thessalonians":"1TH", "2 Thessalonians":"2TH", "1 Timothy":"1TI", "2 Timothy":"2TI", Titus:"TIT", Philemon:"PHM", Hebrews:"HEB", James:"JAS", "1 Peter":"1PE", "2 Peter":"2PE", "1 John":"1JN", "2 John":"2JN", "3 John":"3JN", Jude:"JUD", Revelation:"REV" };
67
+ const toBookId = name => BOOK_IDS[name] || name.slice(0,3).toUpperCase();
68
 
69
  /* ------------------------------------------------------------------
70
+ 3) Bible‑API ratelimit (token‑bucket 15/30 s)
71
  ------------------------------------------------------------------ */
72
  let tokens = 15;
73
+ setInterval(() => { tokens = 15; }, 30_000);
74
+ async function limitedFetch(url, opts) {
75
  while (tokens === 0) await delay(200);
76
  tokens--;
77
  const r = await fetch(url, opts);
78
  if (r.status === 429) {
79
+ const retry = (+r.headers.get("Retry-After") || 5) * 1000;
80
  await delay(retry);
81
  return limitedFetch(url, opts);
82
  }
83
  return r;
84
+ }
85
 
86
  /* ------------------------------------------------------------------
87
+ 4) Mem‑cache segédek
88
  ------------------------------------------------------------------ */
89
+ const verseCache = new Map(); // "Book ch:v" -> text
90
+ const metaCache = new Map(); // "Book" -> [verseCounts]
91
+ function getCache(map, key) {
92
+ const v = map.get(key);
93
+ return v && v.exp > Date.now() ? v.data : undefined;
94
+ }
95
+ function setCache(map, key, data, ttl=3_600_000) {
96
+ map.set(key, { data, exp: Date.now()+ttl });
97
+ }
98
 
99
  /* ------------------------------------------------------------------
100
+ 5) Verse fetch local preferred
101
  ------------------------------------------------------------------ */
102
  function getLocalVerse(book, ch, v) {
103
+ return LOCAL_BIBLE?.[book]?.[ch-1]?.[v-1] ?? null;
 
104
  }
105
  function getLocalChapterCounts(book) {
106
+ return LOCAL_BIBLE?.[book]?.map(ch => ch.length) ?? null;
 
107
  }
108
 
109
  async function fetchEnglishVerse(book, ch, v) {
 
111
  const cached = getCache(verseCache, ref);
112
  if (cached) return cached;
113
 
114
+ // Local first
115
  const local = getLocalVerse(book, ch, v);
116
  if (local) { setCache(verseCache, ref, local); return local; }
117
 
118
+ // Fallback online
119
  const url = `https://bible-api.com/${encodeURIComponent(ref)}?translation=kjv`;
120
  const r = await limitedFetch(url);
121
+ if (!r.ok) throw new Error(`BibleAPI verse error ${r.status}`);
122
  const j = await r.json();
123
  const txt = j.text.trim();
124
  setCache(verseCache, ref, txt);
 
126
  }
127
 
128
  /* ------------------------------------------------------------------
129
+ 6) Meta fetch (chapter verse counts)
130
  ------------------------------------------------------------------ */
131
+ async function bruteForceChapters(book) {
132
+ const counts = [];
133
+ for (let ch=1; ch<=200; ch++) {
134
+ const url = `https://bible-api.com/${encodeURIComponent(book+" "+ch)}?translation=kjv`;
135
+ const r = await limitedFetch(url);
136
+ if (!r.ok) break;
137
+ const j = await r.json();
138
+ counts.push(j.verses.at(-1).verse);
139
+ }
140
+ return counts;
141
+ }
142
+
143
  async function fetchMetaChapters(book) {
144
+ const cached = getCache(metaCache, book);
145
+ if (cached) return cached;
146
 
147
+ // local
148
  const local = getLocalChapterCounts(book);
149
  if (local?.length) { setCache(metaCache, book, local); return local; }
150
 
151
+ // /data endpoint
152
  try {
153
  const url = `https://bible-api.com/data/kjv/${toBookId(book)}`;
154
  const r = await limitedFetch(url);
 
156
  const j = await r.json();
157
  let chapters = [];
158
  if (Array.isArray(j.chapters)) {
159
+ chapters = j.chapters.map(c => parseInt(c.verses_count ?? c.verse_count ?? (Array.isArray(c.verses)?c.verses.length:0) ?? (c.verse_ids?.length??0),10));
160
  } else if (j.chapters && typeof j.chapters === "object") {
161
+ chapters = Object.keys(j.chapters).sort((a,b)=>a-b).map(k=>{
162
+ const o=j.chapters[k];
163
+ return parseInt(o.verses_count ?? o.verse_count ?? (Array.isArray(o.verses)?o.verses.length:0) ?? (o.verse_ids?.length??0),10);
164
+ });
165
+ }
166
+ if (chapters.some(n=>n>1)) { setCache(metaCache, book, chapters); return chapters; }
167
+ }
168
+ } catch(e){ console.warn("/data endpoint fail", e.message); }
169
+
170
+ // brute-force fallback
171
+ const bf = await bruteForceChapters(book);
172
+ if (bf.length) { setCache(metaCache, book, bf); return bf; }
173
+
174
+ throw new Error("Meta fetch failed for "+book);
175
+ }
176
+
177
+ /* ------------------------------------------------------------------
178
+ 7) Routes
179
+ ------------------------------------------------------------------ */
180
+ app.post("/api/analyze", async (req, res) => {
181
+ const { book, chapter, verse } = req.body || {};
182
+ try {
183
+ const engVerse = await fetchEnglishVerse(book, +chapter, +verse);
184
+ const openAIRes = await fetch("https://api.openai.com/v1/chat/completions", {
185
+ method: "POST",
186
+ headers: { "Content-Type":"application/json", Authorization:`Bearer ${process.env.OPENAI_KEY}` },
187
+ body: JSON.stringify({
188
+ model: "gpt-4o-mini",
189
+ messages:[
190
+ { role:"system", content:"Fordítsd magyarra a megadott Biblia‑verset, majd első szám első személyben, legfeljebb két rövid bekezdésben magyarázd el, hogyan kapcsolódik a mai mesterséges intelligencia dilemmáihoz." },
191
+ { role:"user", content:`Verse (KJV): \"${engVerse}\" — ${book} ${chapter}:${verse}` }
192
+ ],
193
+ temperature:0.9,
194
+ max_tokens:300
195
+ })
196
+ });
197
+ if (!openAIRes.ok) throw new Error("OpenAI API hiba");
198
+ const data = await openAIRes.json();
199
+ res.json({ output: data.choices[0].message.content.trim() });
200
+ } catch(err) {
201
+ console.error(err);
202
+ res.status(500).json({ error: err.message });
203
+ }
204
+ });
205
+
206
+ app.get("/api/meta", async (req, res) => {
207
+ const { book } = req.query;
208
+ if (!book) return res.status(400).json({ error:"Missing book" });
209
+ try {
210
+ const chapters = await fetchMetaChapters(book);
211
+ res.json({ chapters });
212
+ } catch(err) {
213
+ console.error(err);
214
+ res.status(500).json({ error: err.message });
215
+ }
216
+ });
217
+
218
+ app.listen(PORT, () => console.log("🚀 Fut a", PORT, "porton"));