initial
This commit is contained in:
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
feediverse.egg-info
|
||||
Pipfile*
|
||||
__pycache__
|
||||
.config.yml.swp
|
||||
22
LICENSE
Normal file
22
LICENSE
Normal file
@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018 Ed Summers
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
58
README.md
Normal file
58
README.md
Normal file
@ -0,0 +1,58 @@
|
||||
*feediverse* will read RSS/Atom feeds and send the messages as Mastodon posts.
|
||||
It's kind of the same thing as [feed2toot] but just one module that works with
|
||||
Python 3.
|
||||
|
||||
## Install
|
||||
|
||||
pip install feediverse
|
||||
|
||||
## Run
|
||||
|
||||
The first time you run *feediverse* you'll need to tell it your Mastodon
|
||||
instance and get an access token which it will save in a configuration file. If
|
||||
you don't specify a config file it will use `~/.feediverse`:
|
||||
|
||||
feediverse
|
||||
|
||||
Once *feediverse* is configured you can add it to your crontab:
|
||||
|
||||
*/15 * * * * /usr/local/bin/feediverse
|
||||
|
||||
## Post Format
|
||||
|
||||
You can customize the post format by opening the configuration file (default is
|
||||
~/.feediverse) and updating the *template* property of your feed. The default
|
||||
format is:
|
||||
|
||||
{title} {url}
|
||||
|
||||
But you can use the `{summary}` as well, and also add text like so:
|
||||
|
||||
Bookmark: {title} {url} {summary}
|
||||
|
||||
## Multiple Feeds
|
||||
|
||||
Since *feeds* is a list you can add additional feeds to watch if you want.
|
||||
|
||||
...
|
||||
feeds:
|
||||
- url: https://example.com/feed/
|
||||
template: "dot com: {title} {url}"
|
||||
- url: https://example.org/feed/
|
||||
template: "dot org: {title} {url}"
|
||||
|
||||
## Why?
|
||||
|
||||
I created *feediverse* because I wanted to send my Pinboard bookmarks to Mastodon.
|
||||
I've got an IFTTT recipe that does this for Twitter, but IFTTT doesn't appear to
|
||||
work with Mastodon yet.
|
||||
|
||||
That being said *feediverse* should work with any RSS or Atom feed (thanks to
|
||||
[feedparser]). But please be responsible. Don't fill up Mastodon with tons of
|
||||
junk just because you can. That kind of toxic behavior is why a lot of people
|
||||
are leaving other social media platforms and trying to start over in Mastodon.
|
||||
|
||||
[feed2toot]: https://gitlab.com/chaica/feed2toot/
|
||||
[feedparser]: http://feedparser.org/
|
||||
|
||||
|
||||
108
feediverse.py
Executable file
108
feediverse.py
Executable file
@ -0,0 +1,108 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import sys
|
||||
import yaml
|
||||
import dateutil
|
||||
import feedparser
|
||||
|
||||
from mastodon import Mastodon
|
||||
from datetime import datetime, timezone
|
||||
|
||||
def main():
|
||||
config_file = get_config_file()
|
||||
if not os.path.isfile(config_file):
|
||||
setup(config_file)
|
||||
|
||||
config = read_config(config_file)
|
||||
|
||||
masto = Mastodon(
|
||||
api_base_url=config['url'],
|
||||
client_id=config['client_id'],
|
||||
client_secret=config['client_secret'],
|
||||
access_token=config['access_token']
|
||||
)
|
||||
|
||||
for feed in config['feeds']:
|
||||
for entry in get_feed(feed['url'], config['updated']):
|
||||
print(feed['template'].format(**entry))
|
||||
#masto.status_post(get_text(entry))
|
||||
|
||||
save_config(config, config_file)
|
||||
|
||||
def save_config(config, config_file):
|
||||
copy = dict(config)
|
||||
copy['updated'] = datetime.now(tz=timezone.utc).isoformat()
|
||||
with open(config_file, 'w') as fh:
|
||||
fh.write(yaml.dump(copy, default_flow_style=False))
|
||||
|
||||
def read_config(config_file):
|
||||
config = {}
|
||||
with open(config_file) as fh:
|
||||
config = yaml.load(fh)
|
||||
if 'updated' in config:
|
||||
config['updated'] = dateutil.parser.parse(config['updated'])
|
||||
else:
|
||||
config['updated'] = datetime.now(tz=timezone.utc)
|
||||
return config
|
||||
|
||||
def get_feed(feed_url, last_update):
|
||||
new_entries = 0
|
||||
feed = feedparser.parse(feed_url)
|
||||
for entry in feed.entries:
|
||||
e = get_entry(entry)
|
||||
if last_update is None or e['updated'] > last_update:
|
||||
new_entries += 1
|
||||
yield e
|
||||
return new_entries
|
||||
|
||||
def get_entry(entry):
|
||||
return {
|
||||
'url': entry.id,
|
||||
'title': entry.title,
|
||||
'summary': entry.get('summary', ''),
|
||||
'updated': dateutil.parser.parse(entry['updated']),
|
||||
}
|
||||
|
||||
def setup(config_file):
|
||||
url = input('What is your Mastodon Instance URL? ')
|
||||
have_app = input('Do you have your app credentials already? [y/n] ')
|
||||
if have_app.lower() == 'y':
|
||||
name = 'feediverse'
|
||||
client_id = input('What is your app\'s client id: ')
|
||||
client_secret = input('What is your client secret: ')
|
||||
access_token = input('access_token: ')
|
||||
else:
|
||||
print("Ok, I'll need a few things in order to get your access token")
|
||||
name = input('app name (e.g. feediverse): ')
|
||||
client_id, client_secret = Mastodon.create_app(name, scopes=['read', 'write'], website='https://github.com/edsu/feediverse')
|
||||
username = input('mastodon username (email): ')
|
||||
password = input('mastodon password (not stored): ')
|
||||
access_token = m.log_in(username, password)
|
||||
|
||||
m = Mastodon(client_id, client_secret)
|
||||
feed_url = input('RSS/Atom feed URL to watch: ')
|
||||
config = {
|
||||
'name': name,
|
||||
'url': url,
|
||||
'client_id': client_id,
|
||||
'client_secret': client_secret,
|
||||
'access_token': access_token,
|
||||
'feeds': [
|
||||
{'url': feed_url, 'template': '{title} {url}'}
|
||||
]
|
||||
}
|
||||
save_config(config, config_file)
|
||||
print("Your feediverse configuration has been saved to {}".format(config_file))
|
||||
print("Add a line line this to your crontab to check every 15 minutes:")
|
||||
print("*/15 * * * * /usr/local/bin/feediverse")
|
||||
|
||||
def get_config_file():
|
||||
if __name__ == "__main__" and len(sys.argv) > 1:
|
||||
config_file = sys.argv[1]
|
||||
else:
|
||||
config_file = os.path.join(os.path.expanduser("~"), ".feediverse")
|
||||
return config_file
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
18
setup.py
Normal file
18
setup.py
Normal file
@ -0,0 +1,18 @@
|
||||
from setuptools import setup
|
||||
|
||||
with open("README.md") as f:
|
||||
long_description = f.read()
|
||||
|
||||
setup(
|
||||
name='feediverse',
|
||||
version='0.0.1',
|
||||
url='https://github.com/edsu/feediverse',
|
||||
author='Ed Summers',
|
||||
author_email='ehs@pobox.com',
|
||||
py_modules=['feediverse', ],
|
||||
description='Connect an RSS Feed to Mastodon',
|
||||
long_description=long_description,
|
||||
long_description_content_type="text/markdown",
|
||||
install_requires=['feedparser', 'mastodon.py', 'python-dateutil', 'pyyaml'],
|
||||
entry_points={'console_scripts': ['feediverse = feediverse:main']}
|
||||
)
|
||||
Reference in New Issue
Block a user