diff --git a/.gitignore b/.gitignore index f5189cf..90063a8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ docs/build/ __pycache__ feedlist +data *.swp *.pyc *.db @@ -8,3 +9,5 @@ feedlist *.txt *.bck *.png +*.yml +.env diff --git a/AUTHORS b/AUTHORS index e0ce7ee..ec96c0b 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,4 +1,5 @@ Antoine Beaupré -Carl Chenet +Carl Chenet Alexis Metaireau The Dod +Matthias Henze diff --git a/CHANGELOG b/CHANGELOG index bc29821..55a14f7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,52 @@ +## [0.17] - 2021-03-26 +### Changed +- fix install bug while installing from sources +- bump changelog + +## [0.16] - 2020-12-09 +### Added +- scripts/register_feed2toot_app: --client-credentials-file option to change the filename in which the client credentials are stored +- scripts/register_feed2toot_app: --user-credentials-file option to change the filename in which the client credentials are stored +- scripts/register_feed2toot_app: --name to change the Mastodon app name + +## [0.15] - 2020-12-07 +### Changed +- maintenance version +- fix bug while using uri_list parameter alone following feedparser upgrade +- update copyrigth +- update author email + + +## [0.14] - 2019-12-31 +### Added +- support for line breaks in toots +- new configuration parameter toot_max_len to define the maximum length of a toot +- new configuration parameter no_tags_in_toot to stop hash tags to be added in toots + +## [0.13] - 2019-12-27 +### Added +- new cli option --ignore-ssl and parameter ignore_ssl to ignore ssl error in the rss feeds + +### Changed +- delete the lock file when using --rss-sections cli option + +## [0.12] - 2019-08-27 +### Changed +- simplify code, mostly in the main function +- fix a bug when title_pattern is used and *_pattern_case_sensitive is not defined + + +## [0.11] - 2019-08-24 +### Added +- MAJOR CHANGE: command line options --lockfile to define a lock file +- MAJOR CHANGE: command line options --lock-timeout to remove this lock file automatically +- MAJOR CHANGE: lock section in configuration with lock_file andd lock_time parameters + +## [0.10] - 2018-09-22 +### Added +- new syntax for the toot parameter of [rss] section. Use {summary:.100} to cut the rss field summary after the first 100 characters. Contributed by Matthias Henze +- add the addtags parameter of the [rss] section. Contributed by Matthias Henze + ## [0.9] - 2018-06-07 ### Added - remove html characters from toots. Contributed by Matthew Lorentz and Simounet @@ -12,14 +61,14 @@ ## [0.6] - 2017-08-02 ### Added -- define a name for a feed, accessible with {feedname}. Contributed by Alexis Metaireau. -- switch the toot visibility. Contributed by The Dod. -- new accept_bozo_exceptions option to allow malformed rss feeds. Contributed by Alexis Metaireau. +- define a name for a feed, accessible with {feedname}, contributed by Alexis Metaireau +- switch the toot visibility. Contributed by The Dod +- new accept_bozo_exceptions option to allow malformed rss feeds, contributed by Alexis Metaireau ### Changed - configuration parser was split into much smaller chunks -- remove useless imports and coding style. Contributed by Alexis Metaireau. -- rephrasing and reformatting of the script register_feed2toot_app. Contributed by Bastien Guerry. +- remove useless imports and coding style, contributed by Alexis Metaireau +- rephrasing and reformatting of the script register_feed2toot_app, contributed by Bastien Guerry ## [0.5] - 2017-05-05 ### Added diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..c6ca6e7 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +FROM python:3.7-bullseye + +ADD . /app +WORKDIR /app + +RUN python3 setup.py install; \ + chmod +x docker_entrypoint.sh; \ + mkdir -p /data /root/.config; \ + rm -rf docke.yml + +WORKDIR /data + +CMD ["/app/docker_entrypoint.sh"] diff --git a/README.md b/README.md index 366167c..dc85036 100644 --- a/README.md +++ b/README.md @@ -1,65 +1,46 @@ +# Feed2Toot Docker + +Feed2Toot Docker is a fork of [chaica/feed2toot](https://gitlab.com/chaica/feed2toot) that allows for simple deployment over Docker. In the future I'll try to add other useful features. + +### Usage + +With Docker Compose: +```yaml +version: '3' + +services: + feed2toot: + image: gitea.massivebox.net/massivebox/feed2toot-docker:latest + environment: + - USERNAME=sampleusername@sampleinstance.url + - PASSWORD=samplepassword + - INSTANCE=https://sampleinstance.url + restart: always + volumes: + - ./data:/data +``` +Insert your full username (including the part with your homeserver's address), password and instance URL where required. +You also need to create a working `feed2toot.ini` in the `./data` folder. See the [upstream docs](`https://feed2toot.readthedocs.io/en/latest/configure.html#create-feed2toot-configuration`) for the guide. + +After the first successful run, you can remove the entire `environment` block if you want, however, if you delete and re-create the container, you will need to put it back. + +### Support + +Don't ask the upstream developers for help, if something is broken it's more likely that it is my fault. +You can get support by opening an [issue](https://gitea.massivebox.net/massivebox/feed2toot-docker/issues), or by [contacting me](https://massivebox.net/contact.html), or in the [Matrix support room](https://matrix.to/#/#support:massivebox.net). + ### Feed2toot -Feed2toot automatically parses rss feeds, identifies new posts and posts them on the [Mastodon](https://mastodon.social) social network. -For the full documentation, [read it online](https://feed2toot.readthedocs.org/en/latest/). - -If you would like, you can [support the development of this project on Liberapay](https://liberapay.com/carlchenet/). -Alternatively you can donate cryptocurrencies: +The following links and addresses are the upstream developers'. I don't want donations for this project, but I encourage you to donate to them. +- [Liberapay](https://liberapay.com/carlchenet/) - BTC: 1AW12Zw93rx4NzWn5evcG7RNNEM2RSLmAC - XMR: 43GGv8KzVhxehv832FWPTF7FSVuWjuBarFd17QP163uxMaFyoqwmDf1aiRtS5jWgCiRsi73yqedNJJ6V1La2joznKHGAhDi -### Quick Install - -* Install Feed2toot from PyPI - - # pip3 install feed2toot - -* Install Feed2toot from sources - *(see the installation guide for full details) - [Installation Guide](http://feed2toot.readthedocs.org/en/latest/install.html)* - - - # tar zxvf feed2toot-0.9.tar.gz - # cd feed2toot - # python3 setup.py install - # # or - # python3 setup.py install --install-scripts=/usr/bin - -### Create the authorization for the Feed2toot app - -* Just launch the following command:: - - $ register_feed2toot_app - -### Use Feed2toot - -* Create or modify feed2toot.ini file in order to configure feed2toot: - - [mastodon] - instance_url=https://mastodon.social - user_credentials=feed2toot_usercred.txt - client_credentials=feed2toot_clientcred.txt - ; Default visibility is public, but you can override it: - ; toot_visibility=unlisted - - [cache] - cachefile=cache.db - - [rss] - uri=https://www.journalduhacker.net/rss - toot={title} {link} - - [hashtaglist] - several_words_hashtags_list=hashtags.txt - -* Launch Feed2toot - - $ feed2toot -c /path/to/feed2toot.ini - ### Authors -* Carl Chenet +* MassiveBox +* Carl Chenet * Antoine Beaupré * First developed by Todd Eddy diff --git a/docker_entrypoint.sh b/docker_entrypoint.sh new file mode 100755 index 0000000..7360431 --- /dev/null +++ b/docker_entrypoint.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +cd /data + +if [[ ! -f "feed2toot_clientcred.txt" || ! -f "feed2toot_usercred.txt" ]]; then + register_feed2toot_app --instance $INSTANCE --username $USERNAME --password $PASSWORD +fi + +while :; do + feed2toot -c ./feed2toot.ini + sleep 5m +done diff --git a/docs/source/authors.rst b/docs/source/authors.rst index 68bc3db..13b4749 100644 --- a/docs/source/authors.rst +++ b/docs/source/authors.rst @@ -1,4 +1,4 @@ Authors ======= -Carl Chenet +Carl Chenet diff --git a/docs/source/conf.py b/docs/source/conf.py index 4cd8695..54b10dc 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -31,6 +31,7 @@ import os # ones. extensions = [ 'sphinx.ext.autodoc', + 'sphinx.ext.autosectionlabel' ] # Add any paths that contain templates here, relative to this directory. @@ -47,16 +48,16 @@ master_doc = 'index' # General information about the project. project = 'feed2toot' -copyright = '2017, Carl Chenet ' +copyright = '2015-2021, Carl Chenet ' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = '0.9' +version = '0.17' # The full version, including alpha/beta/rc tags. -release = '0.9' +release = '0.17' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -200,7 +201,7 @@ latex_elements = { # author, documentclass [howto, manual, or own class]). latex_documents = [ ('index', 'feed2toot.tex', 'feed2toot Documentation', - 'Carl Chenet \\textless{}chaica@ohmytux.com.org\\textgreater{}', 'manual'), + 'Carl Chenet \\textless{}carl.chenet@ohmytux.com.org\\textgreater{}', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of @@ -230,7 +231,7 @@ latex_documents = [ # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'feed2toot', 'feed2toot Documentation', - ['Carl Chenet '], 1) + ['Carl Chenet '], 1) ] # If true, show URL addresses after external links. @@ -244,7 +245,7 @@ man_pages = [ # dir menu entry, description, category) texinfo_documents = [ ('index', 'feed2toot', 'feed2toot Documentation', - 'Carl Chenet ', 'feed2toot', 'One line description of project.', + 'Carl Chenet ', 'feed2toot', 'One line description of project.', 'Miscellaneous'), ] diff --git a/docs/source/configure.rst b/docs/source/configure.rst index 1953f4d..a16fd82 100644 --- a/docs/source/configure.rst +++ b/docs/source/configure.rst @@ -1,23 +1,34 @@ Configure Feed2toot =================== +Create credentials for Mastodon +------------------------------- As a prerequisite to use Feed2toot, you need to authorize a Mastodon app for your account. Just use the script register_feed2toot_app to register the feed2toot app for your account.:: $ ./register_feed2toot_app - - This app generates Mastodon app credentials needed by Feed2toot. - feed2toot_clientcred.txt and feed2toot_usercred.txt will be written in the current dir /home/chaica/progra/python/feed2toot. - One connection is initiated to create the app. + + This script generates the Mastodon application credentials for Feed2toot. + feed2toot_clientcred.txt and feed2toot_usercred.txt will be written + in the current directory: /home/me/feed2toot. + WARNING: previous files with the same names will be overwritten. + + A connection is also initiated to create the application. Your password is *not* stored. + + Mastodon instance URL (defaults to https://mastodon.social): https://framapiaf.org + Mastodon login: toto@titi.com + Mastodon password: + + The app feed2toot was added to your Preferences=>Accounts=>Authorized apps page. + The file feed2toot_clientcred.txt and feed2toot_usercred.txt were created in the current directory. - Mastodon instance url (defaults to https://mastodon.social): - Mastodon login:chaica@ohmytux.com - Mastodon password: - The feed2toot app was added to your preferences=>authorized apps page +As described above, two files were created. See the :ref:`Use register_feed2toot_app` section for more options for register_feed2toot_app. -As described above, two files were created. You'll need them in the feed2toot configuration. +Create Feed2toot configuration +------------------------------ +After using register_feed2toot_app, you'll need the credentials in the feed2toot configuration. In order to configure Feed2toot, you need to create a feed2toot.ini file (or any name you prefer, finishing with the extension .ini) with the following parameters:: @@ -33,16 +44,23 @@ In order to configure Feed2toot, you need to create a feed2toot.ini file (or any cachefile=/var/lib/feed2toot/feed2toot.db cache_limit=10000 + [lock] + lock_file=/var/lock/feed2toot.lock + lock_timeout=3600 + [rss] uri=https://www.journalduhacker.net/rss uri_list=/etc/feed2toot//rsslist.txt toot={title} {link} + ; toot_max_len=125 title_pattern=Open Source title_pattern_case_sensitive=true no_uri_pattern_no_global_pattern=true + ; ignore_ssl=false [hashtaglist] several_words_hashtags_list=/etc/feed2toot/hashtags.txt + ; no_tags_in_toot=false [feedparser] accept_bozo_exceptions=true @@ -67,18 +85,27 @@ For the [cache] section: - cachefile: the path to the cache file storing ids of already tooted links. Absolute path is mandatory. This file should always use the .db extension. - cache_limit: length of the cache queue. defaults to 100. +For the [lock] section (starting from version 0.11): + +- lock_file: lock to stop any other feed2toot instance to run at the same time. Default is ~/.config/feed2toot.lock +- lock_timeout: automatically remove the lock if the datetime in the lock file is greater than n seconds. Default is 3600 seconds. + For the [rss] section: - uri: the url of the rss feed to parse - uri_list: a path to a file with several adresses of rss feeds, one by line. Absolute path is mandatory. -- toot: format of the toot you want to post. It should use existing entries of the RSS fields like {title} or {link}. Launch it with this field empty to display all available entries. +- toot: format of the toot you want to post. It should use existing entries of the RSS fields like {title} or {link}. Launch it with this field empty to display all available entries. If you want to shorten the size of a field, you can use the syntax {summary:.100} to cut the field "summary" of the rss feed after the first 100 characters (starting from version 0.10). To add new lines you can use \n (starting from version 0.14) +- toot_max_len: the max length of a toot can be defined here. If the toot size is longer, the toot is truncated and "..." added at the end. Defaults is 500 characters. - {one field of the rss feed}_pattern: takes a string representing a pattern to match for a specified field of each rss entry of the rss feed, like title_pattern or summary_pattern. - {one field of the rss feed}_pattern_case_sensitive: either the pattern matching for the specified field should be case sensitive or not. Default to true if not specified. - no_uri_pattern_no_global_pattern: don't apply global pattern (see above) when no pattern-by-uri is defined in the uri_list. Allows to get all entries of a rss in the uri_list because no pattern is defined so we match them all. Defaults to false, meaning the global patterns will be tried on every rss in the uri_list NOT HAVING specific patterns and so ONLY entries from the specific uri in the uri_list matching the global patterns will be considered. +- addtags: add the tags from the rss feed at the end of the toot. Defaults to true. +- ignore_ssl: when the uri or uri_list contains an https url with an invalid certificate (e.g an expired one), feed2toot will be unable to get rss content. This option allows to bypass the ssl security to catch the rss content. Defaults to false. For the [hashtaglist] section: - several_words_hashtags_list: a path to the file containing hashtags in two or more words. Absolute path is mandatory. By default Feed2toot adds a # before every words of a hashtag. See documentation below for an example of this file. +- no_tags_in_toot: stop hash tags to be added at the toot. Defaults to false. for the [feedparser] section: @@ -127,8 +154,8 @@ Match specific patterns of rss feeds in the uri_list files ---------------------------------------------------------- You can use specific pattern matching for uri in the uri_list file to filter some of the rss entries of a rss feed. Lets modify the previous file:: -https://www.journalduhacker.net/rss|title|hacker,psql -https://carlchenet.com/feed|title|gitlab + https://www.journalduhacker.net/rss|title|hacker,psql + https://carlchenet.com/feed|title|gitlab Each line of this file starts with an uri, followed by a pipe (|), followed by the name of the available section to parse (see below), again followed by a pipe (|), followed by patterns, each pattern being separated from the other one by a semi-colon (,). @@ -150,9 +177,3 @@ In you rsslist.txt, just don't give anything else than the needed feed url to ge The last line of the file above only has the url of a rss feed. All entries from this feed will be tooted. -How to display available sections of the rss feed -================================================= -Feed2toot offers the **--rss-sections** command line option to display the available section of the rss feed and exits:: - - $ feed2toot --rss-sections -c feed2toot.ini - The following sections are available in this RSS feed: ['title', 'comments', 'authors', 'link', 'author', 'summary', 'links', 'tags', id', 'author_detail', 'published']. diff --git a/docs/source/install.rst b/docs/source/install.rst index cab5edf..3e885d7 100644 --- a/docs/source/install.rst +++ b/docs/source/install.rst @@ -25,7 +25,7 @@ Alternatively, Setuptools may be installed to a user-local path:: * Untar the tarball and go to the source directory with the following commands:: - $ tar zxvf feed2toot-0.9.tar.gz + $ tar zxvf feed2toot-0.17.tar.gz $ cd feed2toot * Next, to install Feed2toot on your computer, type the following command with the root user:: diff --git a/docs/source/use.rst b/docs/source/use.rst index 0066532..d757345 100644 --- a/docs/source/use.rst +++ b/docs/source/use.rst @@ -5,8 +5,8 @@ After the configuration of Feed2toot, just launch the following command:: $ feed2toot -c /path/to/feed2toot.ini Run Feed2toot on a regular basis -================================= -Feed2toot should be launche on a regular basis in order to efficiently send your new RSS entries to Mastodon. It is quite easy to achieve with adding a line to your user crontab, as described below:: +--------------------------------- +Feed2toot should be launched on a regular basis in order to efficiently send your new RSS entries to Mastodon. It is quite easy to achieve by adding a line to your user crontab, as described below:: @hourly feed2toot -c /path/to/feed2toot.ini @@ -15,19 +15,19 @@ will execute feed2toot every hour. Or without the syntactic sugar in the global 0 * * * * johndoe feed2toot -c /path/to/feed2toot.ini Test option -=========== +----------- In order to know what's going to be sent to Mastodon without actually doing it, use the **--dry-run** option:: $ feed2toot --dry-run -c /path/to/feed2toot.ini Debug option -============ +------------ In order to increase the verbosity of what's Feed2toot is doing, use the **--debug** option followed by the level of verbosity see [the the available different levels](https://docs.python.org/3/library/logging.html):: $ feed2toot --debug -c /path/to/feed2toot.ini -Populate the cache file without posting tweets -============================================== +Populate the cache file without posting toots +--------------------------------------------- Starting from 0.8, Feed2toot offers the **--populate-cache** command line option to populate the cache file without posting to Mastodon:: $ feed2toot --populate-cache -c feed2toot.ini @@ -43,14 +43,55 @@ Starting from 0.8, Feed2toot offers the **--populate-cache** command line option populating RSS entry https://www.journalduhacker.net/s/lqswmz How to display available sections of the rss feed -================================================= +------------------------------------------------- Starting from 0.8, Feed2toot offers the **--rss-sections** command line option to display the available section of the rss feed and exits:: $ feed2toot --rss-sections -c feed2toot.ini The following sections are available in this RSS feed: ['title', 'comments', 'authors', 'link', 'author', 'summary', 'links', 'tags', id', 'author_detail', 'published']. Using syslog -============ +------------ Feed2toot is able to send its log to syslog. You can use it with the following command:: $ feed2toot --syslog=WARN -c /path/to/feed2toot.ini + +Limit number of rss entries published at each execution +------------------------------------------------------- +If you want to limit the number of rss entries published at each execution, you can use the --limit CLI option. + + $ feed2toot --limit 5 -c /path/to/feed2toot.ini + +The number of posts to Mastodon will be at 5 posts top with this CLI option. + +Use register_feed2toot_app +========================== +You need a Mastodon app associated to a user on the Mastodon instance. The script register_feed2toot_app will create an app for Feed2toot and upload it on the specified Mastodon instance. + +Primary usage :: + + $ register_feed2toot_app + +Possible CLI options: + +- use the **--client-credentials-file** option to change the filename in which the client credentials are stored (defaults to feed2toot_clientcred.txt) +- use the **--user-credentials-file** option to change the filename in which the user credentials are stored (defaults to feed2toot_usercred.txt) +- use the **--name** to change the Mastodon app name (defaults to feed2toot) + +Example with full options and full output:: + + $ ./register_feed2toot_app --user-credentials-file f2tusercreds.txt --client-credentials-file f2tclientcreds.txt --name f2t + + This script generates the Mastodon application credentials for Feed2toot. + f2tclientcreds.txt and f2tusercreds.txt will be written + in the current directory: /home/me/feed2toot/scripts. + WARNING: previous files with the same names will be overwritten. + + A connection is also initiated to create the application. + Your password is *not* stored. + + Mastodon instance URL (defaults to https://mastodon.social): https://framapiaf.org + Mastodon login: toto@titi.com + Mastodon password: + + The app f2t was added to your preferences=>authorized apps page. + The file f2tclientcreds.txt and f2tusercreds.txt were created in the current directory. diff --git a/feed2toot.py b/feed2toot.py index d7ce8ba..a53bf2a 100755 --- a/feed2toot.py +++ b/feed2toot.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # vim:ts=4:sw=4:ft=python:fileencoding=utf-8 -# Copyright © 2015-2017 Carl Chenet +# Copyright © 2015-2021 Carl Chenet # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or diff --git a/feed2toot/__init__.py b/feed2toot/__init__.py index d19bf9f..979c3de 100644 --- a/feed2toot/__init__.py +++ b/feed2toot/__init__.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # vim:ts=4:sw=4:ft=python:fileencoding=utf-8 -# Copyright © 2017 Carl Chenet +# Copyright © 2015-2021 Carl Chenet # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or diff --git a/feed2toot/addtags.py b/feed2toot/addtags.py index 1859e07..cca0954 100644 --- a/feed2toot/addtags.py +++ b/feed2toot/addtags.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # vim:ts=4:sw=4:ft=python:fileencoding=utf-8 -# Copyright © 2015-2017 Carl Chenet +# Copyright © 2015-2021 Carl Chenet # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or diff --git a/feed2toot/cliparse.py b/feed2toot/cliparse.py index f25dcf7..e8d8739 100644 --- a/feed2toot/cliparse.py +++ b/feed2toot/cliparse.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright © 2015-2017 Carl Chenet +# Copyright © 2015-2021 Carl Chenet # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or @@ -24,7 +24,7 @@ import os import os.path import sys -__version__ = '0.9' +__version__ = '0.17' class CliParse: '''CliParse class''' @@ -34,7 +34,7 @@ class CliParse: def main(self): '''main of CliParse class''' - feed2tootepilog = 'For more information: https://feed2toot.readhthedocs.org' + feed2tootepilog = 'For more information: https://feed2toot.readthedocs.io' feed2tootdescription = 'Take rss feed and send it to Mastodon' parser = ArgumentParser(prog='feed2toot', description=feed2tootdescription, @@ -50,10 +50,19 @@ class CliParse: parser.add_argument('-a', '--all', action='store_true', default=False, dest='all', help='tweet all RSS items, regardless of cache') + parser.add_argument('--ignore-ssl', action='store_true', default=False, + dest='ignore_ssl', + help='ignore ssl errors while fetching rss feeds') parser.add_argument('-l', '--limit', dest='limit', default=10, type=int, help='tweet only LIMIT items (default: %(default)s)') + parser.add_argument('-t', '--lock-timeout', dest='locktimeout', default=3600, type=int, + help='lock timeout in seconds after which feed2toot can removes the lock itself') parser.add_argument('--cachefile', dest='cachefile', help='location of the cache file (default: %(default)s)') + parser.add_argument('--lockfile', dest='lockfile', + default=os.path.join(os.getenv('XDG_CONFIG_HOME', '~/.config'), + 'feed2toot.lock'), + help='location of the lock file (default: %(default)s)') parser.add_argument('-n', '--dry-run', dest='dryrun', action='store_true', default=False, help='Do not actually post tweets') diff --git a/feed2toot/confparse.py b/feed2toot/confparse.py index ae5a723..54010a2 100644 --- a/feed2toot/confparse.py +++ b/feed2toot/confparse.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright © 2015-2017 Carl Chenet +# Copyright © 2015-2021 Carl Chenet # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or @@ -30,13 +30,18 @@ import feedparser # feed2toot library imports from feed2toot.confparsers.cache import parsecache from feed2toot.confparsers.hashtaglist import parsehashtaglist +from feed2toot.confparsers.hashtags.nohashtags import parsenotagsintoot from feed2toot.confparsers.feedparser import parsefeedparser +from feed2toot.confparsers.lock import parselock from feed2toot.confparsers.media import parsemedia from feed2toot.confparsers.plugins import parseplugins +from feed2toot.confparsers.rss.ignoressl import parseignoressl from feed2toot.confparsers.rss.pattern import parsepattern from feed2toot.confparsers.rss.toot import parsetoot from feed2toot.confparsers.rss.uri import parseuri from feed2toot.confparsers.rss.urilist import parseurilist +from feed2toot.confparsers.rss.addtags import parseaddtags +from feed2toot.confparsers.rss.tootmaxlen import parsetootmaxlen class ConfParse: '''ConfParse class''' @@ -64,27 +69,45 @@ class ConfParse: # the rss section ########################### self.tweetformat = parsetoot(config) + options['tootmaxlen'] = parsetootmaxlen(config) ################################################# # pattern and patter_case_sensitive format option ################################################# options['patterns'], options['patternscasesensitive'] = parsepattern(config) + ################################################# + # lock file options + ################################################# + options['lockfile'], options['locktimeout'] = parselock(self.clioptions.lockfile, self.clioptions.locktimeout, config) + ############################### + # addtags option, default: True + ############################### + options['addtags'] = parseaddtags(config) + ################### + # ignore_ssl option + ################### + ignore_ssl = parseignoressl(config, self.clioptions.ignore_ssl) ################# # uri_list option ################# feeds = [] - feeds = parseurilist(config, accept_bozo_exceptions) + feeds = parseurilist(config, accept_bozo_exceptions, ignore_ssl) ############ # uri option ############ - options['rss_uri'], feed, feedname, options['nopatternurinoglobalpattern'] = parseuri(config, self.clioptions.rss_uri, feeds) + if config.has_option('rss', 'uri') or self.clioptions.rss_uri: + options['rss_uri'], feed, feedname, options['nopatternurinoglobalpattern'] = parseuri(config, self.clioptions.rss_uri, feeds, ignore_ssl) + else: + if config.has_option('rss', 'no_uri_pattern_no_global_pattern'): + options['nopatternurinoglobalpattern'] = config.getboolean('rss', 'no_uri_pattern_no_global_pattern') ########################### # the cache section ########################### options['cachefile'], options['cache_limit'] = parsecache(self.clioptions.cachefile, config) ########################### - # the hashtag section + # the hashtaglist section ########################### options['hashtaglist'] = parsehashtaglist(self.clioptions.hashtaglist, config) + options['notagsintoot'] = parsenotagsintoot(config) ########################### # the media section ########################### diff --git a/feed2toot/confparsers/__init__.py b/feed2toot/confparsers/__init__.py index d19bf9f..979c3de 100644 --- a/feed2toot/confparsers/__init__.py +++ b/feed2toot/confparsers/__init__.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # vim:ts=4:sw=4:ft=python:fileencoding=utf-8 -# Copyright © 2017 Carl Chenet +# Copyright © 2015-2021 Carl Chenet # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or diff --git a/feed2toot/confparsers/cache.py b/feed2toot/confparsers/cache.py index 5490b1a..79c013d 100644 --- a/feed2toot/confparsers/cache.py +++ b/feed2toot/confparsers/cache.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright © 2015-2017 Carl Chenet +# Copyright © 2015-2021 Carl Chenet # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or diff --git a/feed2toot/confparsers/feedparser.py b/feed2toot/confparsers/feedparser.py index e3c192a..cf72889 100644 --- a/feed2toot/confparsers/feedparser.py +++ b/feed2toot/confparsers/feedparser.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright © 2015-2017 Carl Chenet +# Copyright © 2015-2021 Carl Chenet # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or diff --git a/feed2toot/confparsers/hashtaglist.py b/feed2toot/confparsers/hashtaglist.py index e7ba0ee..ffdf710 100644 --- a/feed2toot/confparsers/hashtaglist.py +++ b/feed2toot/confparsers/hashtaglist.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright © 2015-2017 Carl Chenet +# Copyright © 2015-2021 Carl Chenet # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or diff --git a/feed2toot/confparsers/hashtags/__init__.py b/feed2toot/confparsers/hashtags/__init__.py new file mode 100644 index 0000000..979c3de --- /dev/null +++ b/feed2toot/confparsers/hashtags/__init__.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python3 +# vim:ts=4:sw=4:ft=python:fileencoding=utf-8 +# Copyright © 2015-2021 Carl Chenet +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see diff --git a/feed2toot/confparsers/hashtags/nohashtags.py b/feed2toot/confparsers/hashtags/nohashtags.py new file mode 100644 index 0000000..0cab8b6 --- /dev/null +++ b/feed2toot/confparsers/hashtags/nohashtags.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Copyright © 2015-2021 Carl Chenet +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# Copyright © 2015-2021 Carl Chenet # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or diff --git a/feed2toot/confparsers/plugins.py b/feed2toot/confparsers/plugins.py index 7600dd2..0911a6b 100644 --- a/feed2toot/confparsers/plugins.py +++ b/feed2toot/confparsers/plugins.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright © 2015-2017 Carl Chenet +# Copyright © 2015-2021 Carl Chenet # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or diff --git a/feed2toot/confparsers/rss/__init__.py b/feed2toot/confparsers/rss/__init__.py index d19bf9f..979c3de 100644 --- a/feed2toot/confparsers/rss/__init__.py +++ b/feed2toot/confparsers/rss/__init__.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # vim:ts=4:sw=4:ft=python:fileencoding=utf-8 -# Copyright © 2017 Carl Chenet +# Copyright © 2015-2021 Carl Chenet # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or diff --git a/feed2toot/confparsers/rss/addtags.py b/feed2toot/confparsers/rss/addtags.py new file mode 100644 index 0000000..6808254 --- /dev/null +++ b/feed2toot/confparsers/rss/addtags.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +# Copyright © 2015-2021 Carl Chenet +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# Copyright © 2015-2021 Carl Chenet # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or @@ -49,4 +49,7 @@ def parsepattern(config): except ValueError as err: logging.warn(err) patternscasesensitive[currentoption] = True + else: + # default value + patternscasesensitive[currentoption] = False return patterns, patternscasesensitive diff --git a/feed2toot/confparsers/rss/toot.py b/feed2toot/confparsers/rss/toot.py index 0c82220..58c8f56 100644 --- a/feed2toot/confparsers/rss/toot.py +++ b/feed2toot/confparsers/rss/toot.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright © 2015-2017 Carl Chenet +# Copyright © 2015-2021 Carl Chenet # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or diff --git a/feed2toot/confparsers/rss/tootmaxlen.py b/feed2toot/confparsers/rss/tootmaxlen.py new file mode 100644 index 0000000..eb2a448 --- /dev/null +++ b/feed2toot/confparsers/rss/tootmaxlen.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# Copyright © 2015-2021 Carl Chenet +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# Copyright © 2015-2021 Carl Chenet # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or @@ -18,10 +18,11 @@ # standard library imports import feedparser +import ssl import sys import re -def parseuri(config, clioption, feeds): +def parseuri(config, clioption, feeds, ignoressl): '''Parse configuration value of the uri option of the rss section''' rssuri = '' feedname ='' @@ -48,6 +49,10 @@ def parseuri(config, clioption, feeds): sys.exit('{confoption} parameter in the [{section}] section of the configuration file is mandatory. Exiting.'.format(section=section, confoption=confoption)) else: rssuri = clioption + # ignore ssl if asked + if ignoressl: + if hasattr(ssl, '_create_unverified_context'): + ssl._create_default_https_context = ssl._create_unverified_context # get the rss feed for rss parameter of [rss] section feed = feedparser.parse(rssuri) if not feed: diff --git a/feed2toot/confparsers/rss/urilist.py b/feed2toot/confparsers/rss/urilist.py index 65a8943..11265a2 100644 --- a/feed2toot/confparsers/rss/urilist.py +++ b/feed2toot/confparsers/rss/urilist.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright © 2015-2017 Carl Chenet +# Copyright © 2015-2021 Carl Chenet # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or @@ -20,10 +20,11 @@ import feedparser import logging import os.path +import ssl import sys import re -def parseurilist(config, accept_bozo_exceptions): +def parseurilist(config, accept_bozo_exceptions, ignoressl): '''Parse configuration value of the uri_list option of the rss section''' bozoexception = False feeds = [] @@ -62,6 +63,10 @@ def parseurilist(config, accept_bozo_exceptions): patternstring = '' # split different searched patterns patterns = [i for i in patternstring.split(stringsep) if i] + # ignore ssl if asked + if ignoressl: + if hasattr(ssl, '_create_unverified_context'): + ssl._create_default_https_context = ssl._create_unverified_context # retrieve the content of the rss feed = feedparser.parse(rss) if 'bozo_exception' in feed: diff --git a/feed2toot/feedcache.py b/feed2toot/feedcache.py index ca030f7..9bfad65 100644 --- a/feed2toot/feedcache.py +++ b/feed2toot/feedcache.py @@ -1,5 +1,5 @@ # vim:ts=4:sw=4:ft=python:fileencoding=utf-8 -# Copyright © 2015-2017 Carl Chenet +# Copyright © 2015-2021 Carl Chenet # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or diff --git a/feed2toot/filterentry.py b/feed2toot/filterentry.py index 4d9d793..37a7a76 100644 --- a/feed2toot/filterentry.py +++ b/feed2toot/filterentry.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright © 2015-2017 Carl Chenet +# Copyright © 2015-2021 Carl Chenet # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or diff --git a/feed2toot/hashtags.py b/feed2toot/hashtags.py new file mode 100644 index 0000000..6354614 --- /dev/null +++ b/feed2toot/hashtags.py @@ -0,0 +1,66 @@ +# vim:ts=4:sw=4:ft=python:fileencoding=utf-8 +# Copyright © 2015-2021 Carl Chenet +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see + +'''Manage a lock file''' + +# standard libraires imports +import codecs + +def extract_hashtags_from_list(options): + '''extract hashtags from the the list''' + if 'hashtaglist' in options and options['hashtaglist']: + severalwordshashtags = codecs.open(options['hashtaglist'], + encoding='utf-8').readlines() + severalwordshashtags = [i.rstrip('\n') for i in severalwordshashtags] + else: + severalwordshashtags = [] + return severalwordshashtags + +def build_hashtags(entry, rss, options, severalwordshashtags): + '''build hashtags''' + severalwordsinhashtag = False + # has the the rss feed hashtag + if 'tags' in entry and options['addtags']: + hastags = True + else: + hastags = False + if hastags: + rss['hashtags'] = [] + for i, _ in enumerate(entry['tags']): + if 'hashtaglist' in options: + prehashtags = entry['tags'][i]['term'] + tmphashtags = entry['tags'][i]['term'] + for element in severalwordshashtags: + if element in prehashtags: + severalwordsinhashtag = True + tmphashtags = prehashtags.replace(element, + ''.join(element.split())) + # replace characters stopping a word from being a hashtag + if severalwordsinhashtag: + # remove ' from hashtag + tmphashtags = tmphashtags.replace("'", "") + # remove - from hashtag + tmphashtags = tmphashtags.replace("-", "") + # remove . from hashtag + tmphashtags = tmphashtags.replace(".", "") + # remove space from hashtag + finalhashtags = tmphashtags.replace(" ", "") + rss['hashtags'].append('#{}'.format(finalhashtags)) + else: + nospace = ''.join(entry['tags'][i]['term']) + # remove space from hashtag + nospace = nospace.replace(" ", "") + rss['hashtags'].append('#{}'.format(nospace)) + return rss diff --git a/feed2toot/lock.py b/feed2toot/lock.py new file mode 100644 index 0000000..e03c56c --- /dev/null +++ b/feed2toot/lock.py @@ -0,0 +1,63 @@ +# vim:ts=4:sw=4:ft=python:fileencoding=utf-8 +# Copyright © 2015-2021 Carl Chenet +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see + +'''Manage a lock file''' + +# standard libraires imports +import datetime +import logging +import os +import os.path +import sys + +class LockFile: + '''LockFile object''' + def __init__(self, lockfile, locktimeout): + '''check the lockfile and the locktimeout''' + self.lockfile = lockfile + ltimeout = datetime.timedelta(seconds=locktimeout) + self.lfdateformat = '%Y-%m-%d_%H-%M-%S' + # if a lock file exists + if os.path.exists(self.lockfile): + if os.path.isfile(self.lockfile): + with open(self.lockfile, 'r') as lf: + lfcontent = lf.read().rstrip() + # lfcontent should be a datetime + logging.debug('Check if lock file is older than timeout ({timeout} secs)'.format(timeout=locktimeout)) + locktime = datetime.datetime.strptime(lfcontent, self.lfdateformat) + if locktime < (datetime.datetime.now() - ltimeout): + # remove the lock file + logging.debug('Found an expired lock file') + self.release() + self.create_lock() + else: + # quit because another feed2toot process is running + logging.debug('Found a valid lock file. Exiting immediately.') + sys.exit(0) + else: + # no lock file. Creating one + self.create_lock() + + def create_lock(self): + '''Create a lock file''' + with open(self.lockfile, 'w') as lf: + currentdatestring = datetime.datetime.now().strftime(self.lfdateformat) + lf.write(currentdatestring) + logging.debug('lockfile {lockfile} created.'.format(lockfile=self.lockfile)) + + def release(self): + '''Release the lockfile''' + os.remove(self.lockfile) + logging.debug('Removed lock file.') diff --git a/feed2toot/main.py b/feed2toot/main.py index c5902c1..1c70e03 100644 --- a/feed2toot/main.py +++ b/feed2toot/main.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # vim:ts=4:sw=4:ft=python:fileencoding=utf-8 -# Copyright © 2015-2017 Carl Chenet +# Copyright © 2015-2021 Carl Chenet # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or @@ -22,16 +22,26 @@ import importlib import logging import logging.handlers import sys +import re + +# external liraries imports +from bs4 import BeautifulSoup # app libraries imports from feed2toot.addtags import AddTags from feed2toot.cliparse import CliParse from feed2toot.confparse import ConfParse -from feed2toot.filterentry import FilterEntry -from feed2toot.removeduplicates import RemoveDuplicates -from feed2toot.tootpost import TootPost from feed2toot.feedcache import FeedCache -from bs4 import BeautifulSoup +from feed2toot.filterentry import FilterEntry +from feed2toot.hashtags import build_hashtags +from feed2toot.hashtags import extract_hashtags_from_list +from feed2toot.lock import LockFile +from feed2toot.message import build_message +from feed2toot.message import send_message_dry_run +from feed2toot.message import send_message +from feed2toot.plugins import activate_plugins +from feed2toot.rss import populate_rss +from feed2toot.sortentries import sort_entries class Main: '''Main class of Feed2toot''' @@ -57,7 +67,7 @@ class Main: logging.debug('configured stdout level %s' % sh.level) def main(self): - """The main function.""" + '''The main function''' clip = CliParse() clioptions = clip.options self.setup_logging(clioptions) @@ -70,14 +80,11 @@ class Main: tweetformat = conf[2] feeds = conf[3] plugins = conf[4] + # check the logfile and logtimeout + lockfile = LockFile(options['lockfile'], options['locktimeout']) # create link to the persistent list cache = FeedCache(options) - if 'hashtaglist' in options and options['hashtaglist']: - severalwordshashtags = codecs.open(options['hashtaglist'], - encoding='utf-8').readlines() - severalwordshashtags = [i.rstrip('\n') for i in severalwordshashtags] - else: - severalwordshashtags = [] + severalwordshashtags = extract_hashtags_from_list(options) # reverse feed entries because most recent one should be sent as the last one in Mastodon for feed in feeds: # store the patterns by rss @@ -89,139 +96,35 @@ class Main: if clioptions.rsssections: if entries: print('The following sections are available in this RSS feed: {}'.format([j for j in entries[0]])) - sys.exit(0) else: - sys.exit('Could not parse the section of the rss feed') - totweet = [] - # cache the ids of last rss feeds - if not clioptions.all: - for i in entries: - if 'id' in i: - if i['id'] not in cache.getdeque(): - totweet.append(i) - elif 'guid' in i: - if i['guid'] not in cache.getdeque(): - totweet.append(i) - else: - # if id or guid not in the entry, use link - if i['link'] not in cache.getdeque(): - totweet.append(i) - else: - totweet = entries - + print('Could not parse the section of the rss feed') + # release the lock file + lockfile.release() + sys.exit(0) + # sort entries and check if they were not previously sent + totweet = sort_entries(clioptions.all, cache, entries) for entry in totweet: - if 'id' in entry: - logging.debug('found feed entry {entryid}'.format(entryid=entry['id'])) - rss = { - 'id': entry['id'], - } - elif 'guid' in entry: - logging.debug('found feed entry {entryid}'.format(entryid=entry['guid'])) - rss = { - 'id': entry['guid'], - } - else: - logging.debug('found feed entry {entryid}'.format(entryid=entry['link'])) - rss = { - 'id': entry['link'], - } - - severalwordsinhashtag = False - # lets see if the rss feed has hashtag - if 'tags' in entry: - hastags = True - else: - hastags = False - - if hastags: - rss['hashtags'] = [] - for i, _ in enumerate(entry['tags']): - if 'hashtaglist' in options: - prehashtags = entry['tags'][i]['term'] - tmphashtags = entry['tags'][i]['term'] - for element in severalwordshashtags: - if element in prehashtags: - severalwordsinhashtag = True - tmphashtags = prehashtags.replace(element, - ''.join(element.split())) - # replace characters stopping a word from being a hashtag - if severalwordsinhashtag: - # remove ' from hashtag - tmphashtags = tmphashtags.replace("'", "") - # remove - from hashtag - tmphashtags = tmphashtags.replace("-", "") - # remove . from hashtag - tmphashtags = tmphashtags.replace(".", "") - # remove space from hashtag - finalhashtags = tmphashtags.replace(" ", "") - rss['hashtags'].append('#{}'.format(finalhashtags)) - else: - nospace = ''.join(entry['tags'][i]['term']) - # remove space from hashtag - nospace = nospace.replace(" ", "") - rss['hashtags'].append('#{}'.format(nospace)) - - elements = [] - for i in tweetformat.split(' '): - tmpelement = '' - # if i is not an empty string - if i: - if i.startswith('{') and i.endswith('}'): - tmpelement = i.strip('{}') - elements.append(tmpelement) - # match elements of the tweet format string with available element in the RSS feed + # populate rss with new entry to send + rss = populate_rss(entry) + rss = build_hashtags(entry, rss, options, severalwordshashtags) + # parse tweetfomat to elements + elements = re.findall(r"\{(.*?)\}",tweetformat) + # strip : from elements to allow string formating, eg. {title:.20} + for i,s in enumerate(elements): + if s.find(':'): + elements[i] = s.split(':')[0] fe = FilterEntry(elements, entry, options, feed['patterns'], feed['rssobject'], feed['feedname']) entrytosend = fe.finalentry if entrytosend: - tweetwithnotag = tweetformat.format(**entrytosend) - # remove duplicates from the final tweet - dedup = RemoveDuplicates(tweetwithnotag) - # only append hashtags if they exist - # remove last tags if tweet too long - if 'hashtags' in rss: - addtag = AddTags(dedup.finaltweet, rss['hashtags']) - finaltweet = addtag.finaltweet + finaltweet = build_message(entrytosend, tweetformat, rss, options['tootmaxlen'], options['notagsintoot']) + if clioptions.dryrun: + send_message_dry_run(config, entrytosend, finaltweet) else: - finaltweet = dedup.finaltweet - - # strip html tags - finaltweet = BeautifulSoup(finaltweet, 'html.parser').get_text() - - if clioptions.dryrun: - if entrytosend: - logging.warning('Would toot with visibility "{visibility}": {toot}'.format( - toot=finaltweet, - visibility=config.get( - 'mastodon', 'toot_visibility', - fallback='public'))) - else: - logging.debug('This rss entry did not meet pattern criteria. Should have not been sent') - else: - storeit = True - if entrytosend and not clioptions.populate: - logging.debug('Tooting with visibility "{visibility}": {toot}'.format( - toot=finaltweet, - visibility=config.get( - 'mastodon', 'toot_visibility', - fallback='public'))) - twp = TootPost(config, options, finaltweet) - storeit = twp.storeit() - else: - logging.debug('populating RSS entry {}'.format(rss['id'])) - # in both cas we store the id of the sent tweet - if storeit: - cache.append(rss['id']) - # plugins - if plugins and entrytosend: - for plugin in plugins: - capitalizedplugin = plugin.title() - pluginclassname = '{plugin}Plugin'.format(plugin=capitalizedplugin) - pluginmodulename = 'feed2toot.plugins.{pluginmodule}'.format(pluginmodule=pluginclassname.lower()) - try: - pluginmodule = importlib.import_module(pluginmodulename) - pluginclass = getattr(pluginmodule, pluginclassname) - pluginclass(plugins[plugin], finaltweet) - except ImportError as err: - print(err) + send_message(config, clioptions, options, entrytosend, finaltweet, cache, rss) + # plugins + if plugins and entrytosend: + activate_plugins(plugins, finaltweet) # do not forget to close cache (shelf object) cache.close() + # release the lock file + lockfile.release() diff --git a/feed2toot/message.py b/feed2toot/message.py new file mode 100644 index 0000000..ef431d6 --- /dev/null +++ b/feed2toot/message.py @@ -0,0 +1,82 @@ +# vim:ts=4:sw=4:ft=python:fileencoding=utf-8 +# Copyright © 2015-2021 Carl Chenet +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see + +'''Build the message''' + +# standard libraires imports +import logging + +# external liraries imports +from bs4 import BeautifulSoup + +# app libraries imports +from feed2toot.addtags import AddTags +from feed2toot.removeduplicates import RemoveDuplicates +from feed2toot.tootpost import TootPost + +def build_message(entrytosend, tweetformat, rss, tootmaxlen, notagsintoot): + '''populate the rss dict with the new entry''' + tweetwithnotag = tweetformat.format(**entrytosend) + # replace line breaks + tootwithlinebreaks = tweetwithnotag.replace('\\n', '\n') + # remove duplicates from the final tweet + dedup = RemoveDuplicates(tootwithlinebreaks) + # only add tags if user wants to + if not notagsintoot: + # only append hashtags if they exist + # remove last tags if tweet too long + if 'hashtags' in rss: + addtag = AddTags(dedup.finaltweet, rss['hashtags']) + finaltweet = addtag.finaltweet + else: + finaltweet = dedup.finaltweet + else: + finaltweet = dedup.finaltweet + # strip html tags + finaltweet = BeautifulSoup(finaltweet, 'html.parser').get_text() + # truncate toot to user-defined value whatever the content is + if len(finaltweet) > tootmaxlen: + finaltweet = finaltweet[0:tootmaxlen-1] + return ''.join([finaltweet[0:-3], '...']) + else: + return finaltweet + +def send_message_dry_run(config, entrytosend, finaltweet): + '''simulate sending message using dry run mode''' + if entrytosend: + logging.warning('Would toot with visibility "{visibility}": {toot}'.format( + toot=finaltweet, + visibility=config.get( + 'mastodon', 'toot_visibility', + fallback='public'))) + else: + logging.debug('This rss entry did not meet pattern criteria. Should have not been sent') + +def send_message(config, clioptions, options, entrytosend, finaltweet, cache, rss): + '''send message''' + storeit = True + if entrytosend and not clioptions.populate: + logging.debug('Tooting with visibility "{visibility}": {toot}'.format( + toot=finaltweet, + visibility=config.get( + 'mastodon', 'toot_visibility', + fallback='public'))) + twp = TootPost(config, options, finaltweet) + storeit = twp.storeit() + else: + logging.debug('populating RSS entry {}'.format(rss['id'])) + # in both cas we store the id of the sent tweet + if storeit: + cache.append(rss['id']) diff --git a/feed2toot/plugins/__init__.py b/feed2toot/plugins/__init__.py index d19bf9f..f6ac8a4 100644 --- a/feed2toot/plugins/__init__.py +++ b/feed2toot/plugins/__init__.py @@ -13,3 +13,19 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see + +# standard libraires imports +import importlib + +def activate_plugins(plugins, finaltweet): + '''activate plugins''' + for plugin in plugins: + capitalizedplugin = plugin.title() + pluginclassname = '{plugin}Plugin'.format(plugin=capitalizedplugin) + pluginmodulename = 'feed2toot.plugins.{pluginmodule}'.format(pluginmodule=pluginclassname.lower()) + try: + pluginmodule = importlib.import_module(pluginmodulename) + pluginclass = getattr(pluginmodule, pluginclassname) + pluginclass(plugins[plugin], finaltweet) + except ImportError as err: + print(err) diff --git a/feed2toot/removeduplicates.py b/feed2toot/removeduplicates.py index 6e857b1..8141d4f 100644 --- a/feed2toot/removeduplicates.py +++ b/feed2toot/removeduplicates.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # vim:ts=4:sw=4:ft=python:fileencoding=utf-8 -# Copyright © 2015-2017 Carl Chenet +# Copyright © 2015-2021 Carl Chenet # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or diff --git a/feed2toot/rss.py b/feed2toot/rss.py new file mode 100644 index 0000000..da271d9 --- /dev/null +++ b/feed2toot/rss.py @@ -0,0 +1,42 @@ +# vim:ts=4:sw=4:ft=python:fileencoding=utf-8 +# Copyright © 2015-2021 Carl Chenet +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see + +'''Manage a lock file''' + +# standard libraires imports +import datetime +import logging +import os +import os.path +import sys + +def populate_rss(entry): + '''populate the rss dict with the new entry''' + if 'id' in entry: + logging.debug('found feed entry {entryid}'.format(entryid=entry['id'])) + rss = { + 'id': entry['id'], + } + elif 'guid' in entry: + logging.debug('found feed entry {entryid}'.format(entryid=entry['guid'])) + rss = { + 'id': entry['guid'], + } + else: + logging.debug('found feed entry {entryid}'.format(entryid=entry['link'])) + rss = { + 'id': entry['link'], + } + return rss diff --git a/feed2toot/sortentries.py b/feed2toot/sortentries.py new file mode 100644 index 0000000..f5ac5ef --- /dev/null +++ b/feed2toot/sortentries.py @@ -0,0 +1,42 @@ +# vim:ts=4:sw=4:ft=python:fileencoding=utf-8 +# Copyright © 2015-2021 Carl Chenet +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see + +'''Manage a lock file''' + +# standard libraires imports +import datetime +import logging +import os +import os.path +import sys + +def sort_entries(is_all, cache, entries): + '''sort entries before sending''' + totweet = [] + if not is_all: + for i in entries: + if 'id' in i: + if i['id'] not in cache.getdeque(): + totweet.append(i) + elif 'guid' in i: + if i['guid'] not in cache.getdeque(): + totweet.append(i) + else: + # if id or guid not in the entry, use link + if i['link'] not in cache.getdeque(): + totweet.append(i) + else: + totweet = entries + return totweet diff --git a/feed2toot/tootpost.py b/feed2toot/tootpost.py index ddf0a0a..9cf7d0d 100644 --- a/feed2toot/tootpost.py +++ b/feed2toot/tootpost.py @@ -1,5 +1,5 @@ # vim:ts=4:sw=4:ft=python:fileencoding=utf-8 -# Copyright © 2015-2017 Carl Chenet +# Copyright © 2015-2021 Carl Chenet # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or diff --git a/scripts/__init__.py b/scripts/__init__.py new file mode 100644 index 0000000..979c3de --- /dev/null +++ b/scripts/__init__.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python3 +# vim:ts=4:sw=4:ft=python:fileencoding=utf-8 +# Copyright © 2015-2021 Carl Chenet +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see diff --git a/scripts/feed2toot b/scripts/feed2toot index cb5f300..09cf4dd 100755 --- a/scripts/feed2toot +++ b/scripts/feed2toot @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # vim:ts=4:sw=4:ft=python:fileencoding=utf-8 -# Copyright © 2015-2017 Carl Chenet +# Copyright © 2015-2021 Carl Chenet # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or diff --git a/scripts/register_feed2toot_app b/scripts/register_feed2toot_app index 1ea856e..7453ecc 100755 --- a/scripts/register_feed2toot_app +++ b/scripts/register_feed2toot_app @@ -1,7 +1,6 @@ #!/usr/bin/env python3 -#!/usr/bin/env python3 # vim:ts=4:sw=4:ft=python:fileencoding=utf-8 -# Copyright © 2015-2017 Carl Chenet +# Copyright © 2015-2021 Carl Chenet # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or @@ -15,16 +14,42 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see +from argparse import ArgumentParser from getpass import getpass from os import getcwd +from os import linesep +from os import sep from mastodon import Mastodon from mastodon.Mastodon import MastodonIllegalArgumentError import sys -print('\nThis script generates the Mastodon application credentials for Feed2toot.\nfeed2toot_clientcred.txt and feed2toot_usercred.txt will be written\nin the current directory: {cwd}.\nA connection is initiated to create the application.\nYour password is *not* stored.\n'.format(cwd=getcwd())) +__version__ = '0.2' + +epilog = 'For more information: https://feed2toot.readthedocs.io' +description = 'Create a Mastodon app for Feed2toot' +parser = ArgumentParser(prog='register_feed2toot_app', + description=description, + epilog=epilog) +parser.add_argument('--version', action='version', version=__version__) +parser.add_argument('--client-credentials-file', dest='clientcredfile', help='the name of the client credentials for the Mastodon app', default='feed2toot_clientcred.txt') +parser.add_argument('--user-credentials-file', dest='usercredfile', help='the name of the user credentials for the Mastodon app', default='feed2toot_usercred.txt') +parser.add_argument('--name', help='the name of the Mastodon app', default='feed2toot') +parser.add_argument('--instance', help='the URL of the Mastodon instance') +parser.add_argument('--username', help='the username of your Mastodon account') +parser.add_argument('--password', help='the password of your Mastodon account') +opts = parser.parse_args() + +clientcredfile=opts.clientcredfile +usercredfile=opts.usercredfile + +headline = '{linesep}This script generates the Mastodon application credentials for Feed2toot.{linesep}{clientcredfile} and {usercredfile} will be written{linesep}in the current directory: {cwd}.{linesep}WARNING: previous files with the same names will be overwritten.{linesep}{linesep}A connection is also initiated to create the application.{linesep}Your password is *not* stored.{linesep}'.format(linesep=linesep, clientcredfile=clientcredfile, usercredfile=usercredfile, cwd=getcwd()) +print(headline) + # get the instance -instance = input('Mastodon instance URL (defaults to https://mastodon.social): ') +instance = opts.instance +if not instance: + instance = input('Mastodon instance URL (defaults to https://mastodon.social): ') if not instance: instance = 'https://mastodon.social' elif not instance.startswith('http'): @@ -32,34 +57,45 @@ elif not instance.startswith('http'): # get the username userok = False +quit_on_error = True while not userok: - user = input('Mastodon login: ') + user = opts.username + if not user: + user = input('Mastodon login: ') + quit_on_error = False if not user: print('Your Mastodon username can not be empty.') - userok = False elif '@' not in user or '.' not in user: print('Your Mastodon username should be an email.') - userok = False else: userok = True + if not userok and quit_on_error: + exit() # get the password -password = getpass(prompt='Mastodon password: ') +password = opts.password +if not password: + password = getpass(prompt='Mastodon password: ') Mastodon.create_app( - 'feed2toot', + opts.name, api_base_url=instance, - to_file = '{cwd}/feed2toot_clientcred.txt'.format(cwd=getcwd()) + to_file = '{cwd}{sep}{clientcredfile}'.format(cwd=getcwd(), sep=sep, clientcredfile=clientcredfile) ) -mastodon = Mastodon(client_id = '{cwd}/feed2toot_clientcred.txt'.format(cwd=getcwd()), +mastodon = Mastodon(client_id = '{cwd}{sep}{clientcredfile}'.format(cwd=getcwd(), sep=sep, clientcredfile=clientcredfile), api_base_url=instance) try: mastodon.log_in( user, password, - to_file = '{cwd}/feed2toot_usercred.txt'.format(cwd=getcwd()) + to_file = '{cwd}{sep}{usercredfile}'.format(cwd=getcwd(), sep=sep, usercredfile=usercredfile) ) except MastodonIllegalArgumentError as err: print(err) - sys.exit('\nI guess you entered a bad login or password.\n') -print('feed2toot was added to your preferences=>authorized apps page.') + sys.exit('{linesep}I guess you entered a bad login or password.{linesep}'.format(linesep=linesep)) + +summary = '{linesep}The app {appname} was added to your Preferences=>Accounts=>Authorized apps page.{linesep}The file {clientcredfile} and {usercredfile} were created in the current directory.{linesep}'.format(appname=opts.name, + linesep=linesep, + clientcredfile=clientcredfile, + usercredfile=usercredfile) +print(summary) sys.exit(0) diff --git a/setup.py b/setup.py index a270ea0..6ce1662 100755 --- a/setup.py +++ b/setup.py @@ -1,4 +1,4 @@ -# Copyright 2015-2017 Carl Chenet +# Copyright 2015-2021 Carl Chenet # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or @@ -25,17 +25,18 @@ CLASSIFIERS = [ 'Operating System :: POSIX :: Linux', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6' + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7' ] setup( name='feed2toot', - version='0.9', + version='0.17', license='GNU GPL v3', description='Parse rss feeds and send new posts to Mastodon', long_description='Parse rss feeds and send new posts to the Mastodon social network', author = 'Carl Chenet', - author_email = 'chaica@ohmytux.com', + author_email = 'carl.chenet@ohmytux.com', url = 'https://gitlab.com/chaica/feed2toot', classifiers=CLASSIFIERS, download_url='https://gitlab.com/chaica/feed2toot',