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