From 135120a6bf088be2d405b4584090bff297cd48e8 Mon Sep 17 00:00:00 2001 From: Craig Maloney Date: Sat, 23 Dec 2017 10:59:42 -0500 Subject: [PATCH 01/20] Updating Changelog date --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1e8f63..2dd1e4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). ### Release -### [0.3.2] - TBD +### [0.3.2] - 2017-12-23 #### Added - Reworked the Tootstream Parser to add styling, link-shortening, link retrieval, and emoji code shortening From 626257ac0cd90725ece902b3c172d18b4a610eaa Mon Sep 17 00:00:00 2001 From: Craig Maloney Date: Wed, 27 Dec 2017 00:33:03 -0500 Subject: [PATCH 02/20] Initial lists API --- src/tootstream/toot.py | 173 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 172 insertions(+), 1 deletion(-) diff --git a/src/tootstream/toot.py b/src/tootstream/toot.py index 859ce0f..0007235 100644 --- a/src/tootstream/toot.py +++ b/src/tootstream/toot.py @@ -136,6 +136,25 @@ def get_userid(mastodon, rest): else: return users[0]['id'] +def get_list_id(mastodon, rest): + """Get the ID for a list""" + if not rest: + return -1 + + # maybe it's already an int + try: + return int(rest) + except ValueError: + pass + + # not an int + lists = mastodon.lists() + for item in lists: + if item['title'].lower() == rest.lower(): + return item['id'] + + + def flaghandler_note(mastodon, rest): """Parse input for flagsr. """ @@ -602,6 +621,12 @@ def edittoot(text): return '' +def printList(list_item): + """Prints list entry nicely with hardcoded colors.""" + cprint(list_item['title'], fg('cyan'), end=" ") + cprint("(id: %s)" % list_item['id'], fg('red')) + + ##################################### ######## DECORATORS ######## ##################################### @@ -1052,11 +1077,18 @@ def stream(mastodon, rest): mastodon.stream_public(toot_listener) elif rest == "local": mastodon.stream_local(toot_listener) + elif rest.startswith('list'): + items = rest.split(' ') + if len(items) < 2: + print("list stream must have a list ID.") + return + item = get_list_id(mastodon, items[-1]) + mastodon.stream_list(item, toot_listener) elif rest.startswith('#'): tag = rest[1:] mastodon.stream_hashtag(tag, toot_listener) else: - print("Only 'home', 'fed', 'local', and '#hashtag' streams are supported.") + print("Only 'home', 'fed', 'local', 'list', and '#hashtag' streams are supported.") except KeyboardInterrupt: pass stream.__argstr__ = '' @@ -1570,6 +1602,145 @@ def quit(mastodon, rest): quit.__section__ = 'Profile' +@command +def lists(mastodon, rest): + """Shows the lists that the user has created """ + user_lists = mastodon.lists() + for list_item in user_lists: + printList(list_item) +lists.__argstr__ = '' +lists.__section__ = 'List' + + +@command +def listcreate(mastodon, rest): + """Creates a list """ + + try: + mastodon.list_create(rest) + cprint("List {} created.".format(rest), fg('green')) + except Exception as e: + cprint("error while creating list: {}".format(type(e).__name__), fg('red')) + return +listcreate.__argstr__ = '' +listcreate.__section__ = 'List' + + +@command +def listdestroy(mastodon, rest): + """Destroys a list """ + item = get_list_id(mastodon, rest) + if not item or item == -1: + cprint("List {} is not found".format(rest), fg('red')) + return + try: + mastodon.list_delete(item) + cprint("List {} deleted.".format(rest), fg('green')) + + except Exception as e: + cprint("error while creating list: {}".format(type(e).__name__), fg('red')) + return +listdestroy.__argstr__ = '' +listdestroy.__section__ = 'List' + +@command +def listhome(mastodon, rest): + """Show the toots from a list""" + if not rest: + cprint("Argument required.", fg('red')) + return + + try: + item = get_list_id(mastodon, rest) + if not item or item == -1: + cprint("List {} is not found".format(rest), fg('red')) + return + list_toots = mastodon.timeline_list(item) + for toot in reversed(list_toots): + printToot(toot) + except Exception as e: + cprint("error while displaying list: {}".format(type(e).__name__), fg('red')) +listhome.__argstr__ = '' +listhome.__section__ = 'List' + + +@command +def listaccounts(mastodon, rest): + """Show the accounts for the list """ + item = get_list_id(mastodon, rest) + if not item: + cprint("List {} is not found".format(rest), fg('red')) + return + list_accounts = mastodon.list_accounts(item) + + cprint("List: %s" % rest, fg('green')) + for user in list_accounts: + printUser(user) + +listaccounts.__argstr__ = '' +listaccounts.__section__ = 'List' + + +@command +def listadd(mastodon, rest): + """Add user to list.""" + if not rest: + cprint("Argument required.", fg('red')) + return + items = rest.split(' ') + if len(items) < 2: + cprint("Not enough arguments.", fg('red')) + return + + list_id = get_list_id(mastodon, items[0]) + account_id = get_userid(mastodon, items[1]) + + if not list_id: + cprint("List {} is not found".format(items[0]), fg('red')) + return + + if not account_id: + cprint("Account {} is not found".format(items[1]), fg('red')) + return + + try: + mastodon.list_accounts_add(list_id, account_id) + cprint("Added {} to list {}.".format(items[1], items[0]), fg('green')) + except Exception as e: + cprint("error while adding to list: {}".format(type(e).__name__), fg('red')) +listadd.__argstr__ = '' +listadd.__section__ = 'List' + + +@command +def listremove(mastodon, rest): + """Remove user from list.""" + if not rest: + cprint("Argument required.", fg('red')) + return + items = rest.split(' ') + if len(items) < 2: + cprint("Not enough arguments.", fg('red')) + return + + list_id = get_list_id(mastodon, items[0]) + account_id = get_userid(mastodon, items[1]) + + if not list_id: + cprint("List {} is not found".format(items[0]), fg('red')) + return + + if not account_id: + cprint("Account {} is not found".format(items[1]), fg('red')) + return + + try: + mastodon.list_accounts_delete(list_id, account_id) + cprint("Removed {} from list {}.".format(items[1], items[0]), fg('green')) + except Exception as e: + cprint("error while deleting from list: {}".format(type(e).__name__), fg('red')) +listremove.__argstr__ = '' +listremove.__section__ = 'List' ##################################### ######### END COMMAND BLOCK ######### ##################################### From 21c1931f6954c6c9bf3ab71b7c728c364c914155 Mon Sep 17 00:00:00 2001 From: Francis Dinh Date: Wed, 27 Dec 2017 11:50:36 -0500 Subject: [PATCH 03/20] Strip leading/trailing whitespace from media filepath --- src/tootstream/toot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tootstream/toot.py b/src/tootstream/toot.py index 859ce0f..0ba20c4 100644 --- a/src/tootstream/toot.py +++ b/src/tootstream/toot.py @@ -250,7 +250,7 @@ def flaghandler_tootreply(mastodon, rest): break # expand paths and check file access - fname = os.path.expanduser(fname) + fname = os.path.expanduser(fname).strip() if os.path.isfile(fname) and os.access(fname, os.R_OK): media.append(fname) count += 1 From ac73e18682955a22d4e04c24fd8a7b9673a7e25e Mon Sep 17 00:00:00 2001 From: Craig Maloney Date: Wed, 27 Dec 2017 12:39:23 -0500 Subject: [PATCH 04/20] More list fixes, added completion --- src/tootstream/toot.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/tootstream/toot.py b/src/tootstream/toot.py index 0007235..ffb6c8d 100644 --- a/src/tootstream/toot.py +++ b/src/tootstream/toot.py @@ -136,6 +136,7 @@ def get_userid(mastodon, rest): else: return users[0]['id'] + def get_list_id(mastodon, rest): """Get the ID for a list""" if not rest: @@ -147,6 +148,8 @@ def get_list_id(mastodon, rest): except ValueError: pass + rest = rest.strip() + # not an int lists = mastodon.lists() for item in lists: @@ -1658,6 +1661,7 @@ def listhome(mastodon, rest): list_toots = mastodon.timeline_list(item) for toot in reversed(list_toots): printToot(toot) + completion_add(toot) except Exception as e: cprint("error while displaying list: {}".format(type(e).__name__), fg('red')) listhome.__argstr__ = '' @@ -1818,6 +1822,8 @@ def main(instance, config, profile): prompt = "[@{} ({})]: ".format(str(user['username']), profile) # Completion setup stuff + for i in mastodon.lists(): + bisect.insort(completion_list, i['title'].lower()) for i in mastodon.account_following(user['id'], limit=80): bisect.insort(completion_list, '@' + i['acct']) readline.set_completer(complete) From 72f3e6ddd842757c7ea5e41d6e83b3c270fe74cf Mon Sep 17 00:00:00 2001 From: Craig Maloney Date: Wed, 27 Dec 2017 13:01:58 -0500 Subject: [PATCH 05/20] Added listrename --- src/tootstream/toot.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/tootstream/toot.py b/src/tootstream/toot.py index ffb6c8d..a16b80d 100644 --- a/src/tootstream/toot.py +++ b/src/tootstream/toot.py @@ -1716,6 +1716,34 @@ def listadd(mastodon, rest): listadd.__section__ = 'List' +@command +def listrename(mastodon, rest): + """update list.""" + rest = rest.strip() + if not rest: + cprint("Argument required.", fg('red')) + return + items = rest.split(' ') + if len(items) < 2: + cprint("Not enough arguments.", fg('red')) + return + + list_id = get_list_id(mastodon, items[0]) + updated_name = items[1] + + if not list_id: + cprint("List {} is not found".format(items[0]), fg('red')) + return + + try: + mastodon.list_update(list_id, updated_name) + cprint("Renamed {} to {}.".format(items[1], items[0]), fg('green')) + except Exception as e: + cprint("error while updating list: {}".format(type(e).__name__), fg('red')) +listrename.__argstr__ = '' +listrename.__section__ = 'List' + + @command def listremove(mastodon, rest): """Remove user from list.""" From 50b0233fafcabf0e56791933890eb9d8c951dd7b Mon Sep 17 00:00:00 2001 From: Craig Maloney Date: Wed, 27 Dec 2017 20:30:02 -0500 Subject: [PATCH 06/20] Fixing up help for list commands --- src/tootstream/toot.py | 99 ++++++++++++++++++++++++------------------ 1 file changed, 57 insertions(+), 42 deletions(-) diff --git a/src/tootstream/toot.py b/src/tootstream/toot.py index a16b80d..03cfe9f 100644 --- a/src/tootstream/toot.py +++ b/src/tootstream/toot.py @@ -1069,7 +1069,9 @@ def local(mastodon, rest): @command def stream(mastodon, rest): - """Streams a timeline. Specify home, fed, local, or a #hashtagname. + """Streams a timeline. Specify home, fed, local, list, or a #hashtagname. + +Timeline 'list' requires a list name (ex: stream list listname). Use ctrl+C to end streaming""" print("Use ctrl+C to end streaming") @@ -1607,7 +1609,7 @@ def quit(mastodon, rest): @command def lists(mastodon, rest): - """Shows the lists that the user has created """ + """Shows the lists that the user has created.""" user_lists = mastodon.lists() for list_item in user_lists: printList(list_item) @@ -1617,7 +1619,7 @@ def lists(mastodon, rest): @command def listcreate(mastodon, rest): - """Creates a list """ + """Creates a list.""" try: mastodon.list_create(rest) @@ -1625,13 +1627,44 @@ def listcreate(mastodon, rest): except Exception as e: cprint("error while creating list: {}".format(type(e).__name__), fg('red')) return -listcreate.__argstr__ = '' +listcreate.__argstr__ = '' listcreate.__section__ = 'List' +@command +def listrename(mastodon, rest): + """Rename a list. + ex: listrename oldlist newlist""" + rest = rest.strip() + if not rest: + cprint("Argument required.", fg('red')) + return + items = rest.split(' ') + if len(items) < 2: + cprint("Not enough arguments.", fg('red')) + return + + list_id = get_list_id(mastodon, items[0]) + updated_name = items[1] + + if not list_id: + cprint("List {} is not found".format(items[0]), fg('red')) + return + + try: + mastodon.list_update(list_id, updated_name) + cprint("Renamed {} to {}.".format(items[1], items[0]), fg('green')) + except Exception as e: + cprint("error while updating list: {}".format(type(e).__name__), fg('red')) +listrename.__argstr__ = ' ' +listrename.__section__ = 'List' + + @command def listdestroy(mastodon, rest): - """Destroys a list """ + """Destroys a list. + ex: listdestroy listname + listdestroy 23""" item = get_list_id(mastodon, rest) if not item or item == -1: cprint("List {} is not found".format(rest), fg('red')) @@ -1643,12 +1676,15 @@ def listdestroy(mastodon, rest): except Exception as e: cprint("error while creating list: {}".format(type(e).__name__), fg('red')) return -listdestroy.__argstr__ = '' +listdestroy.__argstr__ = '' listdestroy.__section__ = 'List' + @command def listhome(mastodon, rest): - """Show the toots from a list""" + """Show the toots from a list. + ex: listhome listname + listhome 23""" if not rest: cprint("Argument required.", fg('red')) return @@ -1664,13 +1700,15 @@ def listhome(mastodon, rest): completion_add(toot) except Exception as e: cprint("error while displaying list: {}".format(type(e).__name__), fg('red')) -listhome.__argstr__ = '' +listhome.__argstr__ = '' listhome.__section__ = 'List' @command def listaccounts(mastodon, rest): - """Show the accounts for the list """ + """Show the accounts for the list. + ex: listaccounts listname + listaccounts 23""" item = get_list_id(mastodon, rest) if not item: cprint("List {} is not found".format(rest), fg('red')) @@ -1681,13 +1719,15 @@ def listaccounts(mastodon, rest): for user in list_accounts: printUser(user) -listaccounts.__argstr__ = '' +listaccounts.__argstr__ = '' listaccounts.__section__ = 'List' @command def listadd(mastodon, rest): - """Add user to list.""" + """Add user to list. + ex: listadd listname @user@instance.example.com + listadd 23 @user@instance.example.com""" if not rest: cprint("Argument required.", fg('red')) return @@ -1712,41 +1752,16 @@ def listadd(mastodon, rest): cprint("Added {} to list {}.".format(items[1], items[0]), fg('green')) except Exception as e: cprint("error while adding to list: {}".format(type(e).__name__), fg('red')) -listadd.__argstr__ = '' +listadd.__argstr__ = ' ' listadd.__section__ = 'List' -@command -def listrename(mastodon, rest): - """update list.""" - rest = rest.strip() - if not rest: - cprint("Argument required.", fg('red')) - return - items = rest.split(' ') - if len(items) < 2: - cprint("Not enough arguments.", fg('red')) - return - - list_id = get_list_id(mastodon, items[0]) - updated_name = items[1] - - if not list_id: - cprint("List {} is not found".format(items[0]), fg('red')) - return - - try: - mastodon.list_update(list_id, updated_name) - cprint("Renamed {} to {}.".format(items[1], items[0]), fg('green')) - except Exception as e: - cprint("error while updating list: {}".format(type(e).__name__), fg('red')) -listrename.__argstr__ = '' -listrename.__section__ = 'List' - - @command def listremove(mastodon, rest): - """Remove user from list.""" + """Remove user from list. + ex: listremove list user@instance.example.com + listremove 23 user@instance.example.com + listremove 23 42""" if not rest: cprint("Argument required.", fg('red')) return @@ -1771,7 +1786,7 @@ def listremove(mastodon, rest): cprint("Removed {} from list {}.".format(items[1], items[0]), fg('green')) except Exception as e: cprint("error while deleting from list: {}".format(type(e).__name__), fg('red')) -listremove.__argstr__ = '' +listremove.__argstr__ = ' ' listremove.__section__ = 'List' ##################################### ######### END COMMAND BLOCK ######### From b52740713d17dd6cd84d2fdc375b637e5f43ed29 Mon Sep 17 00:00:00 2001 From: Craig Maloney Date: Wed, 27 Dec 2017 21:23:14 -0500 Subject: [PATCH 07/20] Adding emoji to shortcode --- src/tootstream/toot.py | 6 +++++- src/tootstream/toot_parser.py | 40 +++++++++++++++++++++++------------ 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/src/tootstream/toot.py b/src/tootstream/toot.py index 0ba20c4..772764f 100644 --- a/src/tootstream/toot.py +++ b/src/tootstream/toot.py @@ -86,7 +86,11 @@ def on_update(self, status): # Get the current width of the terminal terminal_size = shutil.get_terminal_size((80, 20)) -toot_parser = TootParser(indent=' ', width=int(terminal_size.columns) - 2) +toot_parser = TootParser( + indent=' ', + width=int(terminal_size.columns) - 2, + convert_emoji_to_unicode=False, + convert_emoji_to_shortcode=False) toot_listener = TootListener() diff --git a/src/tootstream/toot_parser.py b/src/tootstream/toot_parser.py index 92446f9..ce52675 100644 --- a/src/tootstream/toot_parser.py +++ b/src/tootstream/toot_parser.py @@ -4,14 +4,19 @@ from textwrap import TextWrapper -def convert_emoji_shortcodes(text): +def emoji_shortcode_to_unicode(text): """Convert standard emoji short codes to unicode emoji in the provided text. text - The text to parse. Returns the modified text. """ - return emoji.emojize(text, use_aliases = True) + return emoji.emojize(text, use_aliases=True) + + +def emoji_unicode_to_shortcodes(text): + """Convert unicode emoji to standard emoji short codes.""" + return emoji.demojize(text) def find_attr(name, attrs): @@ -76,21 +81,24 @@ class TootParser(HTMLParser): indent - A string to prepend to all lines in the output text. width - The maximum number of characters to allow in a line of text. shorten_links - Whether or not to shorten links. - convert_emoji - Whether or not to convert emoji short codes to unicode. + convert_emoji_to_unicode - Whether or not to convert emoji short codes to unicode. + convert_emoji_to_shortcode - Whether or not to convert emoji unicode to short codes unicode. link_style - The colored style to apply to generic links. mention_style - The colored style to apply to mentions. hashtag_style - The colored style to apply to hashtags. """ - def __init__(self, - indent = '', - width = 0, - convert_emoji = False, - shorten_links = False, - link_style = None, - mention_style = None, - hashtag_style = None): + def __init__( + self, + indent='', + width=0, + convert_emoji_to_unicode=False, + convert_emoji_to_shortcode=False, + shorten_links=False, + link_style=None, + mention_style=None, + hashtag_style=None): super().__init__() self.reset() @@ -98,7 +106,8 @@ def __init__(self, self.convert_charrefs = True self.indent = indent - self.convert_emoji = convert_emoji + self.convert_emoji_to_unicode = convert_emoji_to_unicode + self.convert_emoji_to_shortcode = convert_emoji_to_shortcode self.shorten_links = shorten_links self.link_style = link_style self.mention_style = mention_style @@ -139,8 +148,11 @@ def handle_data(self, data): if self.hide: return - if self.convert_emoji: - data = convert_emoji_shortcodes(data) + if self.convert_emoji_to_unicode: + data = emoji_shortcode_to_unicode(data) + + if self.convert_emoji_to_shortcode: + data = emoji_unicode_to_shortcodes(data) self.fed.append(data) From e26f873c9ffa2c34986265d9021e0159aaf0cb68 Mon Sep 17 00:00:00 2001 From: Craig Maloney Date: Fri, 29 Dec 2017 23:49:00 -0500 Subject: [PATCH 08/20] Added demojize to usernames, added global config variable --- src/tootstream/toot.py | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/src/tootstream/toot.py b/src/tootstream/toot.py index 772764f..a27fe50 100644 --- a/src/tootstream/toot.py +++ b/src/tootstream/toot.py @@ -15,11 +15,15 @@ import datetime import dateutil import shutil +import emoji # Get the version of Tootstream import pkg_resources # part of setuptools version = pkg_resources.require("tootstream")[0].version +# placeholder variable for converting enoji to shortcodes until we get it in config +convert_emoji_to_shortcode = True + #Looks best with black background. #TODO: Set color list in config file COLORS = list(range(19,231)) @@ -90,7 +94,7 @@ def on_update(self, status): indent=' ', width=int(terminal_size.columns) - 2, convert_emoji_to_unicode=False, - convert_emoji_to_shortcode=False) + convert_emoji_to_shortcode=convert_emoji_to_shortcode) toot_listener = TootListener() @@ -479,8 +483,8 @@ def cprint(text, style, end="\n"): def format_username(user): """Get a user's account name including lock indicator.""" - return ''.join(( "@", user['acct'], - (" {}".format(GLYPHS['locked']) if user['locked'] else "") )) + return ''.join(("@", user['acct'], + (" {}".format(GLYPHS['locked']) if user['locked'] else ""))) def format_user_counts(user): @@ -491,12 +495,20 @@ def format_user_counts(user): countfmt.format(GLYPHS['followed_by'], user['followers_count']) )) +def format_display_name(name): + if convert_emoji_to_shortcode: + name = emoji.demojize(name) + return name + return name + + def printUser(user): """Prints user data nicely with hardcoded colors.""" counts = stylize(format_user_counts(user), fg('blue')) print(format_username(user) + " " + counts) - cprint(user['display_name'], fg('cyan')) + display_name = format_display_name(user['disply_name']) + cprint(display_name, fg('cyan')) print(user['url']) cprint(re.sub('<[^<]+?>', '', user['note']), fg('red')) @@ -505,7 +517,8 @@ def printUsersShort(users): for user in users: if not user: continue userid = "(id:"+str(user['id'])+")" - userdisp = "'"+str(user['display_name'])+"'" + display_name = format_display_name(user['display_name']) + userdisp = "'"+str(display_name)+"'" userurl = str(user['url']) cprint(" "+format_username(user), fg('green'), end=" ") cprint(" "+userid, fg('red'), end=" ") @@ -535,7 +548,8 @@ def format_toot_nameline(toot, dnamestyle): if not toot: return '' formatted_time = format_time(toot['created_at']) - out = [stylize(toot['account']['display_name'], dnamestyle), + display_name = format_display_name(toot['account']['display_name']) + out = [stylize(display_name, dnamestyle), stylize(format_username(toot['account']), fg('green')), stylize(formatted_time, attr('dim'))] return ' '.join(out) @@ -573,7 +587,8 @@ def printToot(toot): # then get other data from toot['reblog'] if toot['reblog']: header = stylize(" Boosted by ", fg('yellow')) - name = " ".join(( toot['account']['display_name'], + display_name = format_display_name(toot['account']['display_name']) + name = " ".join(( display_name, format_username(toot['account'])+":" )) out.append(header + stylize(name, fg('blue'))) toot = toot['reblog'] @@ -1094,7 +1109,7 @@ def note(mastodon, rest): return for note in reversed(mastodon.notifications()): - display_name = " " + note['account']['display_name'] + display_name = " " + format_display_name(note['account']['display_name']) username = format_username(note['account']) note_id = str(note['id']) From ab1c623eefbf4c47355c656d52df6a4dd2815226 Mon Sep 17 00:00:00 2001 From: Craig Maloney Date: Wed, 3 Jan 2018 20:29:38 -0500 Subject: [PATCH 09/20] Adding a check to see if lists are supported --- src/tootstream/toot.py | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/src/tootstream/toot.py b/src/tootstream/toot.py index 03cfe9f..3c9f775 100644 --- a/src/tootstream/toot.py +++ b/src/tootstream/toot.py @@ -90,9 +90,19 @@ def on_update(self, status): toot_listener = TootListener() + ##################################### ######## UTILITY FUNCTIONS ######## ##################################### + +def list_support(mastodon, silent=False): + lists_available = mastodon.verify_minimum_version("2.1.0") + if lists_available is False and silent is False: + cprint("List support is not available with this version of Mastodon", + fg('red')) + return lists_available + + def get_content(toot): html = toot['content'] toot_parser.parse(html) @@ -150,7 +160,6 @@ def get_list_id(mastodon, rest): rest = rest.strip() - # not an int lists = mastodon.lists() for item in lists: if item['title'].lower() == rest.lower(): @@ -1610,6 +1619,8 @@ def quit(mastodon, rest): @command def lists(mastodon, rest): """Shows the lists that the user has created.""" + if not(list_support(mastodon)): + return user_lists = mastodon.lists() for list_item in user_lists: printList(list_item) @@ -1620,7 +1631,8 @@ def lists(mastodon, rest): @command def listcreate(mastodon, rest): """Creates a list.""" - + if not(list_support(mastodon)): + return try: mastodon.list_create(rest) cprint("List {} created.".format(rest), fg('green')) @@ -1635,6 +1647,8 @@ def listcreate(mastodon, rest): def listrename(mastodon, rest): """Rename a list. ex: listrename oldlist newlist""" + if not(list_supportmastodon()): + return rest = rest.strip() if not rest: cprint("Argument required.", fg('red')) @@ -1665,6 +1679,8 @@ def listdestroy(mastodon, rest): """Destroys a list. ex: listdestroy listname listdestroy 23""" + if not(list_support(mastodon)): + return item = get_list_id(mastodon, rest) if not item or item == -1: cprint("List {} is not found".format(rest), fg('red')) @@ -1685,6 +1701,8 @@ def listhome(mastodon, rest): """Show the toots from a list. ex: listhome listname listhome 23""" + if not(list_support(mastodon)): + return if not rest: cprint("Argument required.", fg('red')) return @@ -1709,6 +1727,8 @@ def listaccounts(mastodon, rest): """Show the accounts for the list. ex: listaccounts listname listaccounts 23""" + if not(list_support(mastodon)): + return item = get_list_id(mastodon, rest) if not item: cprint("List {} is not found".format(rest), fg('red')) @@ -1728,6 +1748,8 @@ def listadd(mastodon, rest): """Add user to list. ex: listadd listname @user@instance.example.com listadd 23 @user@instance.example.com""" + if not(list_support(mastodon)): + return if not rest: cprint("Argument required.", fg('red')) return @@ -1762,6 +1784,8 @@ def listremove(mastodon, rest): ex: listremove list user@instance.example.com listremove 23 user@instance.example.com listremove 23 42""" + if not(list_support(mastodon)): + return if not rest: cprint("Argument required.", fg('red')) return @@ -1842,6 +1866,7 @@ def main(instance, config, profile): access_token=token, api_base_url="https://" + instance) + # update config before writing if "token" not in config[profile]: config[profile] = { @@ -1865,8 +1890,10 @@ def main(instance, config, profile): prompt = "[@{} ({})]: ".format(str(user['username']), profile) # Completion setup stuff - for i in mastodon.lists(): - bisect.insort(completion_list, i['title'].lower()) + if list_support(mastodon, silent=True): + for i in mastodon.lists(): + bisect.insort(completion_list, i['title'].lower()) + for i in mastodon.account_following(user['id'], limit=80): bisect.insort(completion_list, '@' + i['acct']) readline.set_completer(complete) From d1bb95bc53c0e22ce0f3e1756112a8c97d5722de Mon Sep 17 00:00:00 2001 From: Craig Maloney Date: Sun, 14 Jan 2018 10:17:52 -0500 Subject: [PATCH 10/20] Adding message for no lists found --- src/tootstream/toot.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/tootstream/toot.py b/src/tootstream/toot.py index 3c9f775..7bc4851 100644 --- a/src/tootstream/toot.py +++ b/src/tootstream/toot.py @@ -1622,6 +1622,9 @@ def lists(mastodon, rest): if not(list_support(mastodon)): return user_lists = mastodon.lists() + if len(user_lists) == 0: + cprint("No lists found", fg('red')) + return for list_item in user_lists: printList(list_item) lists.__argstr__ = '' From 1297e40239575ff7b0d699dcfcf13fbe405805af Mon Sep 17 00:00:00 2001 From: Craig Maloney Date: Sun, 14 Jan 2018 10:23:14 -0500 Subject: [PATCH 11/20] Default shortcode emoji to False --- src/tootstream/toot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tootstream/toot.py b/src/tootstream/toot.py index a27fe50..612cbe4 100644 --- a/src/tootstream/toot.py +++ b/src/tootstream/toot.py @@ -22,7 +22,7 @@ version = pkg_resources.require("tootstream")[0].version # placeholder variable for converting enoji to shortcodes until we get it in config -convert_emoji_to_shortcode = True +convert_emoji_to_shortcode = False #Looks best with black background. #TODO: Set color list in config file From 64d76eaf2fc725bbc6e1654d09a03f2d1b64b2a0 Mon Sep 17 00:00:00 2001 From: Craig Maloney Date: Wed, 17 Jan 2018 21:20:08 -0500 Subject: [PATCH 12/20] Fixing boosting private toots --- src/tootstream/toot.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/tootstream/toot.py b/src/tootstream/toot.py index 612cbe4..bd32ade 100644 --- a/src/tootstream/toot.py +++ b/src/tootstream/toot.py @@ -888,10 +888,15 @@ def boost(mastodon, rest): rest = IDS.to_global(rest) if rest is None: return - mastodon.status_reblog(rest) - boosted = mastodon.status(rest) - msg = " You boosted: ", fg('white') + get_content(boosted) - cprint(msg, fg('green')) + try: + mastodon.status_reblog(rest) + boosted = mastodon.status(rest) + msg = " You boosted: ", fg('white') + get_content(boosted) + cprint(msg, fg('green')) + except Exception as e: + cprint("Received error: ", fg('red') + attr('bold'), end="") + cprint(e, fg('magenta') + attr('bold') + attr('underlined')) + boost.__argstr__ = '' boost.__section__ = 'Toots' From 790d216d3a4ca9f6dc6b53c06db624766b5baa33 Mon Sep 17 00:00:00 2001 From: Craig Maloney Date: Sat, 20 Jan 2018 00:01:56 -0500 Subject: [PATCH 13/20] Adding exception handling for errors while streaming --- src/tootstream/toot.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/tootstream/toot.py b/src/tootstream/toot.py index 612cbe4..a0354db 100644 --- a/src/tootstream/toot.py +++ b/src/tootstream/toot.py @@ -888,10 +888,15 @@ def boost(mastodon, rest): rest = IDS.to_global(rest) if rest is None: return - mastodon.status_reblog(rest) - boosted = mastodon.status(rest) - msg = " You boosted: ", fg('white') + get_content(boosted) - cprint(msg, fg('green')) + try: + mastodon.status_reblog(rest) + boosted = mastodon.status(rest) + msg = " You boosted: ", fg('white') + get_content(boosted) + cprint(msg, fg('green')) + except Exception as e: + cprint("Received error: ", fg('red') + attr('bold'), end="") + cprint(e, fg('magenta') + attr('bold') + attr('underlined')) + boost.__argstr__ = '' boost.__section__ = 'Toots' @@ -1078,6 +1083,8 @@ def stream(mastodon, rest): print("Only 'home', 'fed', 'local', and '#hashtag' streams are supported.") except KeyboardInterrupt: pass + except Exception as e: + cprint("Something went wrong: {}".format(e), fg('red')) stream.__argstr__ = '' stream.__section__ = 'Timeline' From 19c3ec49aab6e0f796d4748a18fc7d762427b835 Mon Sep 17 00:00:00 2001 From: Craig Maloney Date: Sat, 20 Jan 2018 00:16:23 -0500 Subject: [PATCH 14/20] Fixing typo that caused crash with searching user names --- src/tootstream/toot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tootstream/toot.py b/src/tootstream/toot.py index a0354db..d478e36 100644 --- a/src/tootstream/toot.py +++ b/src/tootstream/toot.py @@ -507,7 +507,7 @@ def printUser(user): counts = stylize(format_user_counts(user), fg('blue')) print(format_username(user) + " " + counts) - display_name = format_display_name(user['disply_name']) + display_name = format_display_name(user['display_name']) cprint(display_name, fg('cyan')) print(user['url']) cprint(re.sub('<[^<]+?>', '', user['note']), fg('red')) From 517c0f534cdb271ef82938bace5b0b03e0108b07 Mon Sep 17 00:00:00 2001 From: Craig Maloney Date: Sun, 21 Jan 2018 10:07:07 -0500 Subject: [PATCH 15/20] Updated documentation: * Detailed the configuration file * Detailed how to switch profiles for different instances. --- README.md | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fe61589..c58cb05 100644 --- a/README.md +++ b/README.md @@ -56,11 +56,23 @@ $ python3 setup.py install $ source /path/to/tootstream/bin/activate ``` -2: Run the script +2: Run the program ``` $ tootstream ``` -3: Close the environment with `$ deactivate` + +3: Use the ``help`` command to see the available commands +``` +[@myusername (default)]: help +``` + +4: Exit the program when finished +``` +[@myusername (default)]: quit + +``` + +5: Close the environment with `$ deactivate` ## Ubuntu and Unicode @@ -70,6 +82,26 @@ Tootstream relies heavily on Unicode fonts. The best experience can be had by in $ sudo apt-get install ttf-ancient-fonts ``` +## Configuration + +By default tootstream uses [configparser](https://docs.python.org/3/library/configparser.html) for configuration. The default configuration is stored in the default location for configparser (on the developer's machine this is under /home/myusername/.config/tootstream/tootstream.conf). + +At the moment tootstream only stores login information for each instance in the configuration file. Each instance is under its own section (the default configuration is under the ``[default]`` section). Multiple instances can be stored in the ``tootstream.conf`` file. (See "Using multiple instances") + +## Using multiple instances + +Tootstream supports using accounts on multiple Mastodon instances. + +Use the ``--instance`` parameter to pass the server location (in the case of Mastodon.social we'd use ``--instance mastodon.social``). + +Use the ``--profile`` parameter to use a different named profile. (in the case of Mastodon.social we could call it ``mastodon.social`` and name the section using ``--profile mastodon.social``). + +By default tootstream uses the ``[default]`` profile. If this already has an instance associated with it then tootstream will default to using that instance. + +If you have already set up a profile you may use the ``--profile`` command-line switch to start tootstream with it. The ``--instance`` parameter is optional (and redundant). + +You may select a different configuration using ``--config`` and pass it the full-path to that file. + ## Contributing Contributions welcome! Please read the [contributing guidelines](CONTRIBUTING.md) before getting started. From 638cfb79150c751576ac9d5026b69844ea6963ca Mon Sep 17 00:00:00 2001 From: Craig Maloney Date: Wed, 31 Jan 2018 20:20:27 -0500 Subject: [PATCH 16/20] Mastodon.py version bump --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 5ab4b94..06287b4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ click>=6.7 -Mastodon.py==1.2.1 +Mastodon.py==1.2.2 colored>=1.3.5 humanize>=0.5.1 emoji>=0.4.5 From 9d763757eeef787313b0b9c695abec9c964199a3 Mon Sep 17 00:00:00 2001 From: Lightwave Feesh Date: Wed, 7 Feb 2018 19:27:39 -0600 Subject: [PATCH 17/20] Fix: Correctly format the boost message. --- src/tootstream/toot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tootstream/toot.py b/src/tootstream/toot.py index d478e36..dcae018 100644 --- a/src/tootstream/toot.py +++ b/src/tootstream/toot.py @@ -891,7 +891,7 @@ def boost(mastodon, rest): try: mastodon.status_reblog(rest) boosted = mastodon.status(rest) - msg = " You boosted: ", fg('white') + get_content(boosted) + msg = " You boosted: " + fg('white') + get_content(boosted) cprint(msg, fg('green')) except Exception as e: cprint("Received error: ", fg('red') + attr('bold'), end="") From 498d0d3c76c801e0c713b58f9a7adad261a0da0e Mon Sep 17 00:00:00 2001 From: Craig Maloney Date: Sat, 17 Feb 2018 10:07:03 -0500 Subject: [PATCH 18/20] Version bump --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 8616f02..a6e3df1 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ from setuptools import setup, find_packages setup( name="tootstream", - version="0.3.2", + version="0.3.3", python_requires=">=3", install_requires=[line.strip() for line in open('requirements.txt')], From 8dd228f6cce31a66a7bf5f7b83df3d2d5c4de61d Mon Sep 17 00:00:00 2001 From: Craig Maloney Date: Sat, 17 Feb 2018 10:29:01 -0500 Subject: [PATCH 19/20] Updated the Changelog --- CHANGELOG.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2dd1e4a..7832cdc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,26 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +### Release +### [0.3.3] - TBD + +#### Added +- List support for servers that support it. (See ``help list`` for more details.) +- Bumped to Mastodon.py 1.2.2 + +#### Added (awaiting proper config) +( The following items are active but require a re-working of the configuration file to make active. Currently they are flags inside the ``toot_parser.py`` file. Intrepid explorers may find them.) +- Added emoji shortcode (defaults to "off"). +- Added emoji "demoji" to show shortcodes for emoji (defaults to off). + +#### Fixed +- Fixed boosting private toots +- Fixed message for boosting toots +- Fixed leading / trailing whitespace from media filepath +- Added better exception handling around streaming API + +#### + ### Release ### [0.3.2] - 2017-12-23 From 0332aa82f9569bacd37c2a3d41c2408b02b85314 Mon Sep 17 00:00:00 2001 From: Craig Maloney Date: Sat, 17 Feb 2018 21:28:30 -0500 Subject: [PATCH 20/20] Adding release date --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7832cdc..7580234 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). ### Release -### [0.3.3] - TBD +### [0.3.3] - 2018-02-17 #### Added - List support for servers that support it. (See ``help list`` for more details.)