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

Update server.js

Browse files
Files changed (1) hide show
  1. server.js +103 -137
server.js CHANGED
@@ -1,6 +1,7 @@
1
  import express from "express";
2
  import fetch from "node-fetch";
3
  import dotenv from "dotenv";
 
4
  import { setTimeout as delay } from "node:timers/promises";
5
 
6
  dotenv.config();
@@ -16,172 +17,137 @@ if (!process.env.OPENAI_KEY) {
16
  app.use(express.json());
17
  app.use(express.static("public"));
18
 
19
- /* ---------- Bible book → ID tábla (parameterized API igényli) ---------- */
20
- const BOOK_IDS = {
21
- Genesis: "GEN", Exodus: "EXO", Leviticus: "LEV", Numbers: "NUM", Deuteronomy: "DEU",
22
- Joshua: "JOS", Judges: "JDG", Ruth: "RUT", "1 Samuel": "1SA", "2 Samuel": "2SA",
23
- "1 Kings": "1KI", "2 Kings": "2KI", "1 Chronicles": "1CH", "2 Chronicles": "2CH",
24
- Ezra: "EZR", Nehemiah: "NEH", Esther: "EST", Job: "JOB", Psalms: "PSA", Proverbs: "PRO",
25
- Ecclesiastes: "ECC", "Song of Solomon": "SNG", Isaiah: "ISA", Jeremiah: "JER",
26
- Lamentations: "LAM", Ezekiel: "EZK", Daniel: "DAN", Hosea: "HOS", Joel: "JOL",
27
- Amos: "AMO", Obadiah: "OBA", Jonah: "JON", Micah: "MIC", Nahum: "NAM", Habakkuk: "HAB",
28
- Zephaniah: "ZEP", Haggai: "HAG", Zechariah: "ZEC", Malachi: "MAL", Matthew: "MAT",
29
- Mark: "MRK", Luke: "LUK", John: "JHN", Acts: "ACT", Romans: "ROM", "1 Corinthians": "1CO",
30
- "2 Corinthians": "2CO", Galatians: "GAL", Ephesians: "EPH", Philippians: "PHP",
31
- Colossians: "COL", "1 Thessalonians": "1TH", "2 Thessalonians": "2TH", "1 Timothy": "1TI",
32
- "2 Timothy": "2TI", Titus: "TIT", Philemon: "PHM", Hebrews: "HEB", James: "JAS",
33
- "1 Peter": "1PE", "2 Peter": "2PE", "1 John": "1JN", "2 John": "2JN", "3 John": "3JN",
34
- Jude: "JUD", Revelation: "REV"
35
- };
36
- function toBookId(name) {
37
- return BOOK_IDS[name] || name.slice(0, 3).toUpperCase();
 
 
 
 
 
 
 
 
 
38
  }
39
 
40
- /* ---------- Simple token-bucket rate-limiter for Bible-API (15 req / 30 s) ---------- */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  let tokens = 15;
42
  setInterval(() => (tokens = 15), 30_000);
43
- async function limitedFetch(url, opts) {
44
  while (tokens === 0) await delay(200);
45
  tokens--;
46
  const r = await fetch(url, opts);
47
  if (r.status === 429) {
48
- const retry = parseInt(r.headers.get("Retry-After") || "5", 10) * 1000;
49
  await delay(retry);
50
  return limitedFetch(url, opts);
51
  }
52
  return r;
53
- }
54
 
55
- /* ---------- In-memory verse & meta cache ---------- */
56
- const verseCache = new Map(); // key → {text, exp}
57
- const metaCache = new Map(); // book → {chapters:[n], exp}
58
- function cacheGet(map, key) {
59
- const v = map.get(key);
60
- return v && v.exp > Date.now() ? v.data : undefined;
 
 
 
 
 
 
 
 
61
  }
62
- function cacheSet(map, key, data, ttl = 300_000) {
63
- map.set(key, { data, exp: Date.now() + ttl });
 
64
  }
65
 
66
- /* ---------- Fetch English KJV verse ---------- */
67
  async function fetchEnglishVerse(book, ch, v) {
68
  const ref = `${book} ${ch}:${v}`;
69
- const cached = cacheGet(verseCache, ref);
70
  if (cached) return cached;
 
 
 
 
 
 
71
  const url = `https://bible-api.com/${encodeURIComponent(ref)}?translation=kjv`;
72
  const r = await limitedFetch(url);
73
- if (!r.ok) throw new Error(`bible-api verse error ${r.status}`);
74
  const j = await r.json();
75
- const text = j.text.trim();
76
- cacheSet(verseCache, ref, text);
77
- return text;
78
  }
79
 
80
- /* ---------- Robust /api/meta ---------- */
 
 
81
  async function fetchMetaChapters(book) {
82
- const cached = cacheGet(metaCache, book);
83
- if (cached) return cached;
84
 
85
- const bookId = toBookId(book);
86
- const url = `https://bible-api.com/data/kjv/${bookId}`;
87
- let chapters = [];
 
 
88
  try {
 
89
  const r = await limitedFetch(url);
90
  if (r.ok) {
91
  const j = await r.json();
 
92
  if (Array.isArray(j.chapters)) {
93
- chapters = j.chapters.map(c => parseInt(c.verses_count ?? c.verse_count ?? (c.verse_ids?.length ?? 0), 10));
94
  } else if (j.chapters && typeof j.chapters === "object") {
95
- chapters = Object.keys(j.chapters).sort((a, b) => a - b).map(k => {
96
- const obj = j.chapters[k];
97
- return parseInt(obj.verses_count ?? obj.verse_count ?? (obj.verse_ids?.length ?? 0), 10);
98
- });
99
- } else if (Array.isArray(j.chapter_ids)) {
100
- // we know count of chapters but not verses, will fetch first+last verse per chapter below
101
- chapters = await Promise.all(
102
- j.chapter_ids.map(async id => {
103
- const parts = id.split(".");
104
- const chNum = parts[1] || id; // fallback
105
- return await fetchChapterVerseCount(bookId, chNum);
106
- })
107
- );
108
- }
109
- }
110
- } catch (e) {
111
- console.error("/data API parse fail, falling back", e);
112
- }
113
-
114
- // fallback if still empty → step through chapters until 404
115
- if (!chapters.length) {
116
- chapters = await bruteForceChapters(book);
117
- }
118
-
119
- if (!chapters.length) throw new Error("meta fetch failed");
120
- cacheSet(metaCache, book, chapters, 3600_000); // 1 óra cache
121
- return chapters;
122
- }
123
-
124
- async function fetchChapterVerseCount(bookId, ch) {
125
- const url = `https://bible-api.com/data/kjv/${bookId}/${ch}`;
126
- const r = await limitedFetch(url);
127
- if (!r.ok) return 0;
128
- const j = await r.json();
129
- if (Array.isArray(j.verses)) return j.verses.length;
130
- if (Array.isArray(j.verse_ids)) return j.verse_ids.length;
131
- if (j.verses_count || j.verse_count) return parseInt(j.verses_count ?? j.verse_count, 10);
132
- return 0;
133
- }
134
-
135
- async function bruteForceChapters(book) {
136
- const chapters = [];
137
- for (let ch = 1; ch <= 200; ch++) {
138
- const url = `https://bible-api.com/${encodeURIComponent(book + " " + ch)}?translation=kjv`;
139
- const r = await limitedFetch(url);
140
- if (!r.ok) break; // 404 signals end
141
- const j = await r.json();
142
- const versesInChapter = j.verses[j.verses.length - 1].verse;
143
- chapters.push(versesInChapter);
144
- }
145
- return chapters;
146
- }
147
-
148
- /* ---------- Routes ---------- */
149
- app.post("/api/analyze", async (req, res) => {
150
- const { book, chapter, verse } = req.body || {};
151
- try {
152
- const engVerse = await fetchEnglishVerse(book, chapter, verse);
153
- const aiRes = await fetch("https://api.openai.com/v1/chat/completions", {
154
- method: "POST",
155
- headers: { "Content-Type": "application/json", Authorization: "Bearer " + process.env.OPENAI_KEY },
156
- body: JSON.stringify({
157
- model: "gpt-4o-mini",
158
- messages: [
159
- { 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." },
160
- { role: "user", content: `Verse (KJV): \"${engVerse}\" — ${book} ${chapter}:${verse}` }
161
- ],
162
- temperature: 0.9,
163
- max_tokens: 300
164
- })
165
- });
166
- if (!aiRes.ok) throw new Error("OpenAI API hiba");
167
- const data = await aiRes.json();
168
- res.json({ output: data.choices[0].message.content.trim() });
169
- } catch (err) {
170
- console.error(err);
171
- res.status(500).json({ error: err.message });
172
- }
173
- });
174
-
175
- app.get("/api/meta", async (req, res) => {
176
- const { book } = req.query;
177
- if (!book) return res.status(400).json({ error: "Missing book" });
178
- try {
179
- const chapters = await fetchMetaChapters(book);
180
- res.json({ chapters });
181
- } catch (err) {
182
- console.error(err);
183
- res.status(500).json({ error: err.message });
184
- }
185
- });
186
-
187
- app.listen(PORT, () => console.log("🚀 Fut a", PORT, "porton"));
 
1
  import express from "express";
2
  import fetch from "node-fetch";
3
  import dotenv from "dotenv";
4
+ import fs from "fs";
5
  import { setTimeout as delay } from "node:timers/promises";
6
 
7
  dotenv.config();
 
17
  app.use(express.json());
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 : [["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;
42
+ if (!tmp[c]) tmp[c] = [];
43
+ tmp[c][v.verse - 1] = (v.text ?? v).trim();
44
+ });
45
+ return tmp;
46
+ }
47
+ return null;
48
  }
49
 
50
+ try {
51
+ if (fs.existsSync("./kjv.json")) {
52
+ console.log("📖 Betöltöm a helyi kjv.json fájlt…");
53
+ const raw = JSON.parse(fs.readFileSync("./kjv.json", "utf8"));
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) {
112
  const ref = `${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);
127
+ return txt;
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);
145
  if (r.ok) {
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) ?? (