diff --git a/README.md b/README.md
index bee7db2c..efd6aede 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@ PHP 7+ is required.
```shell
# Install Apache, PHP, PHP-FPM, and various other dependencies.
-sudo apt install -y git composer php-fpm php-cli php-gd php-xml php-apcu php-mbstring php-intl php-curl php-zip apache2 apache2-utils libfcgi0ldbl task-spooler ipv6calc mariadb-server
+sudo apt install -y git composer php-fpm php-cli php-gd php-xml php-apcu php-mbstring php-intl php-curl php-zip apache2 apache2-utils libfcgi0ldbl task-spooler ipv6calc mariadb-server libaprutil1-dbd-mysql
# Create the site root and logs root and clone this repo into it.
sudo mkdir /standardebooks.org/
@@ -26,7 +26,7 @@ echo -e "127.0.0.1\tstandardebooks.test" | sudo tee -a /etc/hosts
openssl req -x509 -nodes -days 99999 -newkey rsa:4096 -subj "/CN=standardebooks.test" -keyout /standardebooks.org/web/config/ssl/standardebooks.test.key -sha256 -out /standardebooks.org/web/config/ssl/standardebooks.test.crt
# Enable the necessary Apache modules.
-sudo a2enmod headers expires ssl rewrite proxy proxy_fcgi
+sudo a2enmod headers expires ssl rewrite proxy proxy_fcgi authn_dbd
# Link and enable the SE Apache configuration file.
sudo ln -s /standardebooks.org/web/config/apache/standardebooks.test.conf /etc/apache2/sites-available/
diff --git a/config/apache/standardebooks.org.conf b/config/apache/standardebooks.org.conf
index 847049af..c3c51195 100644
--- a/config/apache/standardebooks.org.conf
+++ b/config/apache/standardebooks.org.conf
@@ -50,6 +50,23 @@ SSLStaplingReturnResponderErrors off
Define domain standardebooks.org
Define webroot /standardebooks.org/web
+
+ ServerName standardebooks.com
+ ServerAlias www.standardebooks.com
+ RedirectPermanent / https://${domain}/
+
+
+
+ ServerName standardebooks.com
+ ServerAlias www.standardebooks.com
+ RedirectPermanent / https://${domain}/
+
+ SSLEngine on
+ SSLCertificateFile /etc/letsencrypt/live/standardebooks.com/fullchain.pem
+ SSLCertificateKeyFile /etc/letsencrypt/live/standardebooks.com/privkey.pem
+ Header always set Strict-Transport-Security "max-age=15768000"
+
+
ServerName ${domain}
ServerAlias www.${domain}
@@ -72,7 +89,7 @@ Define webroot /standardebooks.org/web
Header always set Strict-Transport-Security "max-age=15768000"
Header set Content-Security-Policy "default-src 'self';"
-
+
# Disable .htaccess files
AllowOverride none
@@ -88,43 +105,31 @@ Define webroot /standardebooks.org/web
AddType application/x-mobi8-ebook .azw3
-
+
# Serve distributables using the "download" dialog instead of opening in-browser
# Note: the trailing e in the Header directive is required
# In modern browsers this is handled with the a@download attribute, we keep this here for compatibility
SetEnvIf Request_URI ^/ebooks/.+?/downloads/(.+)$ FILENAME=$1
Header set Content-Disposition "attachment; filename=%{FILENAME}e"
-
+
# We explicitly set the content-type for items in the /vocab/ directory, because Apache doesn't set it for us,
# and we need a content-type header when using the "nosniff" header. See https://bugzilla.mozilla.org/show_bug.cgi?id=1547076
-
+
Header set Content-Type "text/plain"
-
-
-
- DirectoryIndex index.xml
-
-
- # text/xml allows the page to be displayed in a browser and the encoding to be
- # determined from the document and not the HTTP headers. application/rss+xml will cause it to be downloaded.
-
-
- Header set Content-Type "text/xml; charset=utf-8"
-
-
+
# Enable HTTP CORS so that browser-based readers like Readium can access opds and ebooks
# Allow fonts for newsletter emails
# See https://github.com/standardebooks/tools/issues/2
-
+
Header set Access-Control-Allow-Origin "*"
-
+
# We use a different CSP policy for single-page files because our default one doesn't allow inline images or CSS
-
+
Header set Content-Security-Policy "default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline';"
-
+
# Remove www from requests
RewriteCond %{HTTP_HOST} ^www\.(.+) [NC]
@@ -137,13 +142,13 @@ Define webroot /standardebooks.org/web
SetEnv proxy-sendcl 1
# Forward all PHP requests to the php-fpm pool for this domain.
-
+
SetHandler "proxy:unix:/run/php/${domain}.sock|fcgi://${domain}"
Header set Cache-Control "no-store"
# Set some proxy properties.
-
+
ProxySet connectiontimeout=5 timeout=240
@@ -190,11 +195,11 @@ Define webroot /standardebooks.org/web
# Redirect index pages
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} -d
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME}/index.php -f
- RewriteRule (.*) /$1/index.php
+ RewriteRule (.*) $1/index.php
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} -d
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME}/index.xml -f
- RewriteRule (.*) /$1/index.xml
+ RewriteRule (.*) $1/index.xml
# Remove newline characters inserted by accident in some email clients
RewriteRule ^(.*)\r\n[\ ]?(.*)$ $1$2 [R=301,N]
@@ -248,13 +253,6 @@ Define webroot /standardebooks.org/web
RewriteCond %{REQUEST_FILENAME} !^/ebooks/.+?/text.*$
RewriteRule ^/ebooks/([^\.]+?)$ /ebooks/ebook.php?url-path=$1
- # If we ask for /opds/all?query=xyz, rewrite that to the search page.
- RewriteCond %{QUERY_STRING} ^query=
- RewriteRule ^/feeds/(opds|atom|rss)/all.xml$ /feeds/$1/search.php [QSA]
-
- # Rewrite old links to feeds
- RewriteRule ^/(opds|rss|atom)(.*)$ /feeds/$1$2 [R=301,L]
-
# Newsletter
RewriteRule ^/newsletter$ /newsletter/subscriptions/new.php [L]
RewriteRule ^/newsletter/subscriptions/([^/\.]+?)$ /newsletter/subscriptions/get.php?uuid=$1 [L]
@@ -270,21 +268,65 @@ Define webroot /standardebooks.org/web
RewriteCond expr "tolower(%{REQUEST_METHOD}) =~ /^post$/"
RewriteRule ^/patrons-circle/polls/([^/\.]+)/votes$ /patrons-circle/polls/votes/post.php?pollurlname=$1 [L]
-
-
- ServerName standardebooks.com
- ServerAlias www.standardebooks.com
- RedirectPermanent / https://${domain}/
-
+ # Feeds
+ # Rewrite old links to feeds
+ RewriteRule ^/(opds|rss|atom)(.*)$ /feeds/$1$2 [R=301,L]
-
- ServerName standardebooks.com
- ServerAlias www.standardebooks.com
- RedirectPermanent / https://${domain}/
+ # If we ask for /opds/all?query=xyz, rewrite that to the search page.
+ RewriteCond %{QUERY_STRING} \bquery=
+ RewriteRule ^/feeds/(opds|atom|rss)/all.xml$ /feeds/$1/search.php [QSA]
- SSLEngine on
- SSLCertificateFile /etc/letsencrypt/live/standardebooks.com/fullchain.pem
- SSLCertificateKeyFile /etc/letsencrypt/live/standardebooks.com/privkey.pem
- Header always set Strict-Transport-Security "max-age=15768000"
+ # Enable mod_authn_dbd
+ DBDriver mysql
+ DBDParams "dbname=se user=www-data"
+
+ # Enable HTTP Basic auth for feeds
+ AuthType Basic
+ AuthName "Enter your Patrons Circle email address or your API key, and a blank password."
+ Require valid-user
+ ErrorDocument 401 /feeds/401
+
+ # Credentials caching to prevent slamming the DB. socache must be ahead of dbd
+ AuthBasicProvider socache dbd
+ AuthnCacheProvideFor dbd
+ AuthnCacheContext ${domain}
+
+ # mod_authn_dbd SQL query to authenticate a user
+ # The hash is simply the hash of a blank password. We're only interested in the username/API key.
+ # We have to do this tortured query instead of a cleaner one, because the AuthDBDUserPWQuery
+ # function will only replace %s EXACTLY ONCE. We cannot have more than one %s in the query string.
+ AuthDBDUserPWQuery "\
+ select '$apr1$13q1pnGf$vQnIj94BXP1EPdL/4ISba.' from \
+ ( \
+ select Email, Uuid from Patrons p inner join Users u using (UserId) where p.Ended is null \
+ union \
+ select Email, Uuid from FeedUsers fu inner join Users u using (UserId) where fu.Ended is null \
+ ) x where %s in (Email, Uuid) limit 1 \
+ "
+
+
+ # Disable HTTP Basic auth for the feed XSL stylesheet and the new releases feeds
+ Require all granted
+
+
+
+ # Emit content-types for OPDS feeds, as some clients require a strictly correct content-type in order to work
+
+ Header set Content-Type "application/atom+xml;profile=opds-catalog;kind=acquisition; charset=utf-8"
+
+
+ Header set Content-Type "application/atom+xml;profile=opds-catalog;kind=navigation; charset=utf-8"
+
+
+
+ # Emit content-types for RSS/Atom feeds
+ rss|atom)/.+">
+ # Note the trailing e is required to complete the environmental variable reference
+ Header set Content-Type "application/%{MATCH_FEEDTYPE}e+xml; charset=utf-8"
+
+
+ Header set Content-Type "application/xhtml+xml; charset=utf-8"
+
+
diff --git a/config/apache/standardebooks.test.conf b/config/apache/standardebooks.test.conf
index 2bcaf34f..1cb7e7af 100644
--- a/config/apache/standardebooks.test.conf
+++ b/config/apache/standardebooks.test.conf
@@ -71,7 +71,7 @@ Define webroot /standardebooks.org/web
Header always set Strict-Transport-Security "max-age=15768000"
Header set Content-Security-Policy "default-src 'self';"
-
+
# Disable .htaccess files
AllowOverride none
@@ -87,43 +87,31 @@ Define webroot /standardebooks.org/web
AddType application/x-mobi8-ebook .azw3
-
+
# Serve distributables using the "download" dialog instead of opening in-browser
# Note: the trailing e in the Header directive is required
# In modern browsers this is handled with the a@download attribute, we keep this here for compatibility
SetEnvIf Request_URI ^/ebooks/.+?/downloads/(.+)$ FILENAME=$1
Header set Content-Disposition "attachment; filename=%{FILENAME}e"
-
+
# We explicitly set the content-type for items in the /vocab/ directory, because Apache doesn't set it for us,
# and we need a content-type header when using the "nosniff" header. See https://bugzilla.mozilla.org/show_bug.cgi?id=1547076
-
+
Header set Content-Type "text/plain"
-
-
-
- DirectoryIndex index.xml
-
-
- # text/xml allows the page to be displayed in a browser and the encoding to be
- # determined from the document and not the HTTP headers. application/rss+xml will cause it to be downloaded.
-
-
- Header set Content-Type "text/xml; charset=utf-8"
-
-
+
# Enable HTTP CORS so that browser-based readers like Readium can access opds and ebooks
# Allow fonts for newsletter emails
# See https://github.com/standardebooks/tools/issues/2
-
+
Header set Access-Control-Allow-Origin "*"
-
+
# We use a different CSP policy for single-page files because our default one doesn't allow inline images or CSS
-
+
Header set Content-Security-Policy "default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline';"
-
+
# Remove www from requests
RewriteCond %{HTTP_HOST} ^www\.(.+) [NC]
@@ -136,13 +124,13 @@ Define webroot /standardebooks.org/web
SetEnv proxy-sendcl 1
# Forward all PHP requests to the php-fpm pool for this domain.
-
+
SetHandler "proxy:unix:/run/php/${domain}.sock|fcgi://${domain}"
Header set Cache-Control "no-store"
# Set some proxy properties.
-
+
ProxySet connectiontimeout=5 timeout=240
@@ -189,11 +177,11 @@ Define webroot /standardebooks.org/web
# Redirect index pages
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} -d
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME}/index.php -f
- RewriteRule (.*) /$1/index.php
+ RewriteRule (.*) $1/index.php
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} -d
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME}/index.xml -f
- RewriteRule (.*) /$1/index.xml
+ RewriteRule (.*) $1/index.xml
# Remove newline characters inserted by accident in some email clients
RewriteRule ^(.*)\r\n[\ ]?(.*)$ $1$2 [R=301,N]
@@ -247,13 +235,6 @@ Define webroot /standardebooks.org/web
RewriteCond %{REQUEST_FILENAME} !^/ebooks/.+?/text.*$
RewriteRule ^/ebooks/([^\.]+?)$ /ebooks/ebook.php?url-path=$1
- # If we ask for /opds/all?query=xyz, rewrite that to the search page.
- RewriteCond %{QUERY_STRING} ^query=
- RewriteRule ^/feeds/(opds|atom|rss)/all.xml$ /feeds/$1/search.php [QSA]
-
- # Rewrite old links to feeds
- RewriteRule ^/(opds|rss|atom)(.*)$ /feeds/$1$2 [R=301,L]
-
# Newsletter
RewriteRule ^/newsletter$ /newsletter/subscriptions/new.php [L]
RewriteRule ^/newsletter/subscriptions/([^/\.]+?)$ /newsletter/subscriptions/get.php?uuid=$1 [L]
@@ -269,4 +250,65 @@ Define webroot /standardebooks.org/web
RewriteCond expr "tolower(%{REQUEST_METHOD}) =~ /^post$/"
RewriteRule ^/patrons-circle/polls/([^/\.]+)/votes$ /patrons-circle/polls/votes/post.php?pollurlname=$1 [L]
+
+ # Feeds
+ # Rewrite old links to feeds
+ RewriteRule ^/(opds|rss|atom)(.*)$ /feeds/$1$2 [R=301,L]
+
+ # If we ask for /opds/all?query=xyz, rewrite that to the search page.
+ RewriteCond %{QUERY_STRING} \bquery=
+ RewriteRule ^/feeds/(opds|atom|rss)/all.xml$ /feeds/$1/search.php [QSA]
+
+ # Enable mod_authn_dbd
+ DBDriver mysql
+ DBDParams "dbname=se user=www-data"
+
+ # Enable HTTP Basic auth for feeds
+ AuthType Basic
+ AuthName "Enter your Patrons Circle email address or your API key, and a blank password."
+ Require valid-user
+ ErrorDocument 401 /feeds/401
+
+ # Credentials caching to prevent slamming the DB. socache must be ahead of dbd
+ AuthBasicProvider socache dbd
+ AuthnCacheProvideFor dbd
+ AuthnCacheContext ${domain}
+
+ # mod_authn_dbd SQL query to authenticate a user
+ # The hash is simply the hash of a blank password. We're only interested in the username/API key.
+ # We have to do this tortured query instead of a cleaner one, because the AuthDBDUserPWQuery
+ # function will only replace %s EXACTLY ONCE. We cannot have more than one %s in the query string.
+ AuthDBDUserPWQuery "\
+ select '$apr1$13q1pnGf$vQnIj94BXP1EPdL/4ISba.' from \
+ ( \
+ select Email, Uuid from Patrons p inner join Users u using (UserId) where p.Ended is null \
+ union \
+ select Email, Uuid from FeedUsers fu inner join Users u using (UserId) where fu.Ended is null \
+ ) x where %s in (Email, Uuid) limit 1 \
+ "
+
+
+ # Disable HTTP Basic auth for the feed XSL stylesheet and the new releases feeds
+ Require all granted
+
+
+
+ # Emit content-types for OPDS feeds, as some clients require a strictly correct content-type in order to work
+
+ Header set Content-Type "application/atom+xml;profile=opds-catalog;kind=acquisition; charset=utf-8"
+
+
+ Header set Content-Type "application/atom+xml;profile=opds-catalog;kind=navigation; charset=utf-8"
+
+
+
+ # Emit content-types for RSS/Atom feeds
+ rss|atom)/.+">
+ # Note the trailing e is required to complete the environmental variable reference
+ Header set Content-Type "application/%{MATCH_FEEDTYPE}e+xml; charset=utf-8"
+
+
+ Header set Content-Type "application/xhtml+xml; charset=utf-8"
+
+
diff --git a/config/sql/se/FeedUsers.sql b/config/sql/se/FeedUsers.sql
new file mode 100644
index 00000000..612bfbee
--- /dev/null
+++ b/config/sql/se/FeedUsers.sql
@@ -0,0 +1,7 @@
+CREATE TABLE `FeedUsers` (
+ `UserId` int(10) unsigned NOT NULL,
+ `Created` datetime NOT NULL,
+ `Ended` datetime DEFAULT NULL,
+ `Notes` text DEFAULT NULL,
+ KEY `idxUserId` (`UserId`,`Ended`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
diff --git a/config/sql/se/Patrons.sql b/config/sql/se/Patrons.sql
index b13e6e8a..74f8a79f 100644
--- a/config/sql/se/Patrons.sql
+++ b/config/sql/se/Patrons.sql
@@ -5,6 +5,6 @@ CREATE TABLE `Patrons` (
`IsSubscribedToEmails` tinyint(1) NOT NULL DEFAULT 1,
`Created` datetime NOT NULL,
`Ended` datetime DEFAULT NULL,
- PRIMARY KEY (`UserId`),
- KEY `index2` (`IsAnonymous`,`Ended`)
+ KEY `index2` (`IsAnonymous`,`Ended`),
+ KEY `index1` (`UserId`,`Ended`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
diff --git a/scripts/generate-feeds b/scripts/generate-feeds
index 8dab2ea6..11eae990 100755
--- a/scripts/generate-feeds
+++ b/scripts/generate-feeds
@@ -107,7 +107,7 @@ $opdsRootEntries = [
'acquisition')
];
-$opdsRoot = new OpdsNavigationFeed('Standard Ebooks', 'The navigation root for the Standard Ebooks OPDS feed.', '/feeds/opds', WEB_ROOT . '/feeds/opds/index.xml', $opdsRootEntries, null);
+$opdsRoot = new OpdsNavigationFeed('Standard Ebooks', 'The Standard Ebooks catalog.', '/feeds/opds', WEB_ROOT . '/feeds/opds/index.xml', $opdsRootEntries, null);
SaveFeed($opdsRoot, $force, $now);
// Create the subjects navigation document
diff --git a/templates/FeedHowTo.php b/templates/FeedHowTo.php
new file mode 100644
index 00000000..d2481581
--- /dev/null
+++ b/templates/FeedHowTo.php
@@ -0,0 +1,19 @@
+
+
Accessing the feeds
+
Our New Releases feeds are accessible by the public. Access to our other, more detailed feeds is available to our supporters in the Patrons Circle.
+
+
Individuals
+
+
Join the Patrons Circle by making a donation to get access to all of our ebook feeds for the duration of your gift.
+
Produce an ebook for Standard Ebooks to get lifetime access to our ebook feeds. If you’ve already done that, contact us to enable your access.
+
+
To access a feed, when prompted enter the email address you used to make your donation or on the S.E. mailing list, and leave the password field blank.
+
+
+
Organizations and projects
+
+
Corporate sponsors get access to all of our ebook feeds for the duration of their sponsorship. Contact us to chat about having your organization sponsor our mission.
+
Software projects that are open-source and not-for-profit may be granted free access to our feeds. Contact us to inquire.
Members of the Patrons Circle are steadfast supporters of free, unrestricted, and beautifully presented digital literature. Besides helping support the creation of free, gorgeous ebooks, they also have a direct voice in shaping the future of the Standard Ebooks catalog.
Join the Patrons Circle with a donation of $10/month or more, or join for one year with a one-time donation of $100 or more.
+
During your time as a member of the Patrons Circle, you get:
-
Patrons Circle members have their name listed on our masthead for the duration of their gift.
Once per quarter, Patrons Circle members may submit a book for inclusion on our Wanted Ebooks list. (Submissions must conform to our collections policy and are subject to approval.)
The ability to submit a book for inclusion on our Wanted Ebooks list, once per quarter. (Submissions must conform to our collections policy and are subject to approval.)
Sponsorships at the corporate level are a great way to show your organization’s commitment to the support of the literate arts.
+
Sponsorships at the corporate level are a great way to show your organization’s commitment to supporting the literate arts.
Your organization’s logo and a link will be listed on our masthead, which is prominently linked to on the Standard Ebooks website’s header and footer.
-
Get customized ONIX, OPDS, or RSS feeds of our ebook catalog for use by your organization for the duration of your sponsorship.
+
Get access to our OPDS, Atom, and RSS ebook feeds for use by your organization for the duration of your sponsorship. We can also produce different kinds of feeds to meet your needs, like ONIX feeds.
diff --git a/www/feeds/401.php b/www/feeds/401.php
new file mode 100644
index 00000000..776f69f6
--- /dev/null
+++ b/www/feeds/401.php
@@ -0,0 +1,46 @@
+
+require_once('Core.php');
+
+use function Safe\preg_match;
+
+$type = '';
+preg_match('/^\/feeds\/(opds|rss|atom)/ius', $_SERVER['REQUEST_URI'], $matches);
+
+if(sizeof($matches) > 0){
+ $type = $matches[1];
+}
+
+$title = 'Standard Ebooks Ebook Feeds';
+if($type == 'opds'){
+ $title = 'The Standard Ebooks OPDS Feed';
+}
+
+if($type == 'rss'){
+ $title = 'Standard Ebooks RSS Feeds';
+}
+
+if($type == 'atom'){
+ $title = 'Standard Ebooks Atom Feeds';
+}
+
+?>= Template::Header(['title' => 'The Standard Ebooks OPDS feed', 'highlight' => '', 'description' => 'Get access to the Standard Ebooks OPDS feed for use in ereading programs in scripting.']) ?>
+
+
+ if($type == 'opds'){ ?>
+
The Standard Ebooks OPDS Feed
+
OPDS feeds are designed for use with ereading apps on your phone or tablet, or with ereading systems like KOreader. Add our OPDS feed to your ereading app to search, browse, and download from our entire catalog, directly in your ereader.
+
They’re also perfect for scripting, or for libraries or other organizations who wish to download and process our catalog of ebooks.
+ }elseif($type == 'rss'){ ?>
+
Standard Ebooks RSS Feeds
+
RSS feeds are an alternative to Atom feeds. They contain less information than Atom feeds, but might be better supported by some RSS readers.
+ }elseif($type == 'atom'){ ?>
+
Standard Ebooks Atom Feeds
+
Atom feeds can be read by one of the many RSS clients available for download, like Thunderbird. They contain more information than regular RSS feeds. Most RSS clients can read both Atom and RSS feeds.
+
Note that some RSS readers may show these feeds ordered by when an ebook was last updated, even though the feeds are ordered by when an ebook was first released. You should be able to change the sort order in your RSS reader.
+ }else{ ?>
+
Standard Ebooks Ebook Feeds
+ } ?>
+ = Template::FeedHowTo() ?>
+
+
+= Template::Footer() ?>
diff --git a/www/feeds/atom/index.php b/www/feeds/atom/index.php
index fc59b923..080f9680 100644
--- a/www/feeds/atom/index.php
+++ b/www/feeds/atom/index.php
@@ -1,35 +1,39 @@
require_once('Core.php');
-?>= Template::Header(['title' => 'Atom Ebook Feeds', 'description' => 'A list of available Atom 1.0 feeds of Standard Ebooks ebooks.']) ?>
+?>= Template::Header(['title' => 'Atom 1.0 Ebook Feeds', 'description' => 'A list of available Atom 1.0 feeds of Standard Ebooks ebooks.']) ?>
-
-
Atom 1.0 Feeds
-
Atom feeds can be read by one of the many RSS clients available for download, like Thunderbird. They contain more information than regular RSS feeds. Most RSS clients can read both Atom and RSS feeds.
+
+
Atom 1.0 Ebook Feeds
+
Atom feeds can be read by one of the many RSS clients available for download, like Thunderbird. They contain more information than regular RSS feeds. Most RSS clients can read both Atom and RSS feeds.
Note that some RSS readers may show these feeds ordered by when an ebook was last updated, even though the feeds are ordered by when an ebook was first released. You should be able to change the sort order in your RSS reader.
+
-
+
= Template::Footer() ?>
diff --git a/www/feeds/atom/subjects/index.php b/www/feeds/atom/subjects/index.php
index e0bec2c2..50c2cd8f 100644
--- a/www/feeds/atom/subjects/index.php
+++ b/www/feeds/atom/subjects/index.php
@@ -1,18 +1,22 @@
require_once('Core.php');
-?>= Template::Header(['title' => 'Atom Ebook Feeds by Subject', 'description' => 'A list of available Atom 1.0 feeds of Standard Ebooks ebooks by subject.']) ?>
+?>= Template::Header(['title' => 'Atom 1.0 Ebook Feeds by Subject', 'description' => 'A list of available Atom 1.0 feeds of Standard Ebooks ebooks by subject.']) ?>
-
+
= Template::Footer() ?>
diff --git a/www/feeds/index.php b/www/feeds/index.php
index 7d085910..708e2464 100644
--- a/www/feeds/index.php
+++ b/www/feeds/index.php
@@ -10,28 +10,30 @@ require_once('Core.php');
?>= Template::Header(['title' => 'Ebook Feeds', 'description' => 'A list of available feeds of Standard Ebooks ebooks.']) ?>
-
+
Ebook Feeds
-
We offers several feeds that you can use to get notified about new ebooks, or to browse and download from our catalog directly in your ereader.
+
We offer several ebook feeds that you can use in your ereading app to browse, search, and download from our catalog. You can also add our feeds to your RSS client to get notified of new ebooks as they’re released.
+ = Template::FeedHowTo() ?>
OPDS 1.2 feeds
-
OPDS feeds are designed for use with ereading systems like KOreader or Calibre, or with ereaders like Foliate. They allow you to search, browse, and download from our catalog, directly in your ereader. They’re also perfect for organizations who wish to download and process our catalog efficiently.
+
OPDS feeds are designed for use with ereading apps on your phone or tablet, or with ereading systems like KOreader. Add our OPDS feed to your ereading app to search, browse, and download from our entire catalog, directly in your ereader.
+
They’re also perfect for scripting, or for libraries or other organizations who wish to download and process our catalog of ebooks.
OPDS Reader, a plugin that adds OPDS support to Calibre
+
+
@@ -40,9 +42,9 @@ require_once('Core.php');
Note that some RSS readers may show these feeds ordered by when an ebook was last updated, even though the feeds are ordered by when an ebook was first released. You should be able to change the sort order in your RSS reader.
+
-
+
= Template::Footer() ?>
diff --git a/www/feeds/rss/subjects/index.php b/www/feeds/rss/subjects/index.php
index ad36a41a..87b686a2 100644
--- a/www/feeds/rss/subjects/index.php
+++ b/www/feeds/rss/subjects/index.php
@@ -1,10 +1,10 @@
require_once('Core.php');
-?>= Template::Header(['title' => 'RSS Ebook Feeds by Subject', 'description' => 'A list of available RSS 2.0 feeds of Standard Ebooks ebooks by subject.']) ?>
+?>= Template::Header(['title' => 'RSS 2.0 Ebook Feeds by Subject', 'description' => 'A list of available RSS 2.0 feeds of Standard Ebooks ebooks by subject.']) ?>
-