first commit

This commit is contained in:
2022-12-29 12:53:15 -08:00
commit 84e37ba785
9 changed files with 864 additions and 0 deletions
+44
View File
@@ -0,0 +1,44 @@
Tested with Dependancy:Version
=======================
python3:3.10.6-1~22.04
python3-flask:2.0.1-2ubuntu1
bible-kjv:4.37
uwsgi-core:2.0.20-4
uwsgi-plugin-python3:2.0.20-4
apache2:2.4.52-1ubuntu4.2
Installation
======
This software expects to be installed at /opt/kjv-api/
This installation method is intended to play nice among other Virtual Hosts
0. Install dependancies listed at the head of this document.
- You may substitute the webserver for one of your choice, as long as it's capable
of reverse proxy
1. For security, create a new user with the name 'kjv'
- sudo useradd -r -s /bin/false kjv
2. Change ownership of the /opt/kjv-api/ folder to the kjv user
- sudo chmod -R kjv:kjv /opt/kjv-api/
3. Test the software runs by running kjv-api.sh as a non-root user (preferably as kjv user)
and that you are able to query it locally.
- term1. sudo -Hu kjv ./kjv-api.sh
- term2. curl -XGET 127.0.0.1:1611/random
4. If you intend to set up the software as a persistant service, copy kjv-api.service from
the install folder to /etc/systemd/system/kjv-api.service
- sudo cp /opt/kjv-api/install/kjv-api.service /etc/systemd/system/
- sudo systemctl enable kjv-api.service
- sudo systemctl start kjv-api.service
5. Configure your webserver as a reverse proxy to point to 127.0.0.1:1611 for where you
wish to have the api hosted.
- A sample configuration for apache is provided in the install folder.
- It is up to you to use https or not, but if you know nginx or haproxy you should be able
to set up local proxying to the WSGI server that runs the flask.
- There are WSGI modules for apache avaliable, which may be better for larger scaled
deployments, but http proxying to the local uwsgi served flask works fine.
+44
View File
@@ -0,0 +1,44 @@
KJV-API
=======
King James Version web API is a machine and human callable service to
query the King James Bible and provides useful features, such as random
verses and a quote of the day.
EXTERNAL DATA
=============
The software uses these JSON data stores as external dependancies, saved locally (included)
https://github.com/scrollmapper/bible_databases/blob/master/json/t_kjv.json
https://github.com/scrollmapper/bible_databases/blob/master/json/key_english.json
For additional dependancies see INSTALL.txt
INSTALLATION
============
See INSTALL.TXT
USAGE
=====
The program may be invoked by running kjv-api.sh in a non-root account, or via
systemd daemon process in a non-root account.
Alternately for testing, the program may be run on another port by running:
$ uwsgi_python3 --http-socket <listen address>:<port> --wsgi-file /opt/kjv-api/app.py --callable app
Visiting the base directory will show the user-facing documentation via web browser or cURL.
CONTENTS
========
app.py - main flask program
install/ - folder containing sample configurations
key_english.json - King James Bible books by index in JSON
t_kjv.json - King James Bible in JSON
index.html - root document help file - user-facing documentation
INSTALL.txt - detailed installation instructions
kjv-api.sh - script that properly runs flask through uwsgi
README.txt - this file
AUTHOR
======
Copyright Tyler R Dinsmoor 2022
+202
View File
@@ -0,0 +1,202 @@
# app.py
from flask import Flask, request, jsonify, send_file
import random, json
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
app = Flask(__name__)
blank_kjv = {"bookname":"zero","chapter":0,"text":"zero","verse":0}
blank_key = {'b': 0, 'c': 0, 'n': 'Blank', 't': 'XX', 'g': 0}
kjv = json.load(open('/opt/kjv-api/t_kjv.json','r'))
kjv = kjv['resultset']['row']
kjv.insert(0,blank_kjv)
key = json.load(open('/opt/kjv-api/key_english.json','r'))
key = key['resultset']['keys']
key.insert(0,blank_key)
idx_book = 1
idx_chapter = 2
idx_verse = 3
idx_text = 4
def respond_json(scripture):
kjv = {
"bookname": key[scripture[idx_book]]['n'],
"chapter": scripture[idx_chapter],
"verse": scripture[idx_verse],
"text": scripture[idx_text],
}
return jsonify(kjv)
def respond_text(scripture):
book = str(key[scripture[idx_book]]['n'])
chapter = str(scripture[idx_chapter])
verse = str(scripture[idx_verse])
text = str(scripture[idx_text])
return (book + " " + chapter + ":" + verse + " " + text)
@app.errorhandler(404)
def page_not_found(e):
message = {"error": "You're lost! Isaiah 41:10 - Fear thou not; for I am with thee: be not dismayed; for I am thy God: I will strengthen thee; yea, I will help thee; yea, I will uphold thee with the right hand of my righteousness."}
return jsonify(message)
@app.get("/")
def idx():
return send_file("index.html")
@app.get("/test")
def test():
pass
# json
@app.get("/random")
def random_verse_json():
scripture = random.choice(kjv)['field']
return(respond_json(scripture))
# plain
@app.get("/t/random")
def random_verse_plain():
scripture = random.choice(kjv)['field']
return(respond_text(scripture))
@app.get("/dbg/<ref>")
def dbg(ref):
return (ref)
# json
@app.get("/votd")
def verse_of_the_day():
today = int(str(date.today()).replace('-',''))
#this is deterministic because of today's date being used as the seed
todays_verse_idx = random.seed(today)
scripture = random.choice(kjv)['field']
return(respond_json(scripture))
# plaintext
@app.get("/t/votd")
def verse_of_the_day_text():
today = int(str(date.today()).replace('-',''))
#this is deterministic because of today's date being used as the seed
todays_verse_idx = random.seed(today)
scripture = random.choice(kjv)['field']
return(respond_text(scripture))
# 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.")
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.")
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.")
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.")
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.")
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)
+68
View File
@@ -0,0 +1,68 @@
<!DOCTYPE html>
<html>
<head>
<style>
html{
font-family: sans-serif;
height: 100%;
width: 768px;
overflow: hidden;
display: table;
}
</style>
</head>
<h1>KJV API</h1>
<p>KJV accessible via web calls</p>
<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/t/random">/t/random</a> - a random plaintext 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/t/votd">/t/votd</a> - plaintext 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>
</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>
<h3>Keyword searching</h3>
<p>'/s*/keyword' accepts a search word, and will return any verses matching the query</p>
<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>
<p>echo curl_exec(curl_init('https://api.1611.social/t/votd'));</p>
<p>You can see this work here:</p>
<b>
<?php echo curl_exec(curl_init('https://api.1611.social/t/votd')); ?>
</b>
<h3>Examples</h3>
<p><a href="https://api.1611.social/p/John3:16">https://api.1611.social/p/John3:16</a></p>
<p><a href="https://api.1611.social/s/faith">https://api.1611.social/s/faith</a></p>
<p><a href="https://api.1611.social/sp/hell">https://api.1611.social/sp/hell</a></p>
<p><a href="https://api.1611.social/spl/reprobate">https://api.1611.social/spl/reprobate</a></p>
<h3>Bugs</h3>
<p>Suggestions, bug reports or improvements can be reported to <a href="https://1611.social/@tyler">@tyler@1611.social</a></p>
</html>
+23
View File
@@ -0,0 +1,23 @@
# Example proxy configuration
<VirtualHost *:80>
ServerName domain.tld
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule (.*) https://%{SERVER_NAME}/$1 [R,L]
</VirtualHost>
<VirtualHost *:443>
ServerName domain.tld
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/domain.tld/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/domain.tld/privkey.pem
ProxyPreserveHost On
ProxyRequests Off
ProxyVia Off
ProxyPass / http://127.0.0.1:1611/
ProxyPassReverse / http://127.0.0.1:1611/
</VirtualHost>
+12
View File
@@ -0,0 +1,12 @@
[Unit]
Description=KJV API endpoint at localhost port 1611
[Service]
#ExecStart=uwsgi_python3 --http-socket :1611 --wsgi-file /opt/kjv-api/app.py --callable app --stats 127.0.0.1:9191
ExecStart=/opt/kjv-api/kjv-api.sh
User=kjv
EnvironmentFile=/etc/environment
KillMode=mixed
[Install]
WantedBy=multi-user.target
+468
View File
@@ -0,0 +1,468 @@
{
"resultset": {
"keys": [
{
"b": 1,
"c": 50,
"n": "Genesis",
"t": "OT",
"g": 1
},
{
"b": 2,
"c": 40,
"n": "Exodus",
"t": "OT",
"g": 1
},
{
"b": 3,
"c": 27,
"n": "Leviticus",
"t": "OT",
"g": 1
},
{
"b": 4,
"c": 36,
"n": "Numbers",
"t": "OT",
"g": 1
},
{
"b": 5,
"c": 34,
"n": "Deuteronomy",
"t": "OT",
"g": 1
},
{
"b": 6,
"c": 24,
"n": "Joshua",
"t": "OT",
"g": 2
},
{
"b": 7,
"c": 21,
"n": "Judges",
"t": "OT",
"g": 2
},
{
"b": 8,
"c": 4,
"n": "Ruth",
"t": "OT",
"g": 2
},
{
"b": 9,
"c": 31,
"n": "1 Samuel",
"t": "OT",
"g": 2
},
{
"b": 10,
"c": 24,
"n": "2 Samuel",
"t": "OT",
"g": 2
},
{
"b": 11,
"c": 22,
"n": "1 Kings",
"t": "OT",
"g": 2
},
{
"b": 12,
"c": 25,
"n": "2 Kings",
"t": "OT",
"g": 2
},
{
"b": 13,
"c": 29,
"n": "1 Chronicles",
"t": "OT",
"g": 2
},
{
"b": 14,
"c": 36,
"n": "2 Chronicles",
"t": "OT",
"g": 2
},
{
"b": 15,
"c": 10,
"n": "Ezra",
"t": "OT",
"g": 2
},
{
"b": 16,
"c": 13,
"n": "Nehemiah",
"t": "OT",
"g": 2
},
{
"b": 17,
"c": 10,
"n": "Esther",
"t": "OT",
"g": 2
},
{
"b": 18,
"c": 42,
"n": "Job",
"t": "OT",
"g": 3
},
{
"b": 19,
"c": 150,
"n": "Psalms",
"t": "OT",
"g": 3
},
{
"b": 20,
"c": 31,
"n": "Proverbs",
"t": "OT",
"g": 3
},
{
"b": 21,
"c": 12,
"n": "Ecclesiastes",
"t": "OT",
"g": 3
},
{
"b": 22,
"c": 8,
"n": "Song of Solomon",
"t": "OT",
"g": 3
},
{
"b": 23,
"c": 66,
"n": "Isaiah",
"t": "OT",
"g": 4
},
{
"b": 24,
"c": 52,
"n": "Jeremiah",
"t": "OT",
"g": 4
},
{
"b": 25,
"c": 5,
"n": "Lamentations",
"t": "OT",
"g": 4
},
{
"b": 26,
"c": 48,
"n": "Ezekiel",
"t": "OT",
"g": 4
},
{
"b": 27,
"c": 12,
"n": "Daniel",
"t": "OT",
"g": 4
},
{
"b": 28,
"c": 14,
"n": "Hosea",
"t": "OT",
"g": 4
},
{
"b": 29,
"c": 3,
"n": "Joel",
"t": "OT",
"g": 4
},
{
"b": 30,
"c": 9,
"n": "Amos",
"t": "OT",
"g": 4
},
{
"b": 31,
"c": 1,
"n": "Obadiah",
"t": "OT",
"g": 4
},
{
"b": 32,
"c": 4,
"n": "Jonah",
"t": "OT",
"g": 4
},
{
"b": 33,
"c": 7,
"n": "Micah",
"t": "OT",
"g": 4
},
{
"b": 34,
"c": 3,
"n": "Nahum",
"t": "OT",
"g": 4
},
{
"b": 35,
"c": 3,
"n": "Habakkuk",
"t": "OT",
"g": 4
},
{
"b": 36,
"c": 3,
"n": "Zephaniah",
"t": "OT",
"g": 4
},
{
"b": 37,
"c": 2,
"n": "Haggai",
"t": "OT",
"g": 4
},
{
"b": 38,
"c": 14,
"n": "Zechariah",
"t": "OT",
"g": 4
},
{
"b": 39,
"c": 4,
"n": "Malachi",
"t": "OT",
"g": 4
},
{
"b": 40,
"c": 28,
"n": "Matthew",
"t": "NT",
"g": 5
},
{
"b": 41,
"c": 16,
"n": "Mark",
"t": "NT",
"g": 5
},
{
"b": 42,
"c": 24,
"n": "Luke",
"t": "NT",
"g": 5
},
{
"b": 43,
"c": 21,
"n": "John",
"t": "NT",
"g": 5
},
{
"b": 44,
"c": 28,
"n": "Acts",
"t": "NT",
"g": 6
},
{
"b": 45,
"c": 16,
"n": "Romans",
"t": "NT",
"g": 7
},
{
"b": 46,
"c": 16,
"n": "1 Corinthians",
"t": "NT",
"g": 7
},
{
"b": 47,
"c": 13,
"n": "2 Corinthians",
"t": "NT",
"g": 7
},
{
"b": 48,
"c": 6,
"n": "Galatians",
"t": "NT",
"g": 7
},
{
"b": 49,
"c": 6,
"n": "Ephesians",
"t": "NT",
"g": 7
},
{
"b": 50,
"c": 4,
"n": "Philippians",
"t": "NT",
"g": 7
},
{
"b": 51,
"c": 4,
"n": "Colossians",
"t": "NT",
"g": 7
},
{
"b": 52,
"c": 5,
"n": "1 Thessalonians",
"t": "NT",
"g": 7
},
{
"b": 53,
"c": 3,
"n": "2 Thessalonians",
"t": "NT",
"g": 7
},
{
"b": 54,
"c": 6,
"n": "1 Timothy",
"t": "NT",
"g": 7
},
{
"b": 55,
"c": 4,
"n": "2 Timothy",
"t": "NT",
"g": 7
},
{
"b": 56,
"c": 3,
"n": "Titus",
"t": "NT",
"g": 7
},
{
"b": 57,
"c": 1,
"n": "Philemon",
"t": "NT",
"g": 7
},
{
"b": 58,
"c": 13,
"n": "Hebrews",
"t": "NT",
"g": 7
},
{
"b": 59,
"c": 5,
"n": "James",
"t": "NT",
"g": 7
},
{
"b": 60,
"c": 5,
"n": "1 Peter",
"t": "NT",
"g": 7
},
{
"b": 61,
"c": 3,
"n": "2 Peter",
"t": "NT",
"g": 7
},
{
"b": 62,
"c": 5,
"n": "1 John",
"t": "NT",
"g": 7
},
{
"b": 63,
"c": 1,
"n": "2 John",
"t": "NT",
"g": 7
},
{
"b": 64,
"c": 1,
"n": "3 John",
"t": "NT",
"g": 7
},
{
"b": 65,
"c": 1,
"n": "Jude",
"t": "NT",
"g": 7
},
{
"b": 66,
"c": 22,
"n": "Revelation",
"t": "NT",
"g": 8
}
]
}
}
Executable
+2
View File
@@ -0,0 +1,2 @@
#!/bin/sh
uwsgi_python3 --http-socket :1611 --wsgi-file /opt/kjv-api/app.py --callable app --stats 127.0.0.1:9191
+1
View File
File diff suppressed because one or more lines are too long