-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 553d6e2
Showing
17 changed files
with
711 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
.vscode/ | ||
libsearch.html | ||
cache/ | ||
__pycache__/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2019 Jonas De Pelsmaeker | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
# LibSearch | ||
|
||
LibSearch = Goodreads + bibliotheek.be. Get availabilities of your Goodreads to-read list from Flanders' public libraries. | ||
|
||
## Getting Started | ||
|
||
Download the repo and add your configuration in config.json. As an example is given in config.json. | ||
|
||
* Branch name is the municipality you want to search (enter 'Wetteren' for 'wetteren.bibliotheek.be') | ||
* libraries are the name of the individual libraries in the municipality (this can be empty to search all the libraries in the municipality) | ||
|
||
### Prerequisites | ||
|
||
Add API keys to config.json: | ||
|
||
* [Goodreads developer key & secret](https://www.goodreads.com/api/keys) | ||
* [CultuurConnect API key](https://www.cultuurconnect.be/api) or use test key (b8157a8a17c57162b1c9d8096c5f3620) | ||
|
||
You'll need Python (3.6.5 minimum) and some libraries: | ||
|
||
``` | ||
pip install rauth | ||
pip install aiohttp | ||
``` | ||
|
||
## Running | ||
|
||
Run libsearch.py. | ||
If it's the first time the program is running, it will open the authorisation page for Goodreads. | ||
When you've accepted the authorisation, enter 'y' in the console. | ||
|
||
Your webbrowser will open, displaying the results. | ||
|
||
## License | ||
|
||
This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details | ||
|
||
## Acknowledgments | ||
|
||
* CultuurConnect |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
{ | ||
"Cultuurconnect": { | ||
"authKey": "<key>", | ||
"branches": [ | ||
{ | ||
"name": "Wetteren", | ||
"libraries": [ | ||
"Hoofdbibliotheek", | ||
"Massemen" | ||
] | ||
}, | ||
{ | ||
"name": "Lokeren" | ||
} | ||
] | ||
}, | ||
"Goodreads": { | ||
"devKey": "<key>", | ||
"devSecret": "<secret>" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import asyncio | ||
|
||
from src.Clients.Cultuurconnect import Cultuurconnect | ||
from src.Clients.Goodreads import Goodreads | ||
from src.HTMLOutput import HTMLOutput | ||
from src.Configuration import Configuration | ||
from src.Cache import Cache | ||
from src.Catalogue import Catalogue | ||
|
||
def main(): | ||
|
||
Configuration().load() | ||
books = Goodreads().get_books() | ||
|
||
catalogue = Catalogue() | ||
catalogue.set_books(books) | ||
|
||
cultuurconnect = Cultuurconnect() | ||
loop = asyncio.get_event_loop() | ||
catalogue.books = loop.run_until_complete(cultuurconnect.search_books(catalogue.books)) | ||
|
||
Cache().save_catalogue(catalogue) | ||
|
||
books = loop.run_until_complete(cultuurconnect.get_availibities_of_books(catalogue.books)) | ||
loop.close() | ||
|
||
HTMLOutput().createHTML(catalogue) | ||
HTMLOutput().openHTML() | ||
|
||
|
||
if __name__ == "__main__": | ||
main() | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
class Availability(object): | ||
|
||
is_available = None | ||
branch = None | ||
library = None | ||
status = None | ||
subloc = None | ||
zizo_image_url = None | ||
shelfmark = None | ||
publication = None | ||
return_date = None | ||
|
||
def __init__(self, is_available: bool, branch: str, library: str, status: str, subloc: str, shelfmark: str, publication: str): | ||
self.is_available = is_available | ||
self.branch = branch | ||
self.library = library | ||
self.status = status | ||
self.subloc = subloc | ||
self.shelfmark = shelfmark | ||
self.publication = publication | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
from datetime import date | ||
|
||
class Book(object): | ||
|
||
frabl = None | ||
vlacc = None | ||
isbn = None | ||
pages = None | ||
availabilities = None | ||
detail_page = None | ||
goodreads_page = None | ||
goodreads_cover = None | ||
is_available = False | ||
|
||
def __init__(self, author: str, title: str, goodreads_id: str): | ||
self.author = author | ||
self.title = title | ||
self.goodreads_id = goodreads_id | ||
|
||
self.availabilities = [] | ||
|
||
def add_availablity(self, availability): | ||
self.availabilities.append(availability) | ||
|
||
if availability.is_available: | ||
self.is_available = True | ||
|
||
def get_cover_url(self): | ||
if self.frabl is not None: | ||
return "https://webservices.bibliotheek.be/index.php?func=cover&ISBN={0}&VLACCnr={1}&CDR=&EAN=&ISMN=&coversize=medium".format(self.isbn, self.vlacc) | ||
else: | ||
return self.goodreads_cover | ||
|
||
def to_html(self): | ||
content = """<div class="card"> | ||
<div class="row no-gutters"> | ||
<div class="col-md-1">""" | ||
content += '<img class="card-img" src="{}">'.format(self.get_cover_url()) | ||
|
||
content += """</div> | ||
<div class="col-md-11"> | ||
<div class="card-body">""" | ||
|
||
content += '<h5 class="card-title">{}</h5>'.format(self.title) | ||
content += '<h6 class="card-subtitle mb-2 text-muted">{}</h6>'.format(self.author) | ||
|
||
if self.is_available and len(self.availabilities) > 0: | ||
|
||
content += """<p class="card-text"> | ||
<table class="table"> | ||
<thead> | ||
<tr> | ||
<th scope="col">Branch</th> | ||
<th scope="col">Library</th> | ||
<th scope="col">Sublocation</th> | ||
<th scope="col">Shelfmark</th> | ||
<th scope="col">Publication</th> | ||
</tr> | ||
</thead> | ||
<tbody>""" | ||
|
||
availables = [avail for avail in self.availabilities if avail.is_available] | ||
for avail in availables: | ||
content += "<tr>" | ||
content += "<td>{}</td>".format(avail.branch) | ||
content += "<td>{}</td>".format(avail.library) | ||
content += "<td>" | ||
if avail.zizo_image_url is not None: | ||
content += "<img src='{}' style='width: 30px; margin-right: 5px;'/>".format(avail.zizo_image_url) | ||
content += "{}</td>".format(avail.subloc) | ||
content += "<td>{}</td>".format(avail.shelfmark) | ||
content += "<td>{}</td>".format(avail.publication) | ||
content += "<tr>" | ||
|
||
content += """</tbody></table></p>""" | ||
|
||
content += '<p class="card-text"><small class="text-muted">{} - {}</small></p>'.format(self.isbn, self.pages) | ||
content += '<a href="{}" class="card-link" target="_blank">Catalogus</a>'.format(self.detail_page) | ||
content += '<a href="{}" class="card-link" target="_blank">Goodreads</a>'.format(self.goodreads_page) | ||
|
||
content += """</div> | ||
</div> | ||
</div> | ||
</div>""" | ||
|
||
elif not self.is_available and len(self.availabilities) > 0: | ||
|
||
content += """<p class="card-text"> | ||
<table class="table"> | ||
<thead> | ||
<tr> | ||
<th scope="col">Branch</th> | ||
<th scope="col">Library</th> | ||
<th scope="col">Status</th> | ||
<th scope="col">Return Date</th> | ||
<th scope="col">Days until available</th> | ||
</tr> | ||
</thead> | ||
<tbody>""" | ||
|
||
unavailables = [avail for avail in self.availabilities if avail.is_available == False] | ||
for unavail in unavailables: | ||
content += "<tr>" | ||
content += "<td>{}</td>".format(unavail.branch) | ||
content += "<td>{}</td>".format(unavail.library) | ||
content += "<td>{}</td>".format(unavail.status) | ||
content += "<td>{}</td>".format(unavail.return_date) | ||
if unavail.return_date is not None: | ||
content += "<td>{}</td>".format((unavail.return_date - date.today()).days) | ||
content += "<tr>" | ||
|
||
content += """</tbody></table></p>""" | ||
|
||
content += '<p class="card-text"><small class="text-muted">{} - {}</small></p>'.format(self.isbn, self.pages) | ||
content += '<a href="{}" class="card-link" target="_blank">Catalogus</a>'.format(self.detail_page) | ||
content += '<a href="{}" class="card-link" target="_blank">Goodreads</a>'.format(self.goodreads_page) | ||
|
||
content += """</div> | ||
</div> | ||
</div> | ||
</div>""" | ||
|
||
elif self.frabl is not None: | ||
content += "<p class='card-text'>No availabilities found.</p>" | ||
|
||
content += '<p class="card-text"><small class="text-muted">{} - {}</small></p>'.format(self.isbn, self.pages) | ||
content += '<a href="{}" class="card-link" target="_blank">Catalogus</a>'.format(self.detail_page) | ||
content += '<a href="{}" class="card-link" target="_blank">Goodreads</a>'.format(self.goodreads_page) | ||
|
||
content += """</div> | ||
</div> | ||
</div> | ||
</div>""" | ||
else: | ||
content += "<p class='card-text'>Not found in library catalogue.</p>" | ||
content += '<a href="{}" class="card-link" target="_blank">Goodreads</a>'.format(self.goodreads_page) | ||
|
||
content += """</div> | ||
</div> | ||
</div> | ||
</div>""" | ||
|
||
return content |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import json | ||
import os | ||
from src.utils.Singleton import Singleton | ||
from src.Book import Book | ||
from src.Catalogue import Catalogue | ||
|
||
class Cache(Singleton, object): | ||
|
||
catalogue = None | ||
tokens = None | ||
|
||
cache_dir = os.path.join(os.path.dirname(__file__), '..\\cache') | ||
tokens_cache_path = os.path.join(os.path.dirname(__file__), '..\\cache\\.tokens') | ||
catalogue_cache_path = os.path.join(os.path.dirname(__file__), '..\\cache\\.catalogue.json') | ||
|
||
def __init__(self): | ||
if not os.path.exists(self.cache_dir): | ||
os.mkdir(self.cache_dir) | ||
|
||
def load_catalogue(self): | ||
if os.path.exists(self.catalogue_cache_path): | ||
with open(self.catalogue_cache_path) as catalogue_cache: | ||
self.catalogue = json.load(catalogue_cache) | ||
|
||
def save_catalogue(self, catalogue: Catalogue): | ||
with open(self.catalogue_cache_path, 'w') as catalogue_cache: | ||
dicts = [book.__dict__ for book in catalogue.books if book.frabl is not None] | ||
catalogue_cache.write(json.dumps(dicts)) | ||
|
||
self.catalogue = dicts | ||
|
||
def get_book(self, goodreads_id: str): | ||
if self.catalogue is None: | ||
return None | ||
|
||
found_book = next((book for book in self.catalogue if book['goodreads_id'] == goodreads_id), None) | ||
if found_book is None: | ||
return None | ||
|
||
book = Book(found_book['author'], found_book['title'], found_book['goodreads_id']) | ||
book.frabl = found_book['frabl'] | ||
book.vlacc = found_book['vlacc'] | ||
book.isbn = found_book['isbn'] | ||
book.pages = found_book['pages'] | ||
book.detail_page = found_book['detail_page'] | ||
book.goodreads_page = found_book['goodreads_page'] | ||
book.goodreads_cover = found_book['goodreads_cover'] | ||
|
||
print('{} by {} found in cache'.format(book.title, book.author)) | ||
|
||
return book | ||
|
||
def load_tokens(self): | ||
if os.path.exists(self.tokens_cache_path): | ||
with open(self.tokens_cache_path) as tokens_cache: | ||
self.tokens = json.load(tokens_cache) | ||
|
||
return self.tokens | ||
|
||
return None | ||
|
||
def save_access_tokens(self, access_token: str, access_token_secret: str): | ||
with open(self.tokens_cache_path, 'w') as token_file: | ||
token_file.write(json.dumps({ | ||
'token': access_token, | ||
'secret': access_token_secret | ||
})) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
from src.utils.Singleton import Singleton | ||
|
||
class Catalogue(Singleton, object): | ||
|
||
books = None | ||
|
||
def set_books(self, books: list): | ||
self.books = books | ||
|
||
def get_available_books(self): | ||
return [book for book in self.books if book.is_available] | ||
|
||
def get_unavailable_books(self): | ||
return [book for book in self.books if not book.is_available and len(book.availabilities) > 0] | ||
|
||
def get_books_with_no_availables(self): | ||
return [book for book in self.books if not book.is_available and len(book.availabilities) == 0 and book.frabl is not None] | ||
|
||
def get_not_found_books(self): | ||
return [book for book in self.books if book.frabl is None] |
Oops, something went wrong.