.
675 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Pathfinder-2e-API
2 |
3 | ## Purpose
4 | Provide a standardized set of Pathfinder Second Edition data for community use.
5 |
6 | ## Goals
7 | - Fast
8 | - Lightweight
9 | - Current
10 |
11 | ## Data Source
12 | The API gets its data from the official SRD - [Archives of Nethys](http://2e.aonprd.com/). It caches data it retrieves for 20 minutes, which is tracked per item (e.g. the Human Ancestry will go stale separately from the Dwarf Ancestry).
13 |
14 | ## How to Use V1
15 | Access (url)/api/v1/(resource)?(options)
16 |
17 | ### Ancestries
18 | (resource) = ancestries
19 |
20 | Options:
21 | - name
22 | - Not case sensitive
23 | - (url)/api/v1/ancestries?name=dwarf
24 | - (url)/api/v1/ancestries?name=dWaRF
25 |
26 |
27 | ## Running Locally
28 | 1. Navigate to the repo directory and execute:
29 | ```bash
30 | python3 api.py
31 | ```
32 |
33 | 1. Open a browser and point it to [http://localhost:5000](http://localhost:5000)
--------------------------------------------------------------------------------
/ancestries.py:
--------------------------------------------------------------------------------
1 | import helpers
2 | import datetime
3 | import json
4 | from bs4 import BeautifulSoup
5 | from requests import get
6 | from dateutil.parser import parse
7 |
8 | class AncestryDescription:
9 | def __init__(self, full_page, name):
10 | self.general = helpers.trim_html(full_page, '
', 'You Might...')
11 | self.you_might = helpers.ul_to_list(helpers.trim_html(full_page, 'You Might...
', 'Others Probably...'))
12 | if 'half' in name.lower():
13 | self.others_probably = helpers.ul_to_list(helpers.trim_html(full_page, 'Others Probably...
', f'{name} Mechanics'))
14 | else:
15 | self.others_probably = helpers.ul_to_list(helpers.trim_html(full_page, 'Others Probably...', 'Physical Description'))
16 | self.physical_description = ''
17 | self.society = ''
18 | self.alignment_religion = ''
19 | self.names = ''
20 | self.sample_names = ['']
21 |
22 | def toJSON(self):
23 | return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True, indent=4)
24 |
25 | class Ancestry:
26 | last_hit = datetime.datetime.now()
27 |
28 | def __init__(self, name, url):
29 | self.name = name
30 | self.hp = 0
31 | self.size = ''
32 | self.speed = 0
33 | self.boosts = ['']
34 | self.flaws = ['']
35 | self.languages = ['']
36 | self.specials = ['']
37 | self.url = url
38 | self.last_updated = 'never'
39 |
40 | def to_jsonify(self):
41 | json = {
42 | 'Name': self.name,
43 | 'Traits': self.traits,
44 | 'Description': {
45 | 'General': self.description.general,
46 | 'YouMight': self.description.you_might,
47 | 'OthersProbably': self.description.others_probably,
48 | 'PhysicalDescription': self.description.physical_description,
49 | 'Society': self.description.society,
50 | 'AlignmentReligion': self.description.alignment_religion,
51 | 'Names': self.description.names,
52 | 'SampleNames': self.description.sample_names
53 | },
54 | 'HP': self.hp,
55 | 'Size': self.size,
56 | 'Speed': self.speed,
57 | 'Boosts': self.boosts,
58 | 'Flaws': self.flaws,
59 | 'Languages': self.languages,
60 | 'Specials': self.specials,
61 | 'URL': self.url,
62 | 'LastUpdated': self.last_updated
63 | }
64 | return json
65 |
66 | def scrape(self):
67 | """ Visits the URL of the Ancestry record and scrapes its data """
68 | # don't scrape more than once every 20 minutes
69 | if (self.last_updated != 'never' and
70 | self.description is not None and
71 | self.hp is not None and
72 | self.size is not None and
73 | self.speed is not None and
74 | (datetime.datetime.now() - parse(self.last_updated)).total_seconds() < 1200):
75 | return False
76 |
77 | response = get(self.url)
78 | ancestral_soup = BeautifulSoup(response.text, 'html.parser')
79 |
80 | # scrape traits
81 | trait_spans = ancestral_soup.find_all("span", class_="trait")
82 | trait_list = [t.a.contents[0] for t in trait_spans]
83 | self.traits = trait_list
84 |
85 | # scrape description
86 | self.description = AncestryDescription(str(ancestral_soup), self.name)
87 |
88 | self.last_updated = str(datetime.datetime.now())
89 | return True
90 |
91 | def get_all():
92 | """ Returns a list of all ancestries currently on AoN """
93 | scraped = []
94 | response = get('http://2e.aonprd.com/Ancestries.aspx')
95 | Ancestry.last_hit = datetime.datetime.now()
96 | ancestral_soup = BeautifulSoup(response.text, 'html.parser')
97 | titles = ancestral_soup.find_all("h2", class_="title")
98 | links = [t.find_all("a")[1] for t in titles]
99 | ancestries_list = [[l.contents[0], 'http://2e.aonprd.com/' + l['href']] for l in links]
100 | for a in ancestries_list:
101 | name = a[0]
102 | url = a[1]
103 | scraped.append(Ancestry(name, url))
104 | return scraped
105 |
106 | # test_ancestries = [
107 | # {
108 | # 'Name': 'Human',
109 | # 'Traits': [
110 | # 'Human',
111 | # 'Humanoid'
112 | # ],
113 | # 'Description': {
114 | # 'General': 'Human',
115 | # 'YouMight': [
116 | # 'Be a human',
117 | # 'Do human things'
118 | # ],
119 | # 'OthersProbably': [
120 | # 'Live longer than you',
121 | # 'Are older than you'
122 | # ],
123 | # 'PhysicalDescription': 'Humanistic',
124 | # 'Society': 'Humanitarian',
125 | # 'AlignmentReligion': 'Everything',
126 | # 'Names': 'Namey',
127 | # 'SampleNames': [
128 | # 'Bob',
129 | # 'Alice'
130 | # ]
131 | # },
132 | # 'HP': '8',
133 | # 'Size': 'Medium',
134 | # 'Speed': '25 feet',
135 | # 'Boosts': [
136 | # 'Free',
137 | # 'Free'
138 | # ],
139 | # 'Flaws': [
140 | # 'None'
141 | # ],
142 | # 'Languages': [
143 | # 'Common'
144 | # ],
145 | # 'Specials': [],
146 | # 'URL': '',
147 | # 'LastUpdated': ''
148 | # }
149 | # ]
--------------------------------------------------------------------------------
/api.py:
--------------------------------------------------------------------------------
1 | import data
2 |
3 | import flask
4 | from flask import request, jsonify
5 |
6 | app = flask.Flask(__name__)
7 | app.config["DEBUG"] = True
8 |
9 | api_data = data.Data()
10 |
11 | @app.route('/', methods=['GET'])
12 | def home():
13 | return "Pathfinder 2e API
This is an API for the Second Edition of the Pathfinder TTRPG. All data comes from the official SRD Archives of Nethys.
"
14 |
15 | # @app.route('/actions', methods=['GET'])
16 |
17 | @app.route('/api/v1/ancestries', methods=['GET'])
18 | def ancestries_router():
19 | params = request.args
20 | name = params.get('name')
21 | return jsonify(api_data.get_ancestries(name=name))
22 |
23 | # @app.route('/archetypes', methods=['GET'])
24 | # @app.route('/backgrounds', methods=['GET'])
25 | # @app.route('/classes', methods=['GET'])
26 | # @app.route('/conditions', methods=['GET'])
27 | # @app.route('/equipment', methods=['GET'])
28 | # @app.route('/feats', methods=['GET'])
29 | # @app.route('/hazards', methods=['GET'])
30 | # @app.route('/monsters', methods=['GET'])
31 | # @app.route('/skills', methods=['GET'])
32 | # @app.route('/spells', methods=['GET'])
33 |
34 | @app.errorhandler(404)
35 | def page_not_found(e):
36 | return "404
The resource could not be found.
", 404
37 |
38 | app.run()
--------------------------------------------------------------------------------
/data.py:
--------------------------------------------------------------------------------
1 | import ancestries
2 | import datetime
3 |
4 | class Data:
5 | def __init__(self):
6 | self.all_ancestries = []
7 |
8 | def get_ancestries(self, name=None):
9 | # check for new entries every 20 minutes
10 | if ((datetime.datetime.now() - ancestries.Ancestry.last_hit).total_seconds() >= 1200 or
11 | len(self.all_ancestries) == 0):
12 | tmp_ancestries = ancestries.Ancestry.get_all()
13 | for a in tmp_ancestries:
14 | if a.name not in [x.name for x in self.all_ancestries]:
15 | self.all_ancestries.append(a)
16 |
17 | # check for a matching name
18 | if name is not None:
19 | for a in self.all_ancestries:
20 | if a.name.upper() == name.upper():
21 | a.scrape()
22 | return a.to_jsonify()
23 | # if no matches are found, report
24 | return f'Ancestry "{name}" not found'
25 |
26 | # convert to json, append, and return
27 | json_list = []
28 | for a in self.all_ancestries:
29 | a.scrape()
30 | json_list.append(a.to_jsonify())
31 | return json_list
--------------------------------------------------------------------------------
/helpers.py:
--------------------------------------------------------------------------------
1 | from bs4 import BeautifulSoup
2 |
3 | def trim_html(src, start, end):
4 | tmp = src[src.index(start)+len(start):src.index(end)]
5 | tmp = tmp.replace('
','\n')
6 | tmp = tmp.replace('','')
7 | tmp = tmp.replace('','')
8 | tmp = tmp.replace(u'\u2014','--')
9 | tmp = tmp.replace(u'\u2019',"'")
10 | return tmp
11 |
12 | def ul_to_list(src):
13 | soup = BeautifulSoup(src, 'html.parser')
14 | items = []
15 | for li in soup.find_all("li"):
16 | items.append(li.string)
17 | return items
--------------------------------------------------------------------------------