diff --git a/Makefile.config b/Makefile.config index 2dc7863..c97c941 100644 --- a/Makefile.config +++ b/Makefile.config @@ -7,6 +7,7 @@ export FLASK_ENV=development export TWOOTFEED_CONFIG_DIR=$(APP_PATH)/ export TWOOTFEED_CONFIG_FILE=$(APP_PATH)/config.yml export TWOOTFEED_LOG=twootfeed.log +export TWOOTFEED_SETTINGS=DevelopmentConfig GUNICORN_LOG=gunicorn.log diff --git a/docs/_images/FreshRSS.png b/docs/_images/FreshRSS.png deleted file mode 100644 index 5034351..0000000 Binary files a/docs/_images/FreshRSS.png and /dev/null differ diff --git a/docs/_images/MastodonFreshRSS.png b/docs/_images/MastodonFreshRSS.png deleted file mode 100644 index 8a3edda..0000000 Binary files a/docs/_images/MastodonFreshRSS.png and /dev/null differ diff --git a/docs/_images/MastodonRSSFeed.png b/docs/_images/MastodonRSSFeed.png deleted file mode 100644 index 3937bc7..0000000 Binary files a/docs/_images/MastodonRSSFeed.png and /dev/null differ diff --git a/docs/_images/RSSFeed.png b/docs/_images/RSSFeed.png deleted file mode 100644 index eae4452..0000000 Binary files a/docs/_images/RSSFeed.png and /dev/null differ diff --git a/docs/_images/mastodon.png b/docs/_images/mastodon.png deleted file mode 100644 index 2bc82c8..0000000 Binary files a/docs/_images/mastodon.png and /dev/null differ diff --git a/docs/_images/twitter.png b/docs/_images/twitter.png deleted file mode 100644 index e6869b9..0000000 Binary files a/docs/_images/twitter.png and /dev/null differ diff --git a/docs/_sources/features.rst.txt b/docs/_sources/features.rst.txt index 91ddb08..bf90b48 100644 --- a/docs/_sources/features.rst.txt +++ b/docs/_sources/features.rst.txt @@ -20,49 +20,7 @@ The feed displays only the original tweets (not the retweets) and toots, with: - location (only for Twitter) - numbers of retweets and likes for tweets and boosts and favourites for toots +.. warning:: - -.. danger:: - - | **twootfeed** is developed for personal use. - | Tweets and toots are displayed for the user associated to API keys, it may expose private items if feeds are publicly available. - - -Examples -~~~~~~~~ - -- Search on Twitter - -.. figure:: _images/twitter.png - :alt: Twitter search - :figclass: doc-img - -Results in RSS Feed: - -.. figure:: _images/RSSFeed.png - :alt: RSS Feed - :figclass: doc-img - -Display on FreshRSS, a great free self-hosted aggregator (https://github.com/FreshRSS/FreshRSS): - -.. figure:: _images/FreshRSS.png - :alt: FreshRSS - :figclass: doc-img - -- Search on Mastodon - -.. figure:: _images/mastodon.png - :alt: Mastodon search - :figclass: doc-img - -Results in RSS Feed: - -.. figure:: _images/MastodonRSSFeed.png - :alt: Mastodon Feed - :figclass: doc-img - -Display on FreshRSS: - -.. figure:: _images/MastodonFreshRSS.png - :alt: Mastodon FreshRSS - :figclass: doc-img + | **twootfeed** is developed for a personal use. + | Tweets and toots are displayed for the user associated to the API keys (feeds may contain items with **restricted visibility**). diff --git a/docs/_sources/installation.rst.txt b/docs/_sources/installation.rst.txt index 62b4753..4660ddb 100644 --- a/docs/_sources/installation.rst.txt +++ b/docs/_sources/installation.rst.txt @@ -41,16 +41,45 @@ Installation Update the `feed and app parameters `_. + .. versionadded:: 0.7.0 + + Since **twootfeed** is connected to the user account, feeds may display items with **restricted visibility**. + + A token is now mandatory to start the application and access feeds (minimum length: 25 characters). + + Some examples for token generation: + + > with Python + + .. code-block:: bash + + $ python + Python 3.10.5 (main, Jun 6 2022, 18:49:26) [GCC 12.1.0] on linux + Type "help", "copyright", "credits" or "license" for more information. + >>> import secrets + >>> secrets.token_urlsafe() + 'pgoeS3qOsLHxduzNY_gmn6p5vWZqSzqBgnb_VPupQ7o' + >>> + + > with a linux command line + + .. code-block:: bash + + $ date | sha256sum | base64 | head -c 25; echo + NWU2MzE1ZGM0MmVlZDg5NDNhN + + - The files location can be changed with the following environment variables: -========================= =============================================== =========================================================================================== - variable description app default value -========================= =============================================== =========================================================================================== - `TWOOTFEED_CONFIG_DIR` configuration and credentials files directory **'~/.config/twootfeed/'** - `TWOOTFEED_CONFIG_FILE` config file full path config dir + **'config.yml'** => with default value: **'~/.config/twootfeed/config.yml'** - `TWOOTFEED_LOG` application log file _no default value (log printed on the console)_ -========================= =============================================== =========================================================================================== +=========================== =============================================== =========================================================================================== + variable description app default value +=========================== =============================================== =========================================================================================== + ``TWOOTFEED_CONFIG_DIR`` configuration and credentials files directory **'~/.config/twootfeed/'** + ``TWOOTFEED_CONFIG_FILE`` config file full path config dir + **'config.yml'** => with default value: **'~/.config/twootfeed/config.yml'** + ``TWOOTFEED_LOG`` application log file `no default value (log printed on the console)` + ``TWOOTFEED_SETTINGS`` application settings **'ProductionConfig'** +=========================== =============================================== =========================================================================================== - Start the app @@ -62,33 +91,35 @@ Installation Usage ~~~~~ -The RSS feeds are available on these urls: +The following RSS feeds are available: - for Twitter search: - - http://localhost:8080/tweets/ - - http://localhost:8080/ (*will be deprecated in a next version*) + - http://localhost:8080/tweets/?token=XXX + - http://localhost:8080/?token=XXX (*will be deprecated in a next version*) - for Mastodon search: - keyword as a hashtag: - - http://localhost:8080/toots/ (without the leading #) + - http://localhost:8080/toots/?token=XXX (without the leading #) - query: - - http://localhost:8080/toots/search/ - - http://localhost:8080/toot_search/ (*will be deprecated in a next version*) + - http://localhost:8080/toots/search/?token=XXX + - http://localhost:8080/toot_search/?token=XXX (*will be deprecated in a next version*) - for Mastodon connected user favorites: - - http://localhost:8080/toots/favorites - - http://localhost:8080/toot_favorites (*will be deprecated in a next version*) + - http://localhost:8080/toots/favorites?token=XXX + - http://localhost:8080/toot_favorites?token=XXX (*will be deprecated in a next version*) - for Mastodon connected user bookmarks: - - http://localhost:8080/toots/bookmarks + - http://localhost:8080/toots/bookmarks?token=XXX - for Mastodon connected user home timeline: - - http://localhost:8080/toots/home_timeline \ No newline at end of file + - http://localhost:8080/toots/home_timeline?token=XXX + +where XXX is the token set in configuration. \ No newline at end of file diff --git a/docs/_sources/parameters.rst.txt b/docs/_sources/parameters.rst.txt index 0faefbc..e6df713 100644 --- a/docs/_sources/parameters.rst.txt +++ b/docs/_sources/parameters.rst.txt @@ -25,6 +25,7 @@ Application parameters are stored in ``config.yml`` file: timezone: 'Europe/Paris' text_length_limit: 100 max_items: 20 + token: '' app: host: '0.0.0.0' port: '8080' @@ -54,6 +55,7 @@ Feed * **timezone**: Feed timezone * **text_length_limit:** title length of a Feed item * **max_items**: maximum number of displayed items +* **token**: token for feeds access App ~~~ diff --git a/docs/features.html b/docs/features.html index 4ef6769..24bb1e2 100644 --- a/docs/features.html +++ b/docs/features.html @@ -48,7 +48,6 @@ ---+++ @@ -137,17 +158,21 @@

Installation

+ - + - + - + + + + +

variable

TWOOTFEED_CONFIG_DIR

TWOOTFEED_CONFIG_DIR

configuration and credentials files directory

‘~/.config/twootfeed/’

TWOOTFEED_CONFIG_FILE

TWOOTFEED_CONFIG_FILE

config file full path

config dir + ‘config.yml’ => with default value: ‘~/.config/twootfeed/config.yml’

TWOOTFEED_LOG

TWOOTFEED_LOG

application log file

_no default value (log printed on the console)_

no default value (log printed on the console)

TWOOTFEED_SETTINGS

application settings

‘ProductionConfig’

@@ -160,13 +185,13 @@

Installation

Usage

-

The RSS feeds are available on these urls:

+

The following RSS feeds are available:

+

where XXX is the token set in configuration.

diff --git a/docs/parameters.html b/docs/parameters.html index 60636bc..b22f212 100644 --- a/docs/parameters.html +++ b/docs/parameters.html @@ -106,6 +106,7 @@

Application and feeds parameters timezone: 'Europe/Paris' text_length_limit: 100 max_items: 20 + token: '' app: host: '0.0.0.0' port: '8080' @@ -140,6 +141,7 @@

Feed
  • timezone: Feed timezone

  • text_length_limit: title length of a Feed item

  • max_items: maximum number of displayed items

  • +
  • token: token for feeds access

  • diff --git a/docs/searchindex.js b/docs/searchindex.js index a3cbf4c..c6f2e17 100644 --- a/docs/searchindex.js +++ b/docs/searchindex.js @@ -1 +1 @@ -Search.setIndex({"docnames": ["changelog", "features", "how-to-developers", "index", "installation", "parameters"], "filenames": ["changelog.md", "features.rst", "how-to-developers.rst", "index.rst", "installation.rst", "parameters.rst"], "titles": ["Change log", "Features", "Quick start for developers", "Welcome to twootfeed\u2019s documentation!", "Installation and usage", "Application and feeds parameters"], "terms": {"updat": [0, 4], "depend": 0, "includ": [0, 4], "tweepi": [0, 2], "note": 0, "twootfe": [0, 1, 2, 4, 5], "still": 0, "us": [0, 1, 2, 4, 5], "twitter": [0, 1, 2, 3, 4], "api": [0, 1, 2, 4], "v1": 0, "issu": 0, "27": 0, "gener": [0, 1, 3, 5], "document": 0, "sphinx": 0, "add": 0, "feed": [0, 1, 3, 4], "user": [0, 1, 4], "s": [0, 2, 4], "bookmark": [0, 1, 3, 4], "In": 0, "thi": [0, 2], "releas": 0, "were": 0, "close": 0, "toot": [0, 1, 4], "_": [0, 4], "search": [0, 1, 3, 4, 5], "rout": 0, "return": 0, "404": 0, "error": 0, "wa": 0, "24": 0, "correct": 0, "http": [0, 1, 2, 4, 5], "code": 0, "statu": 0, "when": 0, "kei": [0, 1, 2, 4, 5], "ar": [0, 1, 4, 5], "provid": 0, "22": 0, "The": [0, 1, 4], "number": [0, 1, 5], "worker": [0, 5], "server": [0, 2, 5], "can": [0, 4], "set": 0, "21": 0, "pagin": 0, "get": [0, 2, 5], "19": 0, "environ": [0, 4], "variabl": [0, 4], "limit": 0, "item": [0, 1, 5], "avoid": 0, "time": 0, "out": 0, "refer": 0, "charact": 0, "titl": [0, 5], "now": 0, "avail": [0, 1, 4], "pypi": 0, "name": [0, 5], "url": [0, 1, 4, 5], "displai": [0, 1, 5], "usernam": [0, 1], "python": [0, 2, 4], "sinc": 0, "refactor": 0, "continu": 0, "integr": 0, "test": 0, "coverag": 0, "perform": 0, "improv": 0, "gunicorn": [0, 2, 5], "product": 0, "16": 0, "long": 0, "tweet": [0, 1, 4], "truncat": 0, "imag": [0, 1], "longer": 0, "handl": 0, "tweeperror": 0, "rate": 0, "exceed": 0, "major": 0, "ad": 0, "makefil": 0, "eas": 0, "instal": [0, 3], "start": [0, 3, 4], "see": [0, 2, 4, 5], "readm": 0, "thank": 0, "georgedorn": 0, "pr": 0, "client": [0, 2, 4], "script": [0, 4, 5], "rss": [0, 1, 3, 4], "authent": [0, 5], "regist": [0, 4, 5], "app": [0, 4], "credenti": [0, 4, 5], "minor": 0, "flask": [0, 2], "instead": 0, "django": 0, "queri": [0, 4], "descript": [0, 4, 5], "extern": 0, "all": 0, "media": 0, "an": [1, 5], "from": [1, 3, 4, 5], "mastodon": [1, 2, 3, 4], "favorit": [1, 3, 4], "home": [1, 4], "timelin": [1, 4], "onli": [1, 2], "origin": 1, "retweet": 1, "link": [1, 5], "hashtag": [1, 4], "sourc": 1, "locat": [1, 4], "like": 1, "boost": 1, "favourit": 1, "develop": [1, 3, 5], "person": 1, "associ": 1, "mai": 1, "expos": 1, "privat": 1, "publicli": 1, "result": [1, 5], "freshrss": 1, "great": 1, "free": 1, "self": 1, "host": [1, 5], "aggreg": 1, "github": [1, 2], "com": [1, 2, 4, 5], "feedgener": 2, "standard": 2, "py": 2, "pytz": 2, "pyyaml": 2, "beautifulsoup": 2, "clone": 2, "repo": 2, "git": 2, "samr1": 2, "virtualenv": 2, "packag": 2, "cd": 2, "make": 2, "fill": [2, 4], "field": [2, 4], "you": [2, 4], "config": [2, 4, 5], "yml": [2, 4, 5], "next": [2, 4], "step": 2, "serv": 2, "pars": 3, "featur": 3, "usag": 3, "applic": [3, 4], "paramet": [3, 4], "quick": 3, "chang": [3, 4], "log": [3, 4], "3": 4, "7": 4, "pip": 4, "initi": 4, "configur": 4, "file": [4, 5], "twootfeed_init": 4, "copi": 4, "past": 4, "valu": 4, "consumerkei": [4, 5], "consumersecret": [4, 5], "wrapper": 4, "which": 4, "your": 4, "prompt": 4, "creat": [4, 5], "twootfeed_create_mastodon_cli": 4, "follow": 4, "default": 4, "twootfeed_config_dir": 4, "directori": 4, "twootfeed_config_fil": 4, "full": 4, "path": 4, "dir": 4, "twootfeed_log": 4, "_no": 4, "print": 4, "consol": 4, "localhost": [4, 5], "8080": [4, 5], "keyword": 4, "deprec": 4, "version": 4, "without": 4, "lead": 4, "toot_search": 4, "connect": 4, "toot_favorit": 4, "home_timelin": 4, "store": 5, "q": 5, "social": 5, "client_id_fil": 5, "tootrss_clientcr": 5, "txt": 5, "access_token_fil": 5, "tootrss_usercr": 5, "app_nam": 5, "tootrss": 5, "identifi": 5, "languag": 5, "fr": 5, "author_nam": 5, "feed_url": 5, "5000": 5, "timezon": 5, "europ": 5, "pari": 5, "text_length_limit": 5, "100": 5, "max_item": 5, "20": 5, "0": 5, "port": 5, "nb_worker": 5, "4": 5, "defin": 5, "en": 5, "instanc": 5, "author": 5, "length": 5, "maximum": 5, "wsgi": 5, "mandatori": 5}, "objects": {}, "objtypes": {}, "objnames": {}, "titleterms": {"chang": 0, "log": 0, "version": 0, "0": 0, "6": 0, "8": 0, "2021": 0, "10": 0, "05": 0, "misc": 0, "7": 0, "2020": 0, "03": 0, "15": 0, "new": 0, "featur": [0, 1], "2019": 0, "20": 0, "5": 0, "09": 0, "23": 0, "bug": 0, "fix": 0, "4": 0, "08": 0, "31": 0, "3": 0, "2": 0, "01": 0, "26": 0, "1": 0, "25": 0, "2018": 0, "17": 0, "mastodon": [0, 5], "extend": 0, "support": 0, "14": 0, "make": 0, "04": 0, "favorit": 0, "2017": 0, "12": 0, "07": 0, "18": 0, "descript": 1, "exampl": 1, "quick": 2, "start": 2, "develop": 2, "depend": 2, "instal": [2, 4], "test": 2, "welcom": 3, "twootfe": 3, "s": 3, "document": 3, "content": 3, "usag": 4, "requir": 4, "applic": 5, "feed": 5, "paramet": 5, "twitter": 5, "app": 5}, "envversion": {"sphinx.domains.c": 2, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 6, "sphinx.domains.index": 1, "sphinx.domains.javascript": 2, "sphinx.domains.math": 2, "sphinx.domains.python": 3, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx": 56}}) \ No newline at end of file +Search.setIndex({"docnames": ["changelog", "features", "how-to-developers", "index", "installation", "parameters"], "filenames": ["changelog.md", "features.rst", "how-to-developers.rst", "index.rst", "installation.rst", "parameters.rst"], "titles": ["Change log", "Features", "Quick start for developers", "Welcome to twootfeed\u2019s documentation!", "Installation and usage", "Application and feeds parameters"], "terms": {"updat": [0, 4], "depend": 0, "includ": [0, 4], "tweepi": [0, 2], "note": 0, "twootfe": [0, 1, 2, 4, 5], "still": 0, "us": [0, 1, 2, 4, 5], "twitter": [0, 1, 2, 3, 4], "api": [0, 1, 2, 4], "v1": 0, "issu": 0, "27": 0, "gener": [0, 1, 3, 4, 5], "document": 0, "sphinx": 0, "add": 0, "feed": [0, 1, 3, 4], "user": [0, 1, 4], "s": [0, 2, 4], "bookmark": [0, 1, 3, 4], "In": 0, "thi": [0, 2], "releas": 0, "were": 0, "close": 0, "toot": [0, 1, 4], "_": 0, "search": [0, 1, 3, 4, 5], "rout": 0, "return": 0, "404": 0, "error": 0, "wa": 0, "24": 0, "correct": 0, "http": [0, 2, 4, 5], "code": 0, "statu": 0, "when": 0, "kei": [0, 1, 2, 4, 5], "ar": [0, 1, 4, 5], "provid": 0, "22": 0, "The": [0, 1, 4], "number": [0, 1, 5], "worker": [0, 5], "server": [0, 2, 5], "can": [0, 4], "set": [0, 4], "21": 0, "pagin": 0, "get": [0, 2, 5], "19": 0, "environ": [0, 4], "variabl": [0, 4], "limit": 0, "item": [0, 1, 4, 5], "avoid": 0, "time": 0, "out": 0, "refer": 0, "charact": [0, 4], "titl": [0, 5], "now": [0, 4], "avail": [0, 4], "pypi": 0, "name": [0, 5], "url": [0, 1, 5], "displai": [0, 1, 4, 5], "usernam": [0, 1], "python": [0, 2, 4], "sinc": [0, 4], "refactor": 0, "continu": 0, "integr": 0, "test": 0, "coverag": 0, "perform": 0, "improv": 0, "gunicorn": [0, 2, 5], "product": 0, "16": 0, "long": 0, "tweet": [0, 1, 4], "truncat": 0, "imag": [0, 1], "longer": 0, "handl": 0, "tweeperror": 0, "rate": 0, "exceed": 0, "major": 0, "ad": 0, "makefil": 0, "eas": 0, "instal": [0, 3], "start": [0, 3, 4], "see": [0, 2, 4, 5], "readm": 0, "thank": 0, "georgedorn": 0, "pr": 0, "client": [0, 2, 4], "script": [0, 4, 5], "rss": [0, 1, 3, 4], "authent": [0, 5], "regist": [0, 4, 5], "app": [0, 4], "credenti": [0, 4, 5], "minor": 0, "flask": [0, 2], "instead": 0, "django": 0, "queri": [0, 4], "descript": [0, 4, 5], "extern": 0, "all": 0, "media": 0, "an": [1, 5], "from": [1, 3, 4, 5], "mastodon": [1, 2, 3, 4], "favorit": [1, 3, 4], "home": [1, 4], "timelin": [1, 4], "onli": [1, 2], "origin": 1, "retweet": 1, "link": [1, 5], "hashtag": [1, 4], "sourc": 1, "locat": [1, 4], "like": 1, "boost": 1, "favourit": 1, "develop": [1, 3, 5], "person": 1, "associ": 1, "mai": [1, 4], "contain": 1, "restrict": [1, 4], "visibl": [1, 4], "feedgener": 2, "standard": 2, "py": 2, "pytz": 2, "pyyaml": 2, "beautifulsoup": 2, "clone": 2, "repo": 2, "git": 2, "github": 2, "com": [2, 4, 5], "samr1": 2, "virtualenv": 2, "packag": 2, "cd": 2, "make": 2, "fill": [2, 4], "field": [2, 4], "you": [2, 4], "config": [2, 4, 5], "yml": [2, 4, 5], "next": [2, 4], "step": 2, "serv": 2, "pars": 3, "featur": 3, "usag": 3, "applic": [3, 4], "paramet": [3, 4], "quick": 3, "chang": [3, 4], "log": [3, 4], "3": 4, "7": 4, "pip": 4, "initi": 4, "configur": 4, "file": [4, 5], "twootfeed_init": 4, "copi": 4, "past": 4, "valu": 4, "consumerkei": [4, 5], "consumersecret": [4, 5], "wrapper": 4, "which": 4, "your": 4, "prompt": 4, "creat": [4, 5], "twootfeed_create_mastodon_cli": 4, "new": 4, "version": 4, "0": [4, 5], "connect": 4, "account": 4, "A": 4, "token": [4, 5], "mandatori": [4, 5], "access": [4, 5], "minimum": 4, "length": [4, 5], "25": 4, "some": 4, "exampl": 4, "10": 4, "5": 4, "main": 4, "jun": 4, "6": 4, "2022": 4, "18": 4, "49": 4, "26": 4, "gcc": 4, "12": 4, "1": 4, "linux": 4, "type": 4, "help": 4, "copyright": 4, "credit": 4, "licens": 4, "more": 4, "inform": 4, "import": 4, "secret": 4, "token_urlsaf": 4, "pgoes3qoslhxduzny_gmn6p5vwzqszqbgnb_vpupq7o": 4, "command": 4, "line": 4, "date": 4, "sha256sum": 4, "base64": 4, "head": 4, "c": 4, "echo": 4, "nwu2mze1zgm0mmvlzdg5ndnhn": 4, "follow": 4, "default": 4, "twootfeed_config_dir": 4, "directori": 4, "twootfeed_config_fil": 4, "full": 4, "path": 4, "dir": 4, "twootfeed_log": 4, "print": 4, "consol": 4, "twootfeed_set": 4, "productionconfig": 4, "localhost": [4, 5], "8080": [4, 5], "keyword": 4, "xxx": 4, "deprec": 4, "without": 4, "lead": 4, "toot_search": 4, "toot_favorit": 4, "home_timelin": 4, "where": 4, "store": 5, "q": 5, "result": 5, "social": 5, "client_id_fil": 5, "tootrss_clientcr": 5, "txt": 5, "access_token_fil": 5, "tootrss_usercr": 5, "app_nam": 5, "tootrss": 5, "identifi": 5, "languag": 5, "fr": 5, "author_nam": 5, "feed_url": 5, "5000": 5, "timezon": 5, "europ": 5, "pari": 5, "text_length_limit": 5, "100": 5, "max_item": 5, "20": 5, "host": 5, "port": 5, "nb_worker": 5, "4": 5, "defin": 5, "en": 5, "instanc": 5, "author": 5, "maximum": 5, "wsgi": 5}, "objects": {}, "objtypes": {}, "objnames": {}, "titleterms": {"chang": 0, "log": 0, "version": 0, "0": 0, "6": 0, "8": 0, "2021": 0, "10": 0, "05": 0, "misc": 0, "7": 0, "2020": 0, "03": 0, "15": 0, "new": 0, "featur": [0, 1], "2019": 0, "20": 0, "5": 0, "09": 0, "23": 0, "bug": 0, "fix": 0, "4": 0, "08": 0, "31": 0, "3": 0, "2": 0, "01": 0, "26": 0, "1": 0, "25": 0, "2018": 0, "17": 0, "mastodon": [0, 5], "extend": 0, "support": 0, "14": 0, "make": 0, "04": 0, "favorit": 0, "2017": 0, "12": 0, "07": 0, "18": 0, "descript": 1, "quick": 2, "start": 2, "develop": 2, "depend": 2, "instal": [2, 4], "test": 2, "welcom": 3, "twootfe": 3, "s": 3, "document": 3, "content": 3, "usag": 4, "requir": 4, "applic": 5, "feed": 5, "paramet": 5, "twitter": 5, "app": 5}, "envversion": {"sphinx.domains.c": 2, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 6, "sphinx.domains.index": 1, "sphinx.domains.javascript": 2, "sphinx.domains.math": 2, "sphinx.domains.python": 3, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx": 56}}) \ No newline at end of file diff --git a/docsrc/source/_images/FreshRSS.png b/docsrc/source/_images/FreshRSS.png deleted file mode 100644 index 5034351..0000000 Binary files a/docsrc/source/_images/FreshRSS.png and /dev/null differ diff --git a/docsrc/source/_images/MastodonFreshRSS.png b/docsrc/source/_images/MastodonFreshRSS.png deleted file mode 100644 index 8a3edda..0000000 Binary files a/docsrc/source/_images/MastodonFreshRSS.png and /dev/null differ diff --git a/docsrc/source/_images/MastodonRSSFeed.png b/docsrc/source/_images/MastodonRSSFeed.png deleted file mode 100644 index 3937bc7..0000000 Binary files a/docsrc/source/_images/MastodonRSSFeed.png and /dev/null differ diff --git a/docsrc/source/_images/RSSFeed.png b/docsrc/source/_images/RSSFeed.png deleted file mode 100644 index eae4452..0000000 Binary files a/docsrc/source/_images/RSSFeed.png and /dev/null differ diff --git a/docsrc/source/_images/mastodon.png b/docsrc/source/_images/mastodon.png deleted file mode 100644 index 2bc82c8..0000000 Binary files a/docsrc/source/_images/mastodon.png and /dev/null differ diff --git a/docsrc/source/_images/twitter.png b/docsrc/source/_images/twitter.png deleted file mode 100644 index e6869b9..0000000 Binary files a/docsrc/source/_images/twitter.png and /dev/null differ diff --git a/docsrc/source/features.rst b/docsrc/source/features.rst index 91ddb08..bf90b48 100644 --- a/docsrc/source/features.rst +++ b/docsrc/source/features.rst @@ -20,49 +20,7 @@ The feed displays only the original tweets (not the retweets) and toots, with: - location (only for Twitter) - numbers of retweets and likes for tweets and boosts and favourites for toots +.. warning:: - -.. danger:: - - | **twootfeed** is developed for personal use. - | Tweets and toots are displayed for the user associated to API keys, it may expose private items if feeds are publicly available. - - -Examples -~~~~~~~~ - -- Search on Twitter - -.. figure:: _images/twitter.png - :alt: Twitter search - :figclass: doc-img - -Results in RSS Feed: - -.. figure:: _images/RSSFeed.png - :alt: RSS Feed - :figclass: doc-img - -Display on FreshRSS, a great free self-hosted aggregator (https://github.com/FreshRSS/FreshRSS): - -.. figure:: _images/FreshRSS.png - :alt: FreshRSS - :figclass: doc-img - -- Search on Mastodon - -.. figure:: _images/mastodon.png - :alt: Mastodon search - :figclass: doc-img - -Results in RSS Feed: - -.. figure:: _images/MastodonRSSFeed.png - :alt: Mastodon Feed - :figclass: doc-img - -Display on FreshRSS: - -.. figure:: _images/MastodonFreshRSS.png - :alt: Mastodon FreshRSS - :figclass: doc-img + | **twootfeed** is developed for a personal use. + | Tweets and toots are displayed for the user associated to the API keys (feeds may contain items with **restricted visibility**). diff --git a/docsrc/source/installation.rst b/docsrc/source/installation.rst index 62b4753..4660ddb 100644 --- a/docsrc/source/installation.rst +++ b/docsrc/source/installation.rst @@ -41,16 +41,45 @@ Installation Update the `feed and app parameters `_. + .. versionadded:: 0.7.0 + + Since **twootfeed** is connected to the user account, feeds may display items with **restricted visibility**. + + A token is now mandatory to start the application and access feeds (minimum length: 25 characters). + + Some examples for token generation: + + > with Python + + .. code-block:: bash + + $ python + Python 3.10.5 (main, Jun 6 2022, 18:49:26) [GCC 12.1.0] on linux + Type "help", "copyright", "credits" or "license" for more information. + >>> import secrets + >>> secrets.token_urlsafe() + 'pgoeS3qOsLHxduzNY_gmn6p5vWZqSzqBgnb_VPupQ7o' + >>> + + > with a linux command line + + .. code-block:: bash + + $ date | sha256sum | base64 | head -c 25; echo + NWU2MzE1ZGM0MmVlZDg5NDNhN + + - The files location can be changed with the following environment variables: -========================= =============================================== =========================================================================================== - variable description app default value -========================= =============================================== =========================================================================================== - `TWOOTFEED_CONFIG_DIR` configuration and credentials files directory **'~/.config/twootfeed/'** - `TWOOTFEED_CONFIG_FILE` config file full path config dir + **'config.yml'** => with default value: **'~/.config/twootfeed/config.yml'** - `TWOOTFEED_LOG` application log file _no default value (log printed on the console)_ -========================= =============================================== =========================================================================================== +=========================== =============================================== =========================================================================================== + variable description app default value +=========================== =============================================== =========================================================================================== + ``TWOOTFEED_CONFIG_DIR`` configuration and credentials files directory **'~/.config/twootfeed/'** + ``TWOOTFEED_CONFIG_FILE`` config file full path config dir + **'config.yml'** => with default value: **'~/.config/twootfeed/config.yml'** + ``TWOOTFEED_LOG`` application log file `no default value (log printed on the console)` + ``TWOOTFEED_SETTINGS`` application settings **'ProductionConfig'** +=========================== =============================================== =========================================================================================== - Start the app @@ -62,33 +91,35 @@ Installation Usage ~~~~~ -The RSS feeds are available on these urls: +The following RSS feeds are available: - for Twitter search: - - http://localhost:8080/tweets/ - - http://localhost:8080/ (*will be deprecated in a next version*) + - http://localhost:8080/tweets/?token=XXX + - http://localhost:8080/?token=XXX (*will be deprecated in a next version*) - for Mastodon search: - keyword as a hashtag: - - http://localhost:8080/toots/ (without the leading #) + - http://localhost:8080/toots/?token=XXX (without the leading #) - query: - - http://localhost:8080/toots/search/ - - http://localhost:8080/toot_search/ (*will be deprecated in a next version*) + - http://localhost:8080/toots/search/?token=XXX + - http://localhost:8080/toot_search/?token=XXX (*will be deprecated in a next version*) - for Mastodon connected user favorites: - - http://localhost:8080/toots/favorites - - http://localhost:8080/toot_favorites (*will be deprecated in a next version*) + - http://localhost:8080/toots/favorites?token=XXX + - http://localhost:8080/toot_favorites?token=XXX (*will be deprecated in a next version*) - for Mastodon connected user bookmarks: - - http://localhost:8080/toots/bookmarks + - http://localhost:8080/toots/bookmarks?token=XXX - for Mastodon connected user home timeline: - - http://localhost:8080/toots/home_timeline \ No newline at end of file + - http://localhost:8080/toots/home_timeline?token=XXX + +where XXX is the token set in configuration. \ No newline at end of file diff --git a/docsrc/source/parameters.rst b/docsrc/source/parameters.rst index 0faefbc..e6df713 100644 --- a/docsrc/source/parameters.rst +++ b/docsrc/source/parameters.rst @@ -25,6 +25,7 @@ Application parameters are stored in ``config.yml`` file: timezone: 'Europe/Paris' text_length_limit: 100 max_items: 20 + token: '' app: host: '0.0.0.0' port: '8080' @@ -54,6 +55,7 @@ Feed * **timezone**: Feed timezone * **text_length_limit:** title length of a Feed item * **max_items**: maximum number of displayed items +* **token**: token for feeds access App ~~~ diff --git a/twootfeed/__init__.py b/twootfeed/__init__.py index 18184ed..6528f47 100644 --- a/twootfeed/__init__.py +++ b/twootfeed/__init__.py @@ -5,7 +5,7 @@ from flask import Flask from twootfeed.mastodon.get_api import get_mastodon_api from twootfeed.twitter.get_api import get_twitter_api -from twootfeed.utils.config import get_config +from twootfeed.utils.config import check_token, get_config log_file = os.getenv('TWOOTFEED_LOG') logging.basicConfig( @@ -28,8 +28,12 @@ def create_app() -> Flask: app = Flask(__name__) + app_config = os.getenv('TWOOTFEED_SETTINGS', 'ProductionConfig') + app.config.from_object(f'twootfeed.config.{app_config}') app_log.setLevel(logging.DEBUG if app.debug else logging.INFO) + check_token(app.config['FEED_CONFIG']) + from .mastodon.routes import mastodon_bp from .twitter.routes import twitter_bp diff --git a/twootfeed/config.example.yml b/twootfeed/config.example.yml index 661562b..1dce97c 100755 --- a/twootfeed/config.example.yml +++ b/twootfeed/config.example.yml @@ -18,6 +18,7 @@ feed: timezone: 'Europe/Paris' text_length_limit: 100 max_items: 20 + token: '' app: host: '0.0.0.0' port: '8080' diff --git a/twootfeed/config.py b/twootfeed/config.py new file mode 100644 index 0000000..7f4201d --- /dev/null +++ b/twootfeed/config.py @@ -0,0 +1,32 @@ +import os +import secrets + +from twootfeed import param + +if os.getenv('TWOOTFEED_SETTINGS') == 'TestingConfig': + param['feed']['token'] = secrets.token_urlsafe() + + +class BaseConfig: + """Base configuration""" + + DEBUG = False + TESTING = False + FEED_CONFIG = param['feed'] + + +class DevelopmentConfig(BaseConfig): + """Development configuration""" + + DEBUG = True + + +class TestingConfig(BaseConfig): + """Testing configuration""" + + DEBUG = True + TESTING = True + + +class ProductionConfig(BaseConfig): + """Production configuration""" diff --git a/twootfeed/mastodon/routes.py b/twootfeed/mastodon/routes.py index 0f24444..d220645 100644 --- a/twootfeed/mastodon/routes.py +++ b/twootfeed/mastodon/routes.py @@ -3,11 +3,13 @@ from flask import Blueprint from twootfeed import mastodon_api, param as mastodon_param from twootfeed.mastodon.generate_toots_feed import generate_xml +from twootfeed.utils.decorator import require_token mastodon_bp = Blueprint('mastodon', __name__) @mastodon_bp.route('/toots/', methods=['GET']) +@require_token def tootfeed_hashtag(hashtag: str) -> Tuple[str, int]: """generate a rss feed from parsed mastodon search""" return generate_xml(mastodon_api, mastodon_param, {'hashtag': hashtag}) @@ -15,6 +17,7 @@ def tootfeed_hashtag(hashtag: str) -> Tuple[str, int]: @mastodon_bp.route('/toots/search/', methods=['GET']) @mastodon_bp.route('/toot_search/', methods=['GET']) +@require_token def tootfeed(query_feed: str) -> Tuple[str, int]: """generate a rss feed from parsed mastodon search""" return generate_xml(mastodon_api, mastodon_param, {'query': query_feed}) @@ -22,6 +25,7 @@ def tootfeed(query_feed: str) -> Tuple[str, int]: @mastodon_bp.route('/toots/favorites', methods=['GET']) @mastodon_bp.route('/toot_favorites', methods=['GET']) +@require_token def toot_favorites_feed() -> Tuple[str, int]: """generate a rss feed authenticated user's favorites""" return generate_xml(mastodon_api, mastodon_param, target='favorites') @@ -29,12 +33,14 @@ def toot_favorites_feed() -> Tuple[str, int]: @mastodon_bp.route('/toots/bookmarks', methods=['GET']) @mastodon_bp.route('/toot_bookmarks', methods=['GET']) +@require_token def toot_bookmarks_feed() -> Tuple[str, int]: """generate a rss feed authenticated user's bookmarks""" return generate_xml(mastodon_api, mastodon_param, target='bookmarks') @mastodon_bp.route('/home_timeline', methods=['GET']) +@require_token def home_timeline() -> Tuple[str, int]: """generate a rss feed authenticated user's bookmarks""" return generate_xml(mastodon_api, mastodon_param, target='home_timeline') diff --git a/twootfeed/tests/conftest.py b/twootfeed/tests/conftest.py index 66a4f16..bb45866 100644 --- a/twootfeed/tests/conftest.py +++ b/twootfeed/tests/conftest.py @@ -1,3 +1,4 @@ +import os from typing import Any, Dict, List from unittest.mock import Mock @@ -8,6 +9,9 @@ from .data import retweet, tweet_1, tweet_no_full_text from .utils import Tweepy +os.environ['FLASK_ENV'] = 'testing' +os.environ['TWOOTFEED_SETTINGS'] = 'TestingConfig' + def mock_api(tweets: List[Dict]) -> Mock: mock_response = Mock() @@ -15,8 +19,7 @@ def mock_api(tweets: List[Dict]) -> Mock: return mock_response -@pytest.fixture -def app(monkeypatch: pytest.MonkeyPatch, tmpdir: Any) -> Flask: +def get_app(monkeypatch: pytest.MonkeyPatch, tmpdir: Any) -> Flask: test_dir = str(tmpdir) monkeypatch.setenv('TWOOTFEED_CONFIG', test_dir) monkeypatch.setenv('TWOOTFEED_CONFIG_FILE', test_dir + '/config.yml') @@ -24,6 +27,25 @@ def app(monkeypatch: pytest.MonkeyPatch, tmpdir: Any) -> Flask: return app +@pytest.fixture +def app(monkeypatch: pytest.MonkeyPatch, tmpdir: Any) -> Flask: + return get_app(monkeypatch, tmpdir) + + +@pytest.fixture +def app_missing_token(monkeypatch: pytest.MonkeyPatch, tmpdir: Any) -> Flask: + app = get_app(monkeypatch, tmpdir) + app.config['TOKEN'] = '' + return app + + +@pytest.fixture +def app_invalid_token(monkeypatch: pytest.MonkeyPatch, tmpdir: Any) -> Flask: + app = get_app(monkeypatch, tmpdir) + app.config['TOKEN'] = 'invalid_token' + return app + + @pytest.fixture() def fake_tweepy_ok() -> Mock: return mock_api(tweets=[tweet_1]) diff --git a/twootfeed/tests/data.py b/twootfeed/tests/data.py index 55483b1..faba87b 100644 --- a/twootfeed/tests/data.py +++ b/twootfeed/tests/data.py @@ -1,12 +1,14 @@ +import secrets from copy import deepcopy from datetime import datetime from typing import Dict import pytz +TEST_TOKEN = secrets.token_urlsafe() max_items = 20 -init_param = { +init_param: Dict = { 'twitter': { 'consumerKey': '', 'consumerSecret': '', @@ -29,6 +31,7 @@ 'timezone': 'Europe/Paris', 'text_length_limit': 100, 'max_items': max_items, + 'token': TEST_TOKEN, }, 'app': {'host': '0.0.0.0', 'port': '8080'}, } diff --git a/twootfeed/tests/test_config.py b/twootfeed/tests/test_config.py new file mode 100644 index 0000000..450cca5 --- /dev/null +++ b/twootfeed/tests/test_config.py @@ -0,0 +1,46 @@ +import string +from random import choice +from typing import Dict + +import pytest +from twootfeed.utils.config import check_token +from twootfeed.utils.exceptions import ( + InvalidTokenException, + MissingTokenException, +) + + +class TestCheckConfig: + @pytest.mark.parametrize( + 'input_feed_config', [{}, {'token': ''}, {'token': None}] + ) + def test_it_raises_exception_when_token_is_missing( + self, input_feed_config: Dict + ) -> None: + with pytest.raises( + MissingTokenException, + match="token is missing in configuration", + ): + check_token(feed_config=input_feed_config) + + def test_it_raises_exception_when_token_is_invalid(self) -> None: + with pytest.raises( + InvalidTokenException, + match="token is too short", + ): + check_token( + feed_config={ + 'token': ''.join( + choice(string.ascii_letters) for _ in range(24) + ) + } + ) + + def test_it_does_not_raise_exception_when_token_is_valid(self) -> None: + check_token( + feed_config={ + 'token': ''.join( + choice(string.ascii_letters) for _ in range(25) + ) + } + ) diff --git a/twootfeed/tests/test_get_api.py b/twootfeed/tests/test_get_api.py index a613693..6eb4bc6 100644 --- a/twootfeed/tests/test_get_api.py +++ b/twootfeed/tests/test_get_api.py @@ -15,6 +15,7 @@ def test_config_file( test_dir = str(tmpdir) monkeypatch.setenv('TWOOTFEED_CONFIG', test_dir) monkeypatch.setenv('TWOOTFEED_CONFIG_FILE', test_dir + '/config.yml') + init_param['feed']['token'] = '' assert get_config() == init_param @@ -23,7 +24,7 @@ def test_config_no_config_file( ) -> None: test_dir = str(tmpdir) monkeypatch.setenv('TWOOTFEED_CONFIG', test_dir) - assert isfile(get_config_file(None)) # type: ignore + assert isfile(get_config_file('')) def test_init_config( diff --git a/twootfeed/tests/test_routes.py b/twootfeed/tests/test_routes.py new file mode 100644 index 0000000..7443314 --- /dev/null +++ b/twootfeed/tests/test_routes.py @@ -0,0 +1,130 @@ +from uuid import uuid4 + +import pytest +from flask import Flask + +MASTODON_ENDPOINTS = [ + '/toots/favorites', + '/toot_favorites', + '/toots/bookmarks', + '/toot_bookmarks', + '/home_timeline', +] + + +class TestTwitterRoutesToken: + endpoint = '/{keyword}' + + def test_it_returns_401_when_token_is_missing( + self, app_missing_token: Flask + ) -> None: + client = app_missing_token.test_client() + + response = client.get(self.endpoint.format(keyword=uuid4().hex)) + + assert response.status_code == 401 + data = response.data.decode() + assert data == 'missing token' + + def test_it_returns_403_when_token_is_invalid( + self, app_invalid_token: Flask + ) -> None: + client = app_invalid_token.test_client() + + response = client.get( + f'{self.endpoint.format(keyword=uuid4().hex)}?token=invalid' + ) + + assert response.status_code == 403 + data = response.data.decode() + assert data == 'invalid token' + + +class TestTwitterTweetsRouteToken(TestTwitterRoutesToken): + endpoint = '/tweets/{keyword}' + + +class TestMastodonHashtagRouteToken: + endpoint = '/toots/{hashtag}' + + def test_it_returns_401_when_token_is_missing( + self, app_missing_token: Flask + ) -> None: + client = app_missing_token.test_client() + + response = client.get(self.endpoint.format(hashtag=uuid4().hex)) + + assert response.status_code == 401 + data = response.data.decode() + assert data == 'missing token' + + def test_it_returns_403_when_token_is_invalid( + self, app_invalid_token: Flask + ) -> None: + client = app_invalid_token.test_client() + + response = client.get( + f'{self.endpoint.format(hashtag=uuid4().hex)}?token=invalid' + ) + + assert response.status_code == 403 + data = response.data.decode() + assert data == 'invalid token' + + +class TestMastodonSearchRouteToken: + endpoint = '/toots/search/{query_feed}' + + def test_it_returns_401_when_token_is_missing( + self, app_missing_token: Flask + ) -> None: + client = app_missing_token.test_client() + + response = client.get(self.endpoint.format(query_feed=uuid4().hex)) + + assert response.status_code == 401 + data = response.data.decode() + assert data == 'missing token' + + def test_it_returns_403_when_token_is_invalid( + self, app_invalid_token: Flask + ) -> None: + client = app_invalid_token.test_client() + + response = client.get( + f'{self.endpoint.format(query_feed=uuid4().hex)}?token=invalid' + ) + + assert response.status_code == 403 + data = response.data.decode() + assert data == 'invalid token' + + +class TestMastodonSearchAlternativeRouteToken(TestMastodonSearchRouteToken): + endpoint = '/toot_search/{query_feed}' + + +class TestMastodonRoutesToken: + @pytest.mark.parametrize('input_endpoint', MASTODON_ENDPOINTS) + def test_it_returns_401_when_token_is_missing( + self, app_missing_token: Flask, input_endpoint: str + ) -> None: + client = app_missing_token.test_client() + + response = client.get(input_endpoint) + + assert response.status_code == 401 + data = response.data.decode() + assert data == 'missing token' + + @pytest.mark.parametrize('input_endpoint', MASTODON_ENDPOINTS) + def test_it_returns_403_when_token_is_invalid( + self, app_invalid_token: Flask, input_endpoint: str + ) -> None: + client = app_invalid_token.test_client() + + response = client.get(f'{input_endpoint}?token=invalid') + + assert response.status_code == 403 + data = response.data.decode() + assert data == 'invalid token' diff --git a/twootfeed/twitter/routes.py b/twootfeed/twitter/routes.py index 469da92..3e293b7 100644 --- a/twootfeed/twitter/routes.py +++ b/twootfeed/twitter/routes.py @@ -3,12 +3,14 @@ from flask import Blueprint from twootfeed import param, twitter_api from twootfeed.twitter.generate_tweets_feed import generate_xml +from twootfeed.utils.decorator import require_token twitter_bp = Blueprint('twitter', __name__) @twitter_bp.route('/', methods=['GET']) @twitter_bp.route('/tweets/', methods=['GET']) +@require_token def tweetfeed(query_feed: str) -> Tuple[str, int]: """generate a rss feed from parsed twitter search""" return generate_xml(twitter_api, query_feed, param) diff --git a/twootfeed/utils/config.py b/twootfeed/utils/config.py index d228a7d..126f296 100644 --- a/twootfeed/utils/config.py +++ b/twootfeed/utils/config.py @@ -1,10 +1,12 @@ import os from os.path import abspath, dirname from shutil import copyfile -from typing import Any +from typing import Any, Dict import yaml +from .exceptions import InvalidTokenException, MissingTokenException + DEFAULT_DIRECTORY = os.path.expanduser('~/.config/twootfeed/') TWOOTFEED_CONFIG_DIR = os.getenv('TWOOTFEED_CONFIG_DIR', '') default_directory: str = ( @@ -33,3 +35,12 @@ def get_config() -> Any: def init_config() -> bool: return get_config() is not None + + +def check_token(feed_config: Dict) -> None: + token = feed_config.get('token') + if not token: + raise MissingTokenException("token is missing in configuration") + + if len(token) < 25: + raise InvalidTokenException("token is too short") diff --git a/twootfeed/utils/decorator.py b/twootfeed/utils/decorator.py new file mode 100644 index 0000000..6a4a133 --- /dev/null +++ b/twootfeed/utils/decorator.py @@ -0,0 +1,21 @@ +from functools import wraps +from typing import Any, Callable, Tuple, Union + +from flask import request +from twootfeed import param + + +def require_token(f: Callable) -> Callable: + @wraps(f) + def decorated_function( + *args: Any, **kwargs: Any + ) -> Union[Callable, Tuple[str, int]]: + token = request.args.get('token') + if not token: + return 'missing token', 401 + if token != param['feed']['token']: + return 'invalid token', 403 + + return f(*args, **kwargs) + + return decorated_function diff --git a/twootfeed/utils/exceptions.py b/twootfeed/utils/exceptions.py new file mode 100644 index 0000000..598ee64 --- /dev/null +++ b/twootfeed/utils/exceptions.py @@ -0,0 +1,6 @@ +class MissingTokenException(Exception): + ... + + +class InvalidTokenException(Exception): + ...