-
Notifications
You must be signed in to change notification settings - Fork 1
Mastodon Favorites feed, mastodon client script, refactor #14
Changes from all commits
298445b
2f46c5f
7fc1ee1
20e8aba
433e35a
961d9a6
cb3b907
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,18 +11,15 @@ | |
| import tweepy | ||
| import yaml | ||
| import sys | ||
|
|
||
| import os | ||
| from config import get_config | ||
|
|
||
| app = Flask(__name__) | ||
| app.debug = True | ||
|
|
||
| param = get_config() | ||
|
|
||
| with open('config.yml', 'r') as stream: | ||
| try: | ||
| param = yaml.safe_load(stream) | ||
| except yaml.YAMLError as e: | ||
| print(e) | ||
| sys.exit() | ||
| text_length_limit = int(param['feed'].get('text_length_limit', 100)) | ||
|
|
||
| try: | ||
|
|
@@ -40,9 +37,19 @@ | |
|
|
||
| # Mastodon | ||
| try: | ||
| client_file = param['mastodon']['client_id_file'] | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added some sanity checks here because I had a typo in my config file that was tricky to debug. |
||
| if not os.path.exists(client_file): | ||
| raise Exception("File not found: " + client_file) | ||
| access_token_file = param['mastodon']['access_token_file'] | ||
| if not os.path.exists(access_token_file): | ||
| raise Exception("File not found: " + client_file) | ||
|
|
||
| mastodon_url = param['mastodon'].get('url', 'https://mastodon.social') | ||
|
|
||
| mastodon = Mastodon( | ||
| client_id=param['mastodon']['client_id_file'], | ||
| access_token=param['mastodon']['access_token_file'] | ||
| client_id=client_file, | ||
| access_token=access_token_file, | ||
| api_base_url=mastodon_url | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added this after discovering it refused to work for anything but the flagship instance. |
||
| ) | ||
| except Exception as e: | ||
| print('Error Mastodon instance creation: ' + str(e)) | ||
|
|
@@ -189,7 +196,8 @@ def tootfeed(query_feed): | |
| '♻ : ' + str(toot['reblogs_count']) + ', ' + \ | ||
| '✰ : ' + str(toot['favourites_count']) + '</div></blockquote>' | ||
|
|
||
| toot['created_at'] = datetime.datetime.strptime(toot['created_at'], '%Y-%m-%dT%H:%M:%S.%fZ') | ||
| if isinstance(toot['created_at'], str): | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the most recent Mastodon.py, |
||
| toot['created_at'] = datetime.datetime.strptime(toot['created_at'], '%Y-%m-%dT%H:%M:%S.%fZ') | ||
|
|
||
| buffered.append(toot.copy()) | ||
|
|
||
|
|
@@ -204,12 +212,66 @@ def tootfeed(query_feed): | |
| for toot in buffered: | ||
|
|
||
| text = BeautifulSoup(toot['content'], "html.parser").text | ||
| if len(text) > 100: | ||
| text = text[:100] + '... ' | ||
| pubdate = toot['created_at'] | ||
| if not pubdate.tzinfo: | ||
| pubdate = utc.localize(pubdate).astimezone(pytz.timezone(param['feed']['timezone'])) | ||
|
|
||
| if len(text) > text_length_limit: | ||
| text = text[:text_length_limit] + '... ' | ||
| f.add_item(title=toot['account']['display_name'] + ' (' + toot['account']['username'] + '): ' | ||
| + text, | ||
| link=toot['url'], | ||
| pubdate=pubdate, | ||
| description=toot['htmltext']) | ||
|
|
||
| xml = f.writeString('UTF-8') | ||
| else: | ||
| xml = 'error - Mastodon parameters not defined' | ||
|
|
||
| return xml | ||
|
|
||
| @app.route('/toot_favorites') | ||
| def toot_favorites_feed(): | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And this was what started me on this whole thing. |
||
| """ generate an rss feed authenticated user's favorites """ | ||
|
|
||
| if mastodonOK: | ||
| buffered = [] | ||
| favorite_toots = mastodon.favourites() | ||
| for toot in favorite_toots: | ||
|
|
||
| toot['htmltext'] = '<blockquote><div><img src="' + toot['account']['avatar_static'] + \ | ||
| '" alt="' + toot['account']['display_name'] + \ | ||
| '" /> <strong>' + toot['account']['username'] + \ | ||
| ': </strong>' + toot['content'] + '<br>' + \ | ||
| '♻ : ' + str(toot['reblogs_count']) + ', ' + \ | ||
| '✰ : ' + str(toot['favourites_count']) + '</div></blockquote>' | ||
|
|
||
| if isinstance(toot['created_at'], str): | ||
| toot['created_at'] = datetime.datetime.strptime(toot['created_at'], '%Y-%m-%dT%H:%M:%S.%fZ') | ||
|
|
||
| buffered.append(toot.copy()) | ||
|
|
||
| utc = pytz.utc | ||
| f = feedgenerator.Rss201rev2Feed(title=param['mastodon']['title'] + ' Favourites ', | ||
| link=param['mastodon']['url'] + '/web/favourites', | ||
| description=param['mastodon']['description'], | ||
| language=param['feed']['language'], | ||
| author_name=param['feed']['author_name'], | ||
| feed_url=param['feed']['feed_url']) | ||
|
|
||
| for toot in buffered: | ||
|
|
||
| text = BeautifulSoup(toot['content'], "html.parser").text | ||
| pubdate = toot['created_at'] | ||
| if not pubdate.tzinfo: | ||
| pubdate = utc.localize(pubdate).astimezone(pytz.timezone(param['feed']['timezone'])) | ||
|
|
||
| if len(text) > text_length_limit: | ||
| text = text[:text_length_limit] + '... ' | ||
| f.add_item(title=toot['account']['display_name'] + ' (' + toot['account']['username'] + '): ' | ||
| + text, | ||
| link=toot['url'], | ||
| pubdate=utc.localize(toot['created_at']).astimezone(pytz.timezone(param['feed']['timezone'])), | ||
| pubdate=pubdate, | ||
| description=toot['htmltext']) | ||
|
|
||
| xml = f.writeString('UTF-8') | ||
|
|
@@ -220,4 +282,5 @@ def tootfeed(query_feed): | |
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| app.run() | ||
| app.run(use_reloader=True) | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was helpful during dev. I haven't deployed it, but I suspect it won't have any effect under gunicorn or whatever. |
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| """ | ||
| Loads and parses the configuration file. | ||
| """ | ||
| import yaml | ||
|
|
||
| def get_config(): | ||
| with open('config.yml', 'r', encoding='utf-8') as stream: | ||
| try: | ||
| return yaml.safe_load(stream) | ||
| except yaml.YAMLError as e: | ||
| print(e) | ||
| sys.exit() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| from config import get_config | ||
| from mastodon import Mastodon | ||
| from getpass import getpass | ||
|
|
||
| if __name__ == '__main__': | ||
| print("This script helps you create a new mastodon client and log in.") | ||
| print("Before we start, make sure config.yml exists.") | ||
| config = get_config() | ||
|
|
||
| mast_cfg = config['mastodon'] | ||
| print("Configuration found.") | ||
| print("Looks like you want to use this instance: ", mast_cfg['url']) | ||
| print("If that's wrong, now is a good time to cancel (^C) and fix it.") | ||
| input("<enter> to continue") | ||
|
|
||
| print("Registering a new app with {url} called {app_name} and saving credentials in {client_id_file}".format(**mast_cfg)) | ||
|
|
||
| Mastodon.create_app(mast_cfg['app_name'], api_base_url=mast_cfg['url'], to_file=mast_cfg['client_id_file'], scopes=['read']) | ||
| mastodon = Mastodon(client_id=mast_cfg['client_id_file'], api_base_url=mast_cfg['url']) | ||
| print("Registration successful. Now to log in.") | ||
| user_email = input("User email: ") | ||
| password = getpass("Password (not shown and not saved):") | ||
|
|
||
| # Log in - either every time, or use persisted | ||
| mastodon.log_in(user_email, password, to_file=mast_cfg['access_token_file'], scopes=['read']) | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This app doesn't need write or follow permissions, so this asks only for read access. It looks a little less scary on the authorized apps page... |
||
|
|
||
| print("Verifying credentials...") | ||
| try: | ||
| res = mastodon.account_verify_credentials() | ||
| print("Credentials look good; client reports user's account name is: " + res['acct']) | ||
| print("Configuration complete; app should appear at: " + mast_cfg['url'] + "/oauth/authorized_applications") | ||
| print("You should not need to log in again unless this app is removed or credentials expire.") | ||
| except Exception as ex: | ||
| print("Something went wrong; mastodon client reported an error:") | ||
| print(ex) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| Flask==0.12.2 | ||
| bs4==0.0.1 | ||
| feedgenerator==1.9 | ||
| tweepy==3.5.0 | ||
| pytz==2017.3 | ||
| Mastodon.py==1.1.2 | ||
| PyYAML==3.12 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved the config loading here so I could use it in
create_mastodon_client.