From b533715f075f9179350435d2cbcb63cdd5ac53a3 Mon Sep 17 00:00:00 2001 From: topynate Date: Sat, 19 Dec 2020 05:07:23 +0200 Subject: [PATCH 1/2] Recipe for Substack newsletters This recipe uses the RSS feeds which Substack provides for each newsletter. Unlike the built-in RSS recipe, this one also lets Calibre download all posts from your paid subscriptions. --- recipes/substack.recipe | 53 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 recipes/substack.recipe diff --git a/recipes/substack.recipe b/recipes/substack.recipe new file mode 100644 index 0000000000..d858abb484 --- /dev/null +++ b/recipes/substack.recipe @@ -0,0 +1,53 @@ +#!/usr/bin/env python +# vim:fileencoding=utf-8 +# +# Title: Substack +# License: GNU General Public License v3 – https://www.gnu.org/licenses/gpl-3.0.html +# Copyright: Nathan Cook (nathan.cook@gmail.com) +## +# Written: 2020-12-18 +## + +__license__ = 'GNU General Public License v3 – https://www.gnu.org/licenses/gpl-3.0.html' +__copyright__ = 'Nathan Cook – 2020-12-18' +__version__ = 'v0.1.0' +__date__ = '2020-12-18' + + +from calibre.web.feeds.news import BasicNewsRecipe +import json +from mechanize import Request + +class Substack(BasicNewsRecipe): + title = 'Substack' + oldest_article = 7 + max_articles_per_feed = 100 + auto_cleanup = True + +# Every Substack publication has an RSS feed at https://{name}.substack.com/feed. +# The same URL provides either all posts, or all free posts + previews of paid posts, +# depending on whether you're logged in. + feeds = [ + ('Novum Lumen', 'https://novumlumen.substack.com/feed'), # gratuitously self-promotional example + ] + + def get_browser(self): + br = BasicNewsRecipe.get_browser(self) + if self.username is not None and self.password is not None: + br.open('https://substack.com/account/login?redirect=%2F&email=&with_password=') + data = json.dumps({'email': self.username, 'password': self.password, 'captch_response':None}) + req = Request( + url='https://substack.com/api/v1/login', + headers = { + 'Accept': '*/*', + 'Content-Type': 'application/json', + 'Origin': 'https://substack.com', + 'Referer': 'https://substack.com/account/login?redirect=%2F&email=&with_password=', + }, + data=data, + method='POST') + res = br.open(req) + if res.getcode() != 200: + raise ValueError('Login failed, check username and password') + return br + From b3e96eabab65e4e202f2c93b23e338ec25a1136a Mon Sep 17 00:00:00 2001 From: topynate Date: Sat, 19 Dec 2020 05:53:28 +0200 Subject: [PATCH 2/2] Add missing fields to substack recipe --- recipes/substack.recipe | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/recipes/substack.recipe b/recipes/substack.recipe index d858abb484..0f08290775 100644 --- a/recipes/substack.recipe +++ b/recipes/substack.recipe @@ -9,9 +9,12 @@ ## __license__ = 'GNU General Public License v3 – https://www.gnu.org/licenses/gpl-3.0.html' -__copyright__ = 'Nathan Cook – 2020-12-18' -__version__ = 'v0.1.0' -__date__ = '2020-12-18' +__copyright__ = 'Nathan Cook – 2020-12-19' +__version__ = 'v0.1.1' +__date__ = '2020-12-19' +__author__ = 'topynate' + +language = 'en' from calibre.web.feeds.news import BasicNewsRecipe @@ -20,9 +23,11 @@ from mechanize import Request class Substack(BasicNewsRecipe): title = 'Substack' + __author__ ='topynate' oldest_article = 7 max_articles_per_feed = 100 auto_cleanup = True + needs_subscription = 'optional' # Every Substack publication has an RSS feed at https://{name}.substack.com/feed. # The same URL provides either all posts, or all free posts + previews of paid posts,