Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UaC9HmtHMdRP8dAbN4wAKj
37 lines
2.7 KiB
Python
37 lines
2.7 KiB
Python
# Builds public/vendor/bible/bsb.json, the Berean Standard Bible (public domain: https://berean.bible/), from the free Hello AO Bible API
|
|
# (https://bible.helloao.org). Run once (needs a network): python3 tools/make-bsb.py
|
|
# Compact format: {books:[{id,name,chapters:[[block,...]]}]}; a block is ["h",heading] | ["s",subtitle] | ["p"] (new paragraph) |
|
|
# ["v",number,[part,...],[note,...]] where a part is prose text, [level,text] for a poetry line (level 1 or 2), or "\n" for a line break inside a verse.
|
|
import json, urllib.request, concurrent.futures as cf, os, sys
|
|
API = 'https://bible.helloao.org/api/BSB'
|
|
def get(p):
|
|
for t in range(4):
|
|
try: return json.load(urllib.request.urlopen(urllib.request.Request(API + p, headers={'User-Agent': 'NE-Hub/1.0'}), timeout=40))
|
|
except Exception as e: err = e
|
|
raise err
|
|
books = get('/books.json')['books']
|
|
jobs = [(b['id'], c) for b in books for c in range(1, b['numberOfChapters'] + 1)]
|
|
with cf.ThreadPoolExecutor(8) as ex: raw = dict(zip(jobs, ex.map(lambda j: get(f'/{j[0]}/{j[1]}.json'), jobs)))
|
|
def txt(c): return ' '.join(p if isinstance(p, str) else p.get('text', '') for p in c).replace(' ', ' ').strip()
|
|
def chapter(d):
|
|
notes = {n['noteId']: n for n in d['chapter'].get('footnotes', [])}; out = []
|
|
for x in d['chapter']['content']:
|
|
t = x['type']
|
|
if t == 'heading': out.append(['h', txt(x['content'])])
|
|
elif t == 'hebrew_subtitle': out.append(['s', txt(x['content'])])
|
|
elif t == 'line_break': out.append(['p'])
|
|
elif t == 'verse':
|
|
parts, ns = [], []
|
|
for p in x['content']:
|
|
if isinstance(p, str): parts.append(p)
|
|
elif 'noteId' in p and p['noteId'] in notes: ns.append(notes[p['noteId']]['text']); parts.append(len(ns))
|
|
elif 'lineBreak' in p: parts.append('\n')
|
|
elif 'poem' in p: parts.append([p['poem'], p['text']])
|
|
elif 'text' in p: parts.append(p['text'])
|
|
out.append(['v', x['number'], parts] + ([ns] if ns else []))
|
|
return out
|
|
bible = {'id': 'BSB', 'name': 'Berean Standard Bible', 'license': 'Public domain (dedicated by Bible Hub / Berean Bible, April 30, 2023)', 'source': 'https://berean.bible/', 'books': [
|
|
{'id': b['id'], 'name': b['commonName'], 'chapters': [chapter(raw[(b['id'], c)]) for c in range(1, b['numberOfChapters'] + 1)]} for b in books]}
|
|
dest = os.path.join(os.path.dirname(__file__), '..', 'public', 'vendor', 'bible', 'bsb.json'); os.makedirs(os.path.dirname(dest), exist_ok=True)
|
|
json.dump(bible, open(dest, 'w'), ensure_ascii=False, separators=(',', ':')); print('wrote', dest, os.path.getsize(dest), 'bytes;', sum(len(b['chapters']) for b in bible['books']), 'chapters')
|