Switched to sqlite virtual table searching, a built-in reference parser, and changed endpoints to be more sane.

This commit is contained in:
2023-01-08 11:14:29 -08:00
parent 65f0064953
commit 2d52f6e9d4
5 changed files with 230 additions and 217 deletions
+175 -183
View File
@@ -2,18 +2,11 @@
from flask import Flask, request, jsonify, send_file
import random
from datetime import date # used for daily verse
import subprocess # use of "bible" system application for parsing and returning Bible in KJV0
import shlex # input security
import sqlite3
import string
DEBUG = 2
DEBUG_OUT = 0
import string # for literals
DEBUG = 0
"""
NOTES
- working on getting multi-search terms working right
Lookup_bookname does not like it lol.
To-do:
- Add data and function for Webster 1828 dictionary lookup
--https://github.com/DataWar/1828-dictionary
@@ -34,9 +27,9 @@ def dbg2(message):
dbg("Debug Level 1 Enabled")
dbg2("Debug Level 2 Enabled")
kjv_db = sqlite3.connect("kjv.db")
kjv_cur = kjv_db.cursor()
verse_ids = [i[0] for i in kjv_cur.execute("SELECT id FROM t_kjv;").fetchall()]
kjv_cur = sqlite3.connect("kjv.db").cursor()
verse_ids = [i[0] for i in kjv_cur.execute("SELECT id FROM fts_kjv;").fetchall()]
book_names = [i[0] for i in kjv_cur.execute("SELECT n FROM key_english").fetchall()]
app = Flask(__name__)
@@ -48,26 +41,182 @@ idx_text = 4
def lookup_one_verse(verse_id):
dbg("lookup_one_verse({})".format(verse_id))
return(kjv_cur.execute("SELECT * FROM t_kjv WHERE id = {};".format(verse_id)).fetchone())
return(kjv_cur.execute("SELECT * FROM fts_kjv WHERE id = {};".format(verse_id)).fetchone())
def lookup_bookname(book_id):
dbg2("lookup_bookname({})".format(book_id))
result = kjv_cur.execute("SELECT n FROM key_english WHERE b = {}".format(book_id)).fetchone()[0]
result = kjv_cur.execute("SELECT n FROM key_english WHERE b = {};".format(book_id)).fetchone()
if result:
result = result[0]
dbg("lookup_bookname: {}".format(result))
return result
def lookup_book_id(bookname):
dbg2("lookup_book_id({})".format(bookname))
result = kjv_cur.execute("SELECT b FROM key_english WHERE n = '{}';".format(bookname)).fetchone()
if result:
result = result[0]
return result
def lookup_fts(kwds):
return kjv_cur.execute("SELECT * FROM fts_kjv('{}')".format(kwds)).fetchall()
def lookup_contains(kw):
results = kjv_cur.execute("SELECT * FROM t_kjv WHERE t LIKE '%{}%';".format(kw)).fetchall()
results = kjv_cur.execute("SELECT * FROM fts_kjv WHERE t LIKE '%{}%';".format(kw)).fetchall()
dbg("lookup_contains({}) {} results".format(kw,len(results)))
return results
def lookup_by_verse_id(start_verse, end_verse = None):
if not end_verse:
results = kjv_cur.execute("SELECT * FROM fts_kjv WHERE id = {};".format(start_verse)).fetchall()
else:
results = kjv_cur.execute("SELECT * FROM fts_kjv WHERE id BETWEEN {} AND {};".format(start_verse, end_verse)).fetchall()
return results
def get_verse_id(book_id, chapter, verse):
# verse_id is 00000000 (00 000 000) (book + chapter + verse)
chapterid = chapter.zfill(3)
verseid = verse.zfill(3)
return str(book_id)+chapterid+verseid
def check_lone_word(text, kw):
kw_length = len(kw)
kw_location = text.find(kw)
try:
if (text[kw_location-1]).isalpha():
return False
except IndexError:
# If it's an indexerror, it's likely that
# the keyword is at the end or beginning
# of the text.
pass
try:
if (text[kw_location+kw_length]).isalpha():
return False
except IndexError:
pass
return True
def lookup_contains_exact(kw):
results = kjv_cur.execute("SELECT * FROM t_kjv WHERE t LIKE '%{}%';".format(kw)).fetchall()
preresult = lookup_contains(kw)
results = []
for result in preresult:
if check_lone_word(result[idx_text], kw):
results.append(result)
dbg("lookup_contains({}) {} results".format(kw,len(results)))
return results
def ref_input_cleaning(ref, valid_chars=(string.ascii_letters + string.digits + ":-,")):
# removed invalid characters, whitespace
ref = list(ref.replace(" ",""))
# basic validation checks
# do we have a valid verse designator?
if ref.count(':') != 1:
return False
# are we only checking one range?
if ref.count("-") > 1:
return False
# does our argument end in a number or an endrange?
if not ref[-1].isdigit() and ref[-1] != '-':
return False
# preserve book number if we can guess the first
# character is a number and 3nd is a letter
if ref[0].isdigit() and ref[1].isalpha():
prefix = ref.pop(0) + " "
else:
prefix = ''
# clean the rest of the reference
cleaned_ref = []
for character in ref:
if character in valid_chars:
cleaned_ref.append(character)
# Was there a problem?
if len(cleaned_ref) == 0:
return False
return prefix + ''.join(cleaned_ref).capitalize()
def search_input_cleaning(query, valid_chars=string.ascii_letters + string.digits + "+^\" "):
cleaned_query = []
for character in query:
if character in valid_chars:
cleaned_query.append(character)
return ''.join(cleaned_query)
def ref_parse_book(ref):
for book in reversed(book_names):
if book.startswith(ref):
return book
return False
@app.get("/show")
def show():
ref = request.args.get("kw", "John 3:16")
dbg("show({})".format(ref))
ref = ref_input_cleaning(ref)
if not ref:
return("Invalid Query")
verse_results = []
#check if the search is for multiple refs
refs = ref.split(',')
for ref in refs:
dbg("show.reference({})".format(ref))
maybe_chapter = ref.split(':')[0][3:]
ref_chapter = ''
for maybedigit in maybe_chapter:
if maybedigit.isdigit():
ref_chapter += maybedigit
ref_verse = ref.split(':')[1]
if ref.__contains__('-'):
if ref.endswith('-'):
start_verse = ref_verse.split("-")[0]
end_verse = '500' # just get all the verses if it ends with a dash
else:
start_verse, end_verse = ref_verse.split("-")[0], ref_verse.split("-")[1]
else:
start_verse = ref_verse
end_verse = None
end_verse_id = None
book_str = ref_input_cleaning(ref, string.ascii_letters)
ref_bookname = ref_parse_book(book_str)
ref_book_id = lookup_book_id(ref_bookname)
if not ref_book_id:
return("Book reference invalid. Ex: 1John3:16")
start_verse_id = get_verse_id(ref_book_id, ref_chapter, start_verse)
if end_verse:
end_verse_id = get_verse_id(ref_book_id, ref_chapter, end_verse)
dbg2("show.start_verse_id({})".format(start_verse_id))
dbg2("show.end_verse_id({})".format(end_verse_id))
results = lookup_by_verse_id(start_verse_id, end_verse_id)
dbg2("show.results({})".format(results))
for result in results:
verse_results.append(result)
response_list = response_multi(verse_results)
arg_view = request.args.get("view", None)
if arg_view == "plain" or arg_view == "rich":
response = "<br>".join(response_list)
else:
response = jsonify(response_list)
return response
def response_dict(scripture):
# used for JSON responses
verse = {
"bookname": lookup_bookname(scripture[idx_book]),
"chapter": scripture[idx_chapter],
@@ -86,7 +235,7 @@ def response_text(scripture):
def response_rich(scripture):
css = open('human_readable.css', 'r')
response_html = open('response.html', 'r')
# passage, text
# (passage, text) is the html formatting arguments
bookname = lookup_bookname(scripture[idx_book])
chapter = str(scripture[idx_chapter])
@@ -99,7 +248,7 @@ def response_rich(scripture):
def response_single(scripture):
dbg2("response_single({})".format(scripture))
arg_view = request.args.get("view", None)
arg_view = request.args.get("view", 'json')
dbg("->requested_format = {}".format(arg_view))
if arg_view == "plain":
return(response_text(scripture))
@@ -112,20 +261,17 @@ def response_single(scripture):
def response_multi(scriptures):
dbg2("response_multi({})".format(scriptures))
response = []
arg_view = request.args.get("view", None)
arg_view = request.args.get("view", 'json')
dbg("search ->requested_format = {}".format(arg_view))
if arg_view == "plain":
for scripture in scriptures:
dbg2("for scripture in scriptures content: {}".format(scripture))
response.append(response_text(scripture))
elif arg_view == "rich":
for scripture in scriptures:
dbg2("for scripture in scriptures content: {}".format(scripture))
response.append(response_rich(scripture))
else:
for scripture in scriptures:
dbg2("for scripture in scriptures content: {}".format(scripture))
response.append(response_dict(scripture))
return(response)
@@ -146,6 +292,10 @@ def page_not_found(e):
def idx():
return send_file("index.html")
@app.get("/salvation")
def salvation():
return send_file("heaven.html")
#backwards-compatibility
@app.get('/t/random', defaults={'view':'plain'})
@app.get('/random')
@@ -164,51 +314,18 @@ def verse_of_the_day():
scripture = lookup_one_verse(random.choice(verse_ids))
return(response_single(scripture))
# splits by delimiter then checks for any special characters
# if special characters are found, the query is rejected
def clean_keywords(kwds):
kwds = kwds.split(',')
for kwd in kwds:
if not kwd.isalpha():
return False
dbg("clean_keywords = ({})".format(kwds))
return kwds
def search_and(cleaned_keywords):
results = []
for keyword in cleaned_keywords:
dbg("search->for keyword in database({})".format(keyword))
#This is groups of results per word. Must check between groups.
results.append(lookup_contains(keyword))
return (results)
def search_any(cleaned_keywords):
results = []
for keyword in cleaned_keywords:
dbg("search->for keyword in database({})".format(keyword))
for result in lookup_contains(keyword):
results.append(result)
dbg("==> search_any: {} results".format(len(results)))
return(results)
@app.get("/search")
def search():
keywords = request.args.get("kw", None)
dbg("search({})".format(keywords))
if keywords:
cleaned_keywords = clean_keywords(keywords)
if not clean_keywords:
cleaned_keywords = search_input_cleaning(keywords)
if not cleaned_keywords:
return ("Invalid Query: Special Characters")
else:
return ("No Query")
if request.args.get("operator",None) == "and":
results = search_and(cleaned_keywords)
else:
results = search_any(cleaned_keywords)
results = lookup_fts(cleaned_keywords)
response_list = response_multi(results)
arg_view = request.args.get("view", None)
@@ -219,128 +336,3 @@ def search():
dbg2("Search Response: {}".format(response))
return response
@app.get("/qwerty")
def test():
style = css.read()
return (style + "Hello! Testing!")
# plain
@app.get("/<passage>")
def passage_json(passage):
try:
command = "bible -f {}".format(shlex.quote(passage))
result = str(subprocess.check_output(command, shell=True), "utf-8")
if len(result) == 0:
raise Exception("You have entered an incorrect syntax or your query returned no results.")
return jsonify(result)
except Exception as e:
return jsonify(str(e))
# pretty
@app.get("/p/<passage>")
def passage_text(passage):
try:
command = "bible {}".format(shlex.quote(passage))
result = str(subprocess.check_output(command, shell=True), "utf-8")
if len(result) == 0:
raise Exception("You have entered an incorrect syntax or your query returned no results.")
return result
except Exception as e:
return str(e)
#search
@app.get("/s/<keyword>")
def search_json(keyword):
try:
command = "bible -f ??{}".format(shlex.quote(keyword))
result = str(subprocess.check_output(command, shell=True), "utf-8")
if len(result) == 0:
raise Exception("You have entered an incorrect syntax or your query returned no results.")
if result.contains("??word"):
raise Exception("Not found.")
result = result.split()
result = result[7:]
return jsonify(result)
except Exception as e:
return jsonify(str(e))
#search
@app.get("/sp/<keyword>")
def search_text(keyword):
try:
command = "bible -f ??{}".format(shlex.quote(keyword))
result = str(subprocess.check_output(command, shell=True), "utf-8")
if len(result) == 0:
raise Exception("You have entered an incorrect syntax or your query returned no results.")
if result.contains("??word"):
raise Exception("Not found.")
result = result.split()
result = result[7:]
result = '<br>'.join(map(str, result))
return result
except Exception as e:
return str(e)
#search text json
@app.get("/sl/<keyword>")
def search_json_return(keyword):
try:
command = "bible -f ??{}".format(shlex.quote(keyword))
result = str(subprocess.check_output(command, shell=True), "utf-8")
if len(result) == 0:
raise Exception("You have entered an incorrect syntax or your query returned no results.")
if result.contains("??word"):
raise Exception("Not found.")
result = result.split()
result = result[7:]
complete_result = []
for verse in result:
complete_result.append(passage_text(verse))
return jsonify(complete_result)
except Exception as e:
return str(e)
#search pretty
@app.get("/spl/<keyword>")
def search_text_return(keyword):
try:
command = "bible -f ??{}".format(shlex.quote(keyword))
result = str(subprocess.check_output(command, shell=True), "utf-8")
if len(result) == 0:
raise Exception("You have entered an incorrect syntax or your query returned no results.")
if result.contains("??word"):
raise Exception("Not found.")
result = result.split()
result = result[7:]
complete_result = []
for verse in result:
complete_result.append(passage_text(verse))
result = '<br>'.join(map(str, complete_result))
return result
except Exception as e:
return str(e)
#search pretty multiple
@app.get("/splm/<keywords>")
def search_text_return_multiple(keywords):
if len(keywords.split('&')) > 1:
pass
try:
command = "bible -f ??{}".format(shlex.quote(keyword))
result = str(subprocess.check_output(command, shell=True), "utf-8")
if len(result) == 0:
raise Exception("You have entered an incorrect syntax or your query returned no results.")
if result.contains("??word"):
raise Exception("Not found.")
result = result.split()
result = result[7:]
complete_result = []
for verse in result:
complete_result.append(passage_text(verse))
result = '<br>'.join(map(str, complete_result))
return result
except Exception as e:
return str(e)
+30
View File
@@ -0,0 +1,30 @@
<h3>1. Understand you are a sinner:</h3>
<p>No one is good enough to go to Heaven on their own merit. No matter how much good we do, we still come short.</p>
<p><b>Romans 3:23</b>(a) For all have sinned, and come short of the glory of God;</p>
<p><b>Romans 3:10</b> As it is written, There is none righteous, no, not one:</p>
<h3>2. Realize the penalty for your sin.</h3>
<p>There is a payment for our sin, which is eternal death in a place called Hell and the Lake of Fire.</p>
<p><b>Romans 6:23</b> For the wages of sin is death; but the gift of God is eternal life through Jesus Christ our Lord.</p>
<p><b>Revelation 20:14</b> And death and hell were cast into the lake of fire. This is the second death. And whosoever was not found written in the book of life was cast into the lake of fire.</p>
<h3>3. Believe that Christ died, was buried and rose from the grave as a payment for your sin.</h3>
<p>Salvation is simply putting your faith (or believing) that Jesus Christ paid for your sins.</p>
<p><b>Romans 5:8</b> But God commendeth his love toward us, in that, while we were yet sinners, Christ died for us.</p>
<p><b>John 3:16</b> For God so loved the world, that he gave his only begotten Son, that whosoever believeth in him should not perish, but have everlasting life.</p>
<p><b>1 Corinthians 15:3-4</b> ...Christ died for our sins according to the scriptures; And that he was buried, and that he rose again the third day according to the scriptures:</p>
<h3>4. Understand that salvation will last forever.</h3>
<p>Eternal life is a gift purchased by the blood of Jesus and offered freely to those who call upon Him. Anyone who believes on the Lord Jesus Christ will be saved forever. Being saved is a one time event.</p>
<p><b>Titus 1:2</b> In hope of eternal life, which God, that cannot lie, promised before the world began;</p>
<p><b>John 3:15-16</b> For God so loved the world, that he gave his only begotten Son, that whosoever believeth in him should not perish, but have everlasting life.</p>
<p>When God saves you he gives you eternal life; in other words, life that will last forever. So, when he saves you, it is forever. No matter what you do after you receive “the gift of eternal life” it can never end. Otherwise God lied, and we know that God “cannot lie”.</p>
<h3>5. Pray and ask Jesus Christ to save you.</h3>
<p><b>Romans 10:9-10</b> That if thou shalt confess with thy mouth the Lord Jesus, and shalt believe in thine heart that God hath raised him from the dead, thou shalt be saved. For with the heart man believeth unto righteousness; and with the mouth confession is made unto salvation. </p>
<p>Let us help you word a prayer. Realize it is not mere words that save, but your faith in Jesus Christ.</p>
<p><i>Dear Jesus, I know that I am a sinner, and I know I deserve to go to Hell. But I believe that you died on the cross, was buried, and rose from the grave to pay for my sins. Please save me and take me to Heaven when I die. Amen.</i></p>
+1 -1
View File
@@ -1,7 +1,7 @@
<style>
html{
font-family: sans-serif;
font-size: 50pt;
font-size: 14pt;
width: 768px;
}
</style>
+24 -33
View File
@@ -16,30 +16,31 @@ html{
<h3>Usage</h3>
<p>URL Root: <a href="https://api.1611.social/">https://api.1611.social/<a> (you are here)</p>
<ul>
<li><a href="https://api.1611.social/random">/random</a> - a random JSON-encoded verse. Different every time.</li>
<li><a href="https://api.1611.social/votd">/votd</a> - json-encoded verse changes from day-to-day.</li>
<li><a href="https://api.1611.social/p/john3:16">/p/someverse</a> - will attempt to parse and return the Bible requested in plaintext (pretty, with escape characters)</li>
<li><a href="https://api.1611.social/John3:16">/someverse</a> - will attempt to parse and return the Bible requested in plaintext, with no escape characters. Newlines are encoded as "/n" - for you to handle.</li>
<li><a href="https://api.1611.social/s/faith">/s/keyword</a> - will return any verses containing keyword in a JSON list</li>
<li><a href="https://api.1611.social/sp/faith">/sp/keyword</a> - will return any verses containing keyword in a newline (html break) delimited list</li>
<li><a href="https://api.1611.social/sl/faith">/sl/keyword</a> - will return all searched references and the scripture in JSON</li>
<li><a href="https://api.1611.social/spl/faith">/spl/keyword</a> - will return all searched references and the scripture in text delmied by a newline</li>
<li><a href="https://api.1611.social/random">/random</a> - A random verse from the KJV</li>
<li><a href="https://api.1611.social/votd">/votd</a> - A random verse that changes from day-to-day.</li>
<li><a href="https://api.1611.social/search?kw=faith">/search</a> - Search keywords</li>
<li><a href="https://api.1611.social/show?kw=John3:16">/show</a> - Show a set of verses</li>
</ul>
<h3>Verse searching</h3>
<p>'/someverse' accepts verse references in a variety of forms, including single verses and verse ranges. For
example:</p>
<li>Jn3:16, john3:16,17 ps1:1-6</li>
<p>
Most recognizable abbreviations are allowed, and spelling errors are ignored if the book can be made
out in the first few characters. No distinction is made between upper and lower case. Multiple
references may be provided on an input line, delimited by spaces or commas.
</p>
<p>'/show?kw=' will try to match single verses and verse ranges. For example:</p>
<pre>
<code>
/search?kw=john3:16
/search?kw=John 3:13-18
/search?kw=2 Peter 2:8
/search?kw=1 John 3:3- (gets the whole rest of the chapter)
/search?kw=ecc 2:1-
</code>
</pre>
<h3>Keyword searching</h3>
<p>'/s*/keyword' accepts a search word, and will return any verses matching the query</p>
<p>'/search?kw=keyword' accepts a search word, and will return any verses matching the query. For example:</p>
<pre>
<code>
</code>
</pre>
<h3>Integration</h3>
<p>If you want to put the verse of the day into your website, you can use the following example php:</p>
@@ -47,8 +48,8 @@ example:</p>
<pre>
<code>
FUTURE ENDPOINT SPECIFICATION
======================
API ENDPOINT SPECIFICATION v 0.2
================================
/random - outputs a random verse
/votd - returns verse of the day
@@ -68,20 +69,10 @@ default: json
search default: faith
show default: john3:16
?op=
- Multi-keyword search operator
- and - both keywords must be in verse
- any - any keyword must be in a verse
search default: any
?fz=
- on - fuzzy search on
default: off
EXAMPLES
========
api.tld/random?view=json
api.tld/votd?view=human
api.tld/random
api.tld/votd?view=rich
api.tld/show?kw=john3:16,1john5:7&view=plain
api.tld/search?kw=faith
api.tld/search?kw=nimrod&view=rich
BIN
View File
Binary file not shown.