From 230ed709a0631c4bd539cf698e0172a260f3bafd Mon Sep 17 00:00:00 2001 From: "B. Stack" Date: Wed, 10 Jul 2024 20:46:08 -0400 Subject: prepare for reservations --- README.md | 21 +++++++++++++++++++++ libraries/aspen.py | 49 ++++++++++++++++++++++++++++++++++++++++++++++--- libraries/base.py | 7 +++++++ library_info_cli.py | 21 ++++++++++++--------- 4 files changed, 86 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 4d2fcf1..407bab1 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,26 @@ Columns for items checked out: * how many times renewed already * when checked out +### Reserved items + +* position in line +* date placed +* format +* title +* picture +* pickup location +* status + +### Reserved items + +* position in line +* date placed +* format +* title +* picture +* pickup location +* status + plugin-based, so I can write a plugins/aspen.py with some standard format output. # Improvements @@ -57,3 +77,4 @@ I still need to implement these features. * add reservations for each library * add library card expiration date +* try designing the --output html to save the images to files in ./images/{img50}.{img_format} diff --git a/libraries/aspen.py b/libraries/aspen.py index 1d8de39..40e67c1 100644 --- a/libraries/aspen.py +++ b/libraries/aspen.py @@ -44,6 +44,51 @@ class Library(BaseLibrary): # log in now. Why would we not? self.login() + def get_reservations(self, verbose = False): + availableReservations = [] + unavailableReservations = [] + b = self.baseurl + s = self.session + # step 1: visit "titles on hold" page so it does not complain that I am taking shortcuts + headers = { + "Referer": f"{b}/MyAccount/CheckedOut?source=all" + } + s.get(f"{b}/Holds?source=ils",headers=headers) + output = s.get(f"{b}/AJAX?method=getHolds&source=all",headers=headers) + output = json.loads(output.content)["holds"].replace("\xa0"," ") + soup = BeautifulSoup(output, "html.parser") + try: + unavailableholds_all = soup.find("label",attrs={"for":"unavailableHoldSort_all"}).parent.next_sibling.next_sibling + except AttributeError: + # the label will not exist if there are no unavaiableHolds + unavailableholds_all = None + if unavailableholds_all: + items = unavailableholds_all.find_all("div",class_=["result"]) + for i in items: + labels = [j.text for i in i.find_all("div","result-label")] + values = [j.text for i in i.find_all("div","result-value")] + values_dict = dict(map(lambda i,j:(i,j),labels,values)) + title_obj = i.find("a",class_="result-title") + img_href = i.find("img")["src"] + img_b64, img_type = self.get_image(img_href) + obj = { + "position": values_dict["Position"], + "status": values_dict["Status"], + "date_placed": values_dict["Date Placed"], + "format": values_dict["Format"], + "location": values_dict["Pickup Location"], + "title": title_obj.text, + "img_href": img_href, + "img50": img_b64[:50], + "img": img_b64, + "img_type": img_type, + } + unavailableReservations.append(obj) + + # WORKHERE: availableHolds, might not be available. + # Return a single list of objects + return availableReservations + unavailableReservations + def get_checkouts(self, verbose = False): # WORKHERE: no example of possible/completed renewals at this time checked_out_objects = [] @@ -80,9 +125,7 @@ class Library(BaseLibrary): print(f"DEBUG: Values_dict: {values_dict}",file=sys.stderr) # contains Call number, Format, Barcode, Due img_href = i.find("img", class_="listResultImage")["src"] - img_response = s.get(img_href) - img_b64 = base64.b64encode(img_response.content).decode() - img_type = img_response.headers["Content-Type"] + img_b64, img_type = self.get_image(img_href) # normalize format item_format = "" item_format = "book" if "book" in values_dict["Format"].lower() else "" diff --git a/libraries/base.py b/libraries/base.py index 74394be..f6f75e4 100644 --- a/libraries/base.py +++ b/libraries/base.py @@ -48,3 +48,10 @@ class BaseLibrary: """ This is where the login interaction should happen. """ + + def get_image(self, url): + """ Given the url, return the base64-encoded contents and image type as a tuple. """ + img_response = self.session.get(url) + img_b64 = base64.b64encode(img_response.content).decode() + img_type = img_response.headers["Content-Type"] + return img_b64, img_type diff --git a/library_info_cli.py b/library_info_cli.py index 57b9188..b77e9f4 100755 --- a/library_info_cli.py +++ b/library_info_cli.py @@ -61,7 +61,7 @@ def serialize(item): eprint(f"WARNING: unknown type {type(item)} for json-serializing object {item}") return item -def html(items): +def html(checkouts): # Uses https://datatables.net/download/builder?dt/jq-3.7.0/dt-2.0.8/cr-2.0.3/fh-4.0.1 # with css corrected by having the utf-8 line at the top of the .min.css file. """ Make an html of the items """ @@ -78,7 +78,7 @@ def html(items): prn("\n") prn("\n") seen_accounts = [] - for i in items: + for i in checkouts: if i["patron"] not in seen_accounts: seen_accounts.append(i["patron"]) if seen_accounts: @@ -91,11 +91,12 @@ def html(items): except AttributeError: now = datetime.datetime.utcnow() prn("Last modified: " + now.strftime("%FT%TZ") + "\n") - if len(items): + prn("

Checkouts

\n") + if len(checkouts): prn("\n") prn(f"\n") prn("\n") - for i in items: + for i in checkouts: prn(f"") prn(f"") prn(f"") @@ -112,6 +113,8 @@ def html(items): prn(f"\n") prn(f"\n") prn(f"
PatronbarcodedueformatcoverTitle
{i['patron']}{i['barcode']}
\n") + else: + prn(f"No checkouts.\n") prn(f"\n") prn(f"\n") @@ -132,14 +135,14 @@ if "__main__" == __name__: if debuglevel >= 1: eprint(args) if single: - items = library_info_lib.get_single_checkouts(alias = single, full_images = full_images, verbose = debuglevel >= 5) + checkouts = library_info_lib.get_single_checkouts(alias = single, full_images = full_images, verbose = debuglevel >= 5) else: - items = library_info_lib.get_all_checkouts(full_images = full_images, verbose = debuglevel >= 5) + checkouts = library_info_lib.get_all_checkouts(full_images = full_images, verbose = debuglevel >= 5) if "raw" == output: - print(items) + print(checkouts) elif "json" == output: - print(json.dumps(items,default=serialize)) + print(json.dumps(checkouts,default=serialize)) elif "html" == output: - html(items) + html(checkouts) else: print(f"Error! Invalid choice for output format {output}.") -- cgit