Skip to content

Instantly share code, notes, and snippets.

@mrcnski
Created November 14, 2024 22:59
Show Gist options
  • Save mrcnski/a1cf88b0327f2207715e502faad0e527 to your computer and use it in GitHub Desktop.
Save mrcnski/a1cf88b0327f2207715e502faad0e527 to your computer and use it in GitHub Desktop.
Bible Verse Fetcher
#!/Users/marcin/Sync/Home/dotfiles/bin/.env/bin/python3
# Bible verse fetcher. Put this script in your PATH, update the above path to
# python, and make it executable.
#
# Usage: b <verses> [optional flags]
#
# Example: b John 3:16-17
# Example: b John 3:16-17 --version NIV
#
# Improved version of the original from here:
# https://github.com/covode/bible-fetch
import sys
from bs4 import BeautifulSoup
import argparse
import requests
import subprocess
import time
from unidecode import unidecode
import urllib.parse
def parse_args():
default_version = "NRSVUE"
parser = argparse.ArgumentParser(
description="Fetch Bible references from biblegateway.com"
)
parser.add_argument("reference", nargs="*", help="bible reference")
parser.add_argument(
"--version",
nargs="?",
help="translation (e.g. NIV, NLT)",
default=default_version,
)
parser.add_argument(
"--verse-numbers",
action="store_true",
default=False,
help="print verse numbers (hidden by default)",
)
parser.add_argument(
"--ascii",
action="store_true",
default=False,
help="translate Unicode to ASCII (useful on Windows)",
)
parser.add_argument(
"-d",
"--discord",
action="store_true",
default=False,
help="formats the verse for Discord",
)
if len(sys.argv) == 1:
parser.print_help()
sys.exit(1)
args = parser.parse_args()
return (
" ".join(args.reference),
args.version or default_version,
args.verse_numbers,
args.ascii,
args.discord,
)
def get_soup(query, version):
query = urllib.parse.quote_plus(query)
r = requests.get(
"https://www.biblegateway.com/passage/?search=" + query + "&version=" + version
)
return BeautifulSoup(r.text, "html.parser")
def clean(tag):
for t in tag.find_all("sup"):
if show_verse_numbers and "versenum" in t["class"]:
continue
t.decompose()
if not show_verse_numbers:
for t in tag.find_all(class_="chapternum"):
t.decompose()
for t in tag.find_all(class_="surface"):
t.decompose()
def render(tag):
clean(tag)
text = ""
for t in tag.descendants:
if isinstance(t, str):
text += t
elif t.name == "br":
text += "\n"
return text
def get_passages(soup):
passages = soup.find_all(class_="passage-table")
if passages is None:
return
text = ""
for p in passages:
text += get_passage(p) + "\n\n"
return text.replace(' ', ' ').strip()
def get_passage(soup):
root = soup.find(class_="result-text-style-normal")
if root is None:
return
passage = " ".join(map(lambda t: render(t), root.select("p, div.poetry p")))
return passage
def get_verses(soup):
verses = soup.find_all(class_="passage-table")
if verses is None:
return
text = "; ".join([get_verse(v) for v in verses])
return text
def get_verse(soup):
root = soup.find(class_="dropdown-display-text")
if root is None:
return
verse = ", ".join(root.descendants)
return verse
def clean_whitespace(text):
UNICODE_WHITESPACE_CHARACTERS = [
"\u0009", # character tabulation
"\u000a", # line feed
"\u000b", # line tabulation
"\u000c", # form feed
"\u000d", # carriage return
"\u0020", # space
"\u0085", # next line
"\u00a0", # no-break space
"\u1680", # ogham space mark
"\u2000", # en quad
"\u2001", # em quad
"\u2002", # en space
"\u2003", # em space
"\u2004", # three-per-em space
"\u2005", # four-per-em space
"\u2006", # six-per-em space
"\u2007", # figure space
"\u2008", # punctuation space
"\u2009", # thin space
"\u200A", # hair space
"\u2028", # line separator
"\u2029", # paragraph separator
"\u202f", # narrow no-break space
"\u205f", # medium mathematical space
"\u3000", # ideographic space
]
for char in UNICODE_WHITESPACE_CHARACTERS:
text = text.replace(char, " ")
# Consolidate multiple spaces into one.
return " ".join(text.split())
def format_discord(passage):
return f"*{passage}*"
query, version, show_verse_numbers, use_ascii, discord = parse_args()
soup = get_soup(query, version)
passages = get_passages(soup)
passages = clean_whitespace(passages)
verses = get_verses(soup)
if use_ascii:
passages = unidecode(passages)
if discord:
passages = format_discord(passages)
if passages is not None:
print(passages)
print(f"~ {verses}")
# Copy the output to the clipboard.
if discord:
subprocess.run("pbcopy", text=True, input=f"{passages}\n~ {verses}")
else:
subprocess.run("pbcopy", text=True, input=verses)
time.sleep(0.5)
subprocess.run("pbcopy", text=True, input=passages)
print()
print("Passage(s) copied to clipboard!")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment