Renamed feed to opds for better compatibility with some readers

added clickable links to feed (should hopefully solve #79)
updated readme
pull/90/head
OzzieIsaacs 8 years ago
parent e744ccc20e
commit 157a2e6e4a

@ -61,23 +61,25 @@
{% for author in authors %} {% for author in authors %}
<entry> <entry>
<title>{{author.name}}</title> <title>{{author.name}}</title>
<!--content type="text">{{author.name}}</content-->
<id>{{ url_for('feed_author', name=author.name) }}</id> <id>{{ url_for('feed_author', name=author.name) }}</id>
<link href="{{ url_for('feed_author', name=author.name)}}" type="application/atom+xml;profile=opds-catalog;kind=navigation" rel="subsection"/> <link type="application/atom+xml;profile=opds-catalog;type=feed;kind=acquisition" href="{{url_for('feed_author', name=author.name)}}" />
<link type="application/atom+xml" href="{{url_for('feed_author', name=author.name)}}" rel="subsection"/>
</entry> </entry>
{% endfor %} {% endfor %}
{% for entry in categorys %} {% for entry in categorys %}
<entry> <entry>
<title>{{entry.name}}</title> <title>{{entry.name}}</title>
<id>{{ url_for('feed_category', name=entry.name) }}</id> <id>{{ url_for('feed_category', name=entry.name) }}</id>
<link href="{{ url_for('feed_category', name=entry.name)}}" type="application/atom+xml;profile=opds-catalog;kind=navigation" rel="subsection"/> <link type="application/atom+xml;profile=opds-catalog;type=feed;kind=acquisition" href="{{url_for('feed_category', name=entry.name)}}" />
<link type="application/atom+xml" href="{{url_for('feed_category', name=entry.name)}}" rel="subsection"/>
</entry> </entry>
{% endfor %} {% endfor %}
{% for entry in series %} {% for entry in series %}
<entry> <entry>
<title>{{entry.name}}</title> <title>{{entry.name}}</title>
<id>{{ url_for('feed_series', name=entry.name) }}</id> <id>{{ url_for('feed_series', name=entry.name) }}</id>
<link href="{{ url_for('feed_series', name=entry.name)}}" type="application/atom+xml;profile=opds-catalog;kind=navigation" rel="subsection"/> <link type="application/atom+xml;profile=opds-catalog;type=feed;kind=acquisition" href="{{url_for('feed_series', name=entry.name)}}" />
</entry> <link type="application/atom+xml" href="{{url_for('feed_series', name=entry.name)}}" rel="subsection"/>
</entry>
{% endfor %} {% endfor %}
</feed> </feed>

@ -21,51 +21,55 @@
<entry> <entry>
<title>{{_('Hot Books')}}</title> <title>{{_('Hot Books')}}</title>
<link rel="http://opds-spec.org/sort/popular" <link type="application/atom+xml;profile=opds-catalog;type=feed;kind=acquisition" href="{{url_for('feed_hot')}}" />
href="{{url_for('feed_hot')}}" <link type="application/atom+xml" href="{{url_for('feed_hot')}}" rel="http://opds-spec.org/sort/popular"/>
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
<id>{{url_for('feed_hot')}}</id> <id>{{url_for('feed_hot')}}</id>
<content type="text">{{_('Popular publications from this catalog based on Rating.')}}</content> <content type="text">{{_('Popular publications from this catalog based on Rating.')}}</content>
</entry> </entry>
<entry> <entry>
<title>{{_('New Books')}}</title> <title>{{_('New Books')}}</title>
<link type="application/atom+xml;profile=opds-catalog;type=feed;kind=acquisition" href="{{url_for('feed_new')}}" />
<link rel="http://opds-spec.org/sort/new" <link rel="http://opds-spec.org/sort/new"
href="{{url_for('feed_new')}}" href="{{url_for('feed_new')}}"
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/> type="application/atom+xml;profile=opds-catalog;type=feed;kind=acquisition"/>
<id>{{url_for('feed_new')}}</id> <id>{{url_for('feed_new')}}</id>
<content type="text">{{_('The latest Books')}}</content> <content type="text">{{_('The latest Books')}}</content>
</entry> </entry>
<entry> <entry>
<title>{{_('Random Books')}}</title> <title>{{_('Random Books')}}</title>
<link type="application/atom+xml;profile=opds-catalog;type=feed;kind=acquisition" href="{{url_for('feed_discover')}}" />
<link rel="http://opds-spec.org/featured" <link rel="http://opds-spec.org/featured"
href="{{url_for('feed_discover')}}" href="{{url_for('feed_discover')}}"
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/> type="application/atom+xml;profile=opds-catalog;type=feed;kind=acquisition"/>
<id>{{url_for('feed_discover')}}</id> <id>{{url_for('feed_discover')}}</id>
<content type="text">{{_('Show Random Books')}}</content> <content type="text">{{_('Show Random Books')}}</content>
</entry> </entry>
<entry> <entry>
<title>{{_('Authors')}}</title> <title>{{_('Authors')}}</title>
<link type="application/atom+xml;profile=opds-catalog;type=feed;kind=acquisition" href="{{url_for('feed_authorindex')}}" />
<link rel="subsection" <link rel="subsection"
href="{{url_for('feed_authorindex')}}" href="{{url_for('feed_authorindex')}}"
type="application/atom+xml;profile=opds-catalog;kind=navigation"/> type="application/atom+xml;profile=opds-catalog;type=feed;kind=navigation"/>
<id>{{url_for('feed_authorindex')}}</id> <id>{{url_for('feed_authorindex')}}</id>
<content type="text">{{_('Books ordered by Author')}}</content> <content type="text">{{_('Books ordered by Author')}}</content>
</entry> </entry>
<entry> <entry>
<title>{{_('Category list')}}</title> <title>{{_('Category list')}}</title>
<link type="application/atom+xml;profile=opds-catalog;type=feed;kind=acquisition" href="{{url_for('feed_categoryindex')}}" />
<link rel="subsection" <link rel="subsection"
href="{{url_for('feed_categoryindex')}}" href="{{url_for('feed_categoryindex')}}"
type="application/atom+xml;profile=opds-catalog;kind=navigation"/> type="application/atom+xml;profile=opds-catalog;type=feed;kind=navigation"/>
<id>{{url_for('feed_categoryindex')}}</id> <id>{{url_for('feed_categoryindex')}}</id>
<content type="text">{{_('Books ordered by category')}}</content> <content type="text">{{_('Books ordered by category')}}</content>
</entry> </entry>
<entry> <entry>
<title>{{_('Series list')}}</title> <title>{{_('Series list')}}</title>
<link type="application/atom+xml;profile=opds-catalog;type=feed;kind=acquisition" href="{{url_for('feed_seriesindex')}}" />
<link rel="subsection" <link rel="subsection"
href="{{url_for('feed_seriesindex')}}" href="{{url_for('feed_seriesindex')}}"
type="application/atom+xml;profile=opds-catalog;kind=navigation"/> type="application/atom+xml;profile=opds-catalog;type=feed;kind=navigation"/>
<id>{{url_for('feed_seriesindex')}}</id> <id>{{url_for('feed_seriesindex')}}</id>
<content type="text">{{_('Books ordered by series')}}</content> <content type="text">{{_('Books ordered by series')}}</content>
</entry> </entry>

@ -13,21 +13,6 @@
<Url type="application/atom+xml" <Url type="application/atom+xml"
template="{{url_for('feed_search')}}?query={searchTerms}"/> template="{{url_for('feed_search')}}?query={searchTerms}"/>
<Url type="application/x-suggestions+json"
rel="suggestions"
template="http://www.gutenberg.org/ebooks/suggest/?query={searchTerms}"/>
<!--
<Url type="application/rss+xml"
template="http://example.com/?q={searchTerms}&amp;pw={startPage?}&amp;format=rss"/>
<Image height="64" width="64" type="image/png">http://example.com/websearch.png</Image>
<Image height="16" width="16" type="image/vnd.microsoft.icon">http://example.com/websearch.ico</Image>
-->
<!--Query role="example" searchTerms="shakespeare hamlet" />
<Query role="example" searchTerms="doyle detective" />
<Query role="example" searchTerms="love stories" /-->
<Attribution>Search Data Copyright 1971-2012, Project Gutenberg, All Rights Reserved.</Attribution>
<SyndicationRight>open</SyndicationRight> <SyndicationRight>open</SyndicationRight>
<Language>de-DE</Language> <Language>de-DE</Language>
<OutputEncoding>UTF-8</OutputEncoding> <OutputEncoding>UTF-8</OutputEncoding>

@ -13,17 +13,6 @@
<Url type="application/atom+xml" <Url type="application/atom+xml"
template="{{url_for('feed_search')}}?query={searchTerms}"/> template="{{url_for('feed_search')}}?query={searchTerms}"/>
<!--
<Url type="application/rss+xml"
template="http://example.com/?q={searchTerms}&amp;pw={startPage?}&amp;format=rss"/>
<Image height="64" width="64" type="image/png">http://example.com/websearch.png</Image>
<Image height="16" width="16" type="image/vnd.microsoft.icon">http://example.com/websearch.ico</Image>
-->
<Query role="example" searchTerms="shakespeare hamlet" />
<Query role="example" searchTerms="doyle detective" />
<Query role="example" searchTerms="love stories" />
<SyndicationRight>open</SyndicationRight> <SyndicationRight>open</SyndicationRight>
<Language>de-DE</Language> <Language>de-DE</Language>
<OutputEncoding>UTF-8</OutputEncoding> <OutputEncoding>UTF-8</OutputEncoding>

@ -416,7 +416,7 @@ def before_request():
g.allow_upload = config.UPLOADING g.allow_upload = config.UPLOADING
@app.route("/feed") @app.route("/opds")
@requires_basic_auth_if_no_ano @requires_basic_auth_if_no_ano
def feed_index(): def feed_index():
if current_user.filter_language() != "all": if current_user.filter_language() != "all":
@ -429,7 +429,7 @@ def feed_index():
return response return response
@app.route("/feed/osd") @app.route("/opds/osd")
@requires_basic_auth_if_no_ano @requires_basic_auth_if_no_ano
def feed_osd(): def feed_osd():
xml = render_template('osd.xml') xml = render_template('osd.xml')
@ -438,7 +438,7 @@ def feed_osd():
return response return response
@app.route("/feed/search", methods=["GET"]) @app.route("/opds/search", methods=["GET"])
@requires_basic_auth_if_no_ano @requires_basic_auth_if_no_ano
def feed_search(): def feed_search():
term = request.args.get("query").strip() term = request.args.get("query").strip()
@ -459,7 +459,7 @@ def feed_search():
return response return response
@app.route("/feed/new") @app.route("/opds/new")
@requires_basic_auth_if_no_ano @requires_basic_auth_if_no_ano
def feed_new(): def feed_new():
off = request.args.get("start_index") off = request.args.get("start_index")
@ -472,13 +472,13 @@ def feed_new():
entries = db.session.query(db.Books).filter(filter).order_by(db.Books.timestamp.desc()).offset(off).limit( entries = db.session.query(db.Books).filter(filter).order_by(db.Books.timestamp.desc()).offset(off).limit(
config.NEWEST_BOOKS) config.NEWEST_BOOKS)
xml = render_template('feed.xml', entries=entries, xml = render_template('feed.xml', entries=entries,
next_url="/feed/new?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off))) next_url="/opds/new?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off)))
response = make_response(xml) response = make_response(xml)
response.headers["Content-Type"] = "application/xml" response.headers["Content-Type"] = "application/xml"
return response return response
@app.route("/feed/discover") @app.route("/opds/discover")
@requires_basic_auth_if_no_ano @requires_basic_auth_if_no_ano
def feed_discover(): def feed_discover():
off = request.args.get("start_index") off = request.args.get("start_index")
@ -490,13 +490,13 @@ def feed_discover():
off = 0 off = 0
entries = db.session.query(db.Books).filter(filter).order_by(func.random()).offset(off).limit(config.NEWEST_BOOKS) entries = db.session.query(db.Books).filter(filter).order_by(func.random()).offset(off).limit(config.NEWEST_BOOKS)
xml = render_template('feed.xml', entries=entries, xml = render_template('feed.xml', entries=entries,
next_url="/feed/discover?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off))) next_url="/opds/discover?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off)))
response = make_response(xml) response = make_response(xml)
response.headers["Content-Type"] = "application/xml" response.headers["Content-Type"] = "application/xml"
return response return response
@app.route("/feed/hot") @app.route("/opds/hot")
@requires_basic_auth_if_no_ano @requires_basic_auth_if_no_ano
def feed_hot(): def feed_hot():
off = request.args.get("start_index") off = request.args.get("start_index")
@ -510,13 +510,13 @@ def feed_hot():
off).limit(config.NEWEST_BOOKS) off).limit(config.NEWEST_BOOKS)
xml = render_template('feed.xml', entries=entries, xml = render_template('feed.xml', entries=entries,
next_url="/feed/hot?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off))) next_url="/opds/hot?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off)))
response = make_response(xml) response = make_response(xml)
response.headers["Content-Type"] = "application/xml" response.headers["Content-Type"] = "application/xml"
return response return response
@app.route("/feed/author") @app.route("/opds/author")
@requires_basic_auth_if_no_ano @requires_basic_auth_if_no_ano
def feed_authorindex(): def feed_authorindex():
off = request.args.get("start_index") off = request.args.get("start_index")
@ -528,13 +528,13 @@ def feed_authorindex():
off = 0 off = 0
authors = db.session.query(db.Authors).order_by(db.Authors.sort).offset(off).limit(config.NEWEST_BOOKS) authors = db.session.query(db.Authors).order_by(db.Authors.sort).offset(off).limit(config.NEWEST_BOOKS)
xml = render_template('feed.xml', authors=authors, xml = render_template('feed.xml', authors=authors,
next_url="/feed/author?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off))) next_url="/opds/author?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off)))
response = make_response(xml) response = make_response(xml)
response.headers["Content-Type"] = "application/xml" response.headers["Content-Type"] = "application/xml"
return response return response
@app.route("/feed/author/<name>") @app.route("/opds/author/<name>")
@requires_basic_auth_if_no_ano @requires_basic_auth_if_no_ano
def feed_author(name): def feed_author(name):
off = request.args.get("start_index") off = request.args.get("start_index")
@ -547,13 +547,13 @@ def feed_author(name):
entries = db.session.query(db.Books).filter(db.Books.authors.any(db.Authors.name.like("%" + name + "%"))).filter( entries = db.session.query(db.Books).filter(db.Books.authors.any(db.Authors.name.like("%" + name + "%"))).filter(
filter).offset(off).limit(config.NEWEST_BOOKS) filter).offset(off).limit(config.NEWEST_BOOKS)
xml = render_template('feed.xml', entries=entries, xml = render_template('feed.xml', entries=entries,
next_url="/feed/author?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off))) next_url="/opds/author?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off)))
response = make_response(xml) response = make_response(xml)
response.headers["Content-Type"] = "application/xml" response.headers["Content-Type"] = "application/xml"
return response return response
@app.route("/feed/category") @app.route("/opds/category")
@requires_basic_auth_if_no_ano @requires_basic_auth_if_no_ano
def feed_categoryindex(): def feed_categoryindex():
off = request.args.get("start_index") off = request.args.get("start_index")
@ -561,13 +561,13 @@ def feed_categoryindex():
off = 0 off = 0
entries = db.session.query(db.Tags).order_by(db.Tags.name).offset(off).limit(config.NEWEST_BOOKS) entries = db.session.query(db.Tags).order_by(db.Tags.name).offset(off).limit(config.NEWEST_BOOKS)
xml = render_template('feed.xml', categorys=entries, xml = render_template('feed.xml', categorys=entries,
next_url="/feed/category?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off))) next_url="/opds/category?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off)))
response = make_response(xml) response = make_response(xml)
response.headers["Content-Type"] = "application/xml" response.headers["Content-Type"] = "application/xml"
return response return response
@app.route("/feed/category/<name>") @app.route("/opds/category/<name>")
@requires_basic_auth_if_no_ano @requires_basic_auth_if_no_ano
def feed_category(name): def feed_category(name):
off = request.args.get("start_index") off = request.args.get("start_index")
@ -580,13 +580,13 @@ def feed_category(name):
entries = db.session.query(db.Books).filter(db.Books.tags.any(db.Tags.name.like("%" + name + "%"))).order_by( entries = db.session.query(db.Books).filter(db.Books.tags.any(db.Tags.name.like("%" + name + "%"))).order_by(
db.Books.timestamp.desc()).filter(filter).offset(off).limit(config.NEWEST_BOOKS) db.Books.timestamp.desc()).filter(filter).offset(off).limit(config.NEWEST_BOOKS)
xml = render_template('feed.xml', entries=entries, xml = render_template('feed.xml', entries=entries,
next_url="/feed/category?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off))) next_url="/opds/category?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off)))
response = make_response(xml) response = make_response(xml)
response.headers["Content-Type"] = "application/xml" response.headers["Content-Type"] = "application/xml"
return response return response
@app.route("/feed/series") @app.route("/opds/series")
@requires_basic_auth_if_no_ano @requires_basic_auth_if_no_ano
def feed_seriesindex(): def feed_seriesindex():
off = request.args.get("start_index") off = request.args.get("start_index")
@ -594,13 +594,13 @@ def feed_seriesindex():
off = 0 off = 0
entries = db.session.query(db.Series).order_by(db.Series.name).offset(off).limit(config.NEWEST_BOOKS) entries = db.session.query(db.Series).order_by(db.Series.name).offset(off).limit(config.NEWEST_BOOKS)
xml = render_template('feed.xml', series=entries, xml = render_template('feed.xml', series=entries,
next_url="/feed/series?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off))) next_url="/opds/series?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off)))
response = make_response(xml) response = make_response(xml)
response.headers["Content-Type"] = "application/xml" response.headers["Content-Type"] = "application/xml"
return response return response
@app.route("/feed/series/<name>") @app.route("/opds/series/<name>")
@requires_basic_auth_if_no_ano @requires_basic_auth_if_no_ano
def feed_series(name): def feed_series(name):
off = request.args.get("start_index") off = request.args.get("start_index")
@ -613,13 +613,13 @@ def feed_series(name):
entries = db.session.query(db.Books).filter(db.Books.series.any(db.Series.name.like("%" + name + "%"))).order_by( entries = db.session.query(db.Books).filter(db.Books.series.any(db.Series.name.like("%" + name + "%"))).order_by(
db.Books.timestamp.desc()).filter(filter).offset(off).limit(config.NEWEST_BOOKS) db.Books.timestamp.desc()).filter(filter).offset(off).limit(config.NEWEST_BOOKS)
xml = render_template('feed.xml', entries=entries, xml = render_template('feed.xml', entries=entries,
next_url="/feed/series?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off))) next_url="/opds/series?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off)))
response = make_response(xml) response = make_response(xml)
response.headers["Content-Type"] = "application/xml" response.headers["Content-Type"] = "application/xml"
return response return response
@app.route("/feed/download/<int:book_id>/<format>") @app.route("/opds/download/<int:book_id>/<format>")
@requires_basic_auth_if_no_ano @requires_basic_auth_if_no_ano
@download_required @download_required
def get_opds_download_link(book_id, format): def get_opds_download_link(book_id, format):
@ -1036,7 +1036,7 @@ def get_cover(cover_path):
return send_from_directory(os.path.join(config.DB_ROOT, cover_path), "cover.jpg") return send_from_directory(os.path.join(config.DB_ROOT, cover_path), "cover.jpg")
@app.route("/feed/cover/<path:cover_path>") @app.route("/opds/cover/<path:cover_path>")
@requires_basic_auth_if_no_ano @requires_basic_auth_if_no_ano
def feed_get_cover(cover_path): def feed_get_cover(cover_path):
return send_from_directory(os.path.join(config.DB_ROOT, cover_path), "cover.jpg") return send_from_directory(os.path.join(config.DB_ROOT, cover_path), "cover.jpg")

@ -10,7 +10,8 @@ Calibre Web is a web app providing a clean interface for browsing, reading and d
- Bootstrap 3 HTML5 interface - Bootstrap 3 HTML5 interface
- User management - User management
- Admin interface - Admin interface
- OPDS feed for eBook reader apps - User Interface in english, german and french
- OPDS feed for eBook reader apps
- Filter and search by titles, authors, tags, series and language - Filter and search by titles, authors, tags, series and language
- Create custom book collection (shelves) - Create custom book collection (shelves)
- Support for editing eBook metadata - Support for editing eBook metadata
@ -18,7 +19,7 @@ Calibre Web is a web app providing a clean interface for browsing, reading and d
- Restrict eBook download to logged-in users - Restrict eBook download to logged-in users
- Support for public user registration - Support for public user registration
- Send eBooks to Kindle devices with the click of a button - Send eBooks to Kindle devices with the click of a button
- Support for reading eBooks directly in the browser - Support for reading eBooks directly in the browser (.txt, .epub, .pdf)
- Upload new books in PDF format - Upload new books in PDF format
- Support for Calibre custom columns - Support for Calibre custom columns
- Fine grained per-user permissions - Fine grained per-user permissions
@ -27,7 +28,7 @@ Calibre Web is a web app providing a clean interface for browsing, reading and d
1. Rename `config.ini.example` to `config.ini` and set `DB_ROOT` to the path of the folder where your Calibre library (metadata.db) lives 1. Rename `config.ini.example` to `config.ini` and set `DB_ROOT` to the path of the folder where your Calibre library (metadata.db) lives
2. Execute the command: `python cps.py` 2. Execute the command: `python cps.py`
3. Point your browser to `http://localhost:8083` or `http://localhost:8083/feed` for the OPDS catalog 3. Point your browser to `http://localhost:8083` or `http://localhost:8083/opds` for the OPDS catalog
**Default admin login:** **Default admin login:**
*Username:* admin *Username:* admin

Loading…
Cancel
Save