From 09a138ba27fa446da34cb79b3ecd7c9f2f1c5823 Mon Sep 17 00:00:00 2001 From: kakakaya Date: Tue, 6 Mar 2018 00:10:34 +0900 Subject: [PATCH 1/8] Use SCOPES const --- toot/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/toot/api.py b/toot/api.py index 7d31367..479fc36 100644 --- a/toot/api.py +++ b/toot/api.py @@ -55,7 +55,7 @@ def get_browser_login_url(app): return "{}/oauth/authorize/?{}".format(app.base_url, urlencode({ "response_type": "code", "redirect_uri": "urn:ietf:wg:oauth:2.0:oob", - "scope": "read write follow", + "scope": SCOPES, "client_id": app.client_id, })) From 08c5226ae2d4df67d73259f01b3a058bf1c85c0a Mon Sep 17 00:00:00 2001 From: kakakaya Date: Tue, 6 Mar 2018 03:00:37 +0900 Subject: [PATCH 2/8] Add api.timeline_public method --- toot/api.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/toot/api.py b/toot/api.py index 479fc36..59622c2 100644 --- a/toot/api.py +++ b/toot/api.py @@ -91,6 +91,10 @@ def timeline_home(app, user): return http.get(app, user, '/api/v1/timelines/home').json() +def timeline_public(app, user, local=False): + return http.get(app, user, '/api/v1/timelines/public', {'local': 'true' if local else 'false'}).json() + + def get_next_path(headers): """Given timeline response headers, returns the path to the next batch""" links = headers.get('Link', '') From 406943237a7058b51b7932fa81d184c2f0d552d9 Mon Sep 17 00:00:00 2001 From: kakakaya Date: Tue, 6 Mar 2018 03:13:45 +0900 Subject: [PATCH 3/8] Support -l/--local argument in timeline command --- toot/commands.py | 5 ++++- toot/console.py | 8 +++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/toot/commands.py b/toot/commands.py index 6a98a0d..16b44a3 100644 --- a/toot/commands.py +++ b/toot/commands.py @@ -8,7 +8,10 @@ from toot.utils import assert_domain_exists def timeline(app, user, args): - items = api.timeline_home(app, user) + if args.local: + items = api.timeline_public(app, user, local=True) + else: + items = api.timeline_home(app, user) print_timeline(items) diff --git a/toot/console.py b/toot/console.py index 8613775..80d3a45 100644 --- a/toot/console.py +++ b/toot/console.py @@ -132,7 +132,13 @@ READ_COMMANDS = [ Command( name="timeline", description="Show recent items in your public timeline", - arguments=[], + arguments=[ + (["-l", "--local"], { + "action": 'store_true', + "default": False, + "help": "Show local timeline instead of public timeline.", + }), + ], require_auth=True, ), Command( From e1cfda1acb749b273055fbcb6ecf42914f60d705 Mon Sep 17 00:00:00 2001 From: Ivan Habunek Date: Tue, 12 Jun 2018 10:40:36 +0200 Subject: [PATCH 4/8] Add support for tag and list timelines --- toot/api.py | 16 ++++++++++++++-- toot/commands.py | 16 ++++++++++++++-- toot/console.py | 21 +++++++++++++++++---- 3 files changed, 45 insertions(+), 8 deletions(-) diff --git a/toot/api.py b/toot/api.py index 59622c2..a5bde26 100644 --- a/toot/api.py +++ b/toot/api.py @@ -2,7 +2,7 @@ import re -from urllib.parse import urlparse, urlencode +from urllib.parse import urlparse, urlencode, quote from toot import http, CLIENT_NAME, CLIENT_WEBSITE from toot.exceptions import AuthenticationError @@ -92,7 +92,19 @@ def timeline_home(app, user): def timeline_public(app, user, local=False): - return http.get(app, user, '/api/v1/timelines/public', {'local': 'true' if local else 'false'}).json() + params = {'local': 'true' if local else 'false'} + return http.get(app, user, '/api/v1/timelines/public', params).json() + + +def timeline_tag(app, user, hashtag, local=False): + url = '/api/v1/timelines/tag/{}'.format(quote(hashtag)) + params = {'local': 'true' if local else 'false'} + return http.get(app, user, url, params).json() + + +def timeline_list(app, user, list_id): + url = '/api/v1/timelines/list/{}'.format(list_id) + return http.get(app, user, url).json() def get_next_path(headers): diff --git a/toot/commands.py b/toot/commands.py index 16b44a3..6fcad6f 100644 --- a/toot/commands.py +++ b/toot/commands.py @@ -8,10 +8,22 @@ from toot.utils import assert_domain_exists def timeline(app, user, args): - if args.local: - items = api.timeline_public(app, user, local=True) + # Make sure tag, list and public are not used simultaneously + if len([arg for arg in [args.tag, args.list_id, args.public] if arg]) > 1: + raise ConsoleError("Only one of --public --tag --list-id can be used at one time.") + + if args.local and not (args.public or args.tag): + raise ConsoleError("The --local option is only valid alongside --public or --tag.") + + if args.public: + items = api.timeline_public(app, user, local=args.local) + elif args.tag: + items = api.timeline_tag(app, user, args.tag, local=args.local) + elif args.list_id: + items = api.timeline_list(app, user, args.list_id) else: items = api.timeline_home(app, user) + print_timeline(items) diff --git a/toot/console.py b/toot/console.py index 80d3a45..fa47bd6 100644 --- a/toot/console.py +++ b/toot/console.py @@ -131,12 +131,25 @@ READ_COMMANDS = [ ), Command( name="timeline", - description="Show recent items in your public timeline", + description="Show recent items in a timeline (home by default)", arguments=[ - (["-l", "--local"], { - "action": 'store_true', + (["-p", "--public"], { + "action": "store_true", "default": False, - "help": "Show local timeline instead of public timeline.", + "help": "Show public timeline.", + }), + (["-t", "--tag"], { + "type": str, + "help": "Show timeline for given hashtag.", + }), + (["-i", "--list-id"], { + "type": int, + "help": "Show timeline for given list ID.", + }), + (["-l", "--local"], { + "action": "store_true", + "default": False, + "help": "Show only statuses from local instance (public and tag timelines only).", }), ], require_auth=True, From 3f79b76aabbc480a7d6513d53455b033fff4242a Mon Sep 17 00:00:00 2001 From: Ivan Habunek Date: Tue, 12 Jun 2018 10:52:47 +0200 Subject: [PATCH 5/8] Fix alignment in timeline --- toot/output.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/toot/output.py b/toot/output.py index 536cba2..f019de1 100644 --- a/toot/output.py +++ b/toot/output.py @@ -9,7 +9,7 @@ from itertools import chain from itertools import zip_longest from textwrap import wrap, TextWrapper -from toot.utils import format_content, get_text +from toot.utils import format_content, get_text, trunc START_CODES = { 'red': '\033[31m', @@ -138,7 +138,7 @@ def print_timeline(items): return zip_longest(left_column, right_column, fillvalue="") for left, right in timeline_rows(item): - print_out("{:30} │ {}".format(left, right)) + print_out("{:30} │ {}".format(trunc(left, 30), right)) def _parse_item(item): content = item['reblog']['content'] if item['reblog'] else item['content'] From b3d81c43cc65aacb790bf309e0ee477776f797b0 Mon Sep 17 00:00:00 2001 From: Ivan Habunek Date: Tue, 12 Jun 2018 11:36:24 +0200 Subject: [PATCH 6/8] Tweak timeline output --- tests/test_console.py | 3 ++- toot/output.py | 21 +++++++++++---------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/tests/test_console.py b/tests/test_console.py index eabc1bc..198349e 100644 --- a/tests/test_console.py +++ b/tests/test_console.py @@ -109,7 +109,8 @@ def test_timeline(mock_get, monkeypatch, capsys): out, err = capsys.readouterr() assert "The computer can't tell you the emotional story." in out - assert "Frank Zappa @fz" in out + assert "Frank Zappa" in out + assert "@fz" in out @mock.patch('toot.http.post') diff --git a/toot/output.py b/toot/output.py index f019de1..6d17fab 100644 --- a/toot/output.py +++ b/toot/output.py @@ -124,16 +124,18 @@ def print_timeline(items): return chain(*[wrapper.wrap(l) for l in text.split("\n")]) def timeline_rows(item): - name = item['name'] + display_name = item['account']['display_name'] + username = "@" + item['account']['username'] time = item['time'].strftime('%Y-%m-%d %H:%M%Z') - left_column = [name, time] - if 'reblogged' in item: - left_column.append(item['reblogged']) + left_column = [display_name] + if display_name != username: + left_column.append(username) + left_column.append(time) + if item['reblogged']: + left_column.append("Reblogged @{}".format(item['reblogged'])) - text = item['text'] - - right_column = wrap_text(text, 80) + right_column = wrap_text(item['text'], 80) return zip_longest(left_column, right_column, fillvalue="") @@ -142,15 +144,14 @@ def print_timeline(items): def _parse_item(item): content = item['reblog']['content'] if item['reblog'] else item['content'] - reblogged = item['reblog']['account']['username'] if item['reblog'] else "" + reblogged = item['reblog']['account']['username'] if item['reblog'] else None - name = item['account']['display_name'] + " @" + item['account']['username'] soup = BeautifulSoup(content, "html.parser") text = soup.get_text().replace(''', "'") time = datetime.strptime(item['created_at'], "%Y-%m-%dT%H:%M:%S.%fZ") return { - "name": name, + "account": item['account'], "text": text, "time": time, "reblogged": reblogged, From c26ccc13f92d813338b5239a4aa0cda34466a457 Mon Sep 17 00:00:00 2001 From: Ivan Habunek Date: Tue, 12 Jun 2018 11:42:28 +0200 Subject: [PATCH 7/8] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c6932c..b5628ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ Changelog **0.18.0 (TBA)** +* Add support for public, tag and list timelines in `toot timeline` (#52) * Add `--sensitive` and `--spoiler-text` options to `toot post` (#63) **0.17.1 (2018-01-15)** From 10f68fdab775620a8715e6341117ad5080bb7f0c Mon Sep 17 00:00:00 2001 From: Ivan Habunek Date: Tue, 12 Jun 2018 11:53:10 +0200 Subject: [PATCH 8/8] Replace --list-id with --list verbosity ftw --- toot/commands.py | 8 ++++---- toot/console.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/toot/commands.py b/toot/commands.py index 6fcad6f..67e4fa1 100644 --- a/toot/commands.py +++ b/toot/commands.py @@ -9,8 +9,8 @@ from toot.utils import assert_domain_exists def timeline(app, user, args): # Make sure tag, list and public are not used simultaneously - if len([arg for arg in [args.tag, args.list_id, args.public] if arg]) > 1: - raise ConsoleError("Only one of --public --tag --list-id can be used at one time.") + if len([arg for arg in [args.tag, args.list, args.public] if arg]) > 1: + raise ConsoleError("Only one of --public, --tag, or --list can be used at one time.") if args.local and not (args.public or args.tag): raise ConsoleError("The --local option is only valid alongside --public or --tag.") @@ -19,8 +19,8 @@ def timeline(app, user, args): items = api.timeline_public(app, user, local=args.local) elif args.tag: items = api.timeline_tag(app, user, args.tag, local=args.local) - elif args.list_id: - items = api.timeline_list(app, user, args.list_id) + elif args.list: + items = api.timeline_list(app, user, args.list) else: items = api.timeline_home(app, user) diff --git a/toot/console.py b/toot/console.py index fa47bd6..f829985 100644 --- a/toot/console.py +++ b/toot/console.py @@ -142,7 +142,7 @@ READ_COMMANDS = [ "type": str, "help": "Show timeline for given hashtag.", }), - (["-i", "--list-id"], { + (["-i", "--list"], { "type": int, "help": "Show timeline for given list ID.", }),