diff --git a/tests/test_console.py b/tests/test_console.py index 3f28e04..53bb5e6 100644 --- a/tests/test_console.py +++ b/tests/test_console.py @@ -121,6 +121,7 @@ def test_delete(mock_delete, capsys): @mock.patch('toot.http.get') def test_timeline(mock_get, monkeypatch, capsys): mock_get.return_value = MockResponse([{ + 'id': '111111111111111111', 'account': { 'display_name': 'Frank Zappa', 'username': 'fz' @@ -128,6 +129,7 @@ def test_timeline(mock_get, monkeypatch, capsys): 'created_at': '2017-04-12T15:53:18.174Z', 'content': "

The computer can't tell you the emotional story. It can give you the exact mathematical design, but what's missing is the eyebrows.

", 'reblog': None, + 'in_reply_to_id': None }]) console.run_command(app, user, 'timeline', []) @@ -139,7 +141,94 @@ def test_timeline(mock_get, monkeypatch, capsys): assert "but what's missing is the eyebrows." in out assert "Frank Zappa" in out assert "@fz" in out + assert "id: 111111111111111111" in out + assert "[RE]" not in out +@mock.patch('toot.http.get') +def test_timeline_with_re(mock_get, monkeypatch, capsys): + mock_get.return_value = MockResponse([{ + 'id': '111111111111111111', + 'account': { + 'display_name': 'Frank Zappa', + 'username': 'fz' + }, + 'created_at': '2017-04-12T15:53:18.174Z', + 'content': "

The computer can't tell you the emotional story. It can give you the exact mathematical design, but what's missing is the eyebrows.

", + 'reblog': None, + 'in_reply_to_id': '111111111111111110' + }]) + + console.run_command(app, user, 'timeline', []) + + mock_get.assert_called_once_with(app, user, '/api/v1/timelines/home') + + out, err = capsys.readouterr() + assert "The computer can't tell you the emotional story." in out + assert "but what's missing is the eyebrows." in out + assert "Frank Zappa" in out + assert "@fz" in out + assert "id: 111111111111111111" in out + assert "[RE]" in out + +@mock.patch('toot.http.get') +def test_thread(mock_get, monkeypatch, capsys): + mock_get.side_effect = [ + MockResponse({ + 'id': '111111111111111111', + 'account': { + 'display_name': 'Frank Zappa', + 'username': 'fz' + }, + 'created_at': '2017-04-12T15:53:18.174Z', + 'content': "my response in the middle", + 'reblog': None, + 'in_reply_to_id': '111111111111111110' + }), + MockResponse({ + 'ancestors': [{ + 'id': '111111111111111110', + 'account': { + 'display_name': 'Frank Zappa', + 'username': 'fz' + }, + 'created_at': '2017-04-12T15:53:18.174Z', + 'content': "original content", + 'reblog': None, + 'in_reply_to_id': None}], + 'descendants': [{ + 'id': '111111111111111112', + 'account': { + 'display_name': 'Frank Zappa', + 'username': 'fz' + }, + 'created_at': '2017-04-12T15:53:18.174Z', + 'content': "response message", + 'reblog': None, + 'in_reply_to_id': '111111111111111111'}], + }), + ] + + console.run_command(app, user, 'thread', ['111111111111111111']) + + calls = [ + mock.call(app, user, '/api/v1/statuses/111111111111111111'), + mock.call(app, user, '/api/v1/statuses/111111111111111111/context'), + ] + mock_get.assert_has_calls(calls, any_order=False) + + out, err = capsys.readouterr() + + # Display order + assert out.index('original content') < out.index('my response in the middle') + assert out.index('my response in the middle') < out.index('response message') + + assert "original content" in out + assert "my response in the middle" in out + assert "response message" in out + assert "Frank Zappa" in out + assert "@fz" in out + assert "id: 111111111111111111" in out + assert "[RE]" in out @mock.patch('toot.http.post') def test_upload(mock_post, capsys): diff --git a/toot/api.py b/toot/api.py index e0cca77..e302f84 100644 --- a/toot/api.py +++ b/toot/api.py @@ -17,10 +17,17 @@ def _account_action(app, user, account, action): return http.post(app, user, url).json() -def _status_action(app, user, status_id, action): - url = '/api/v1/statuses/{}/{}'.format(status_id, action) +def _status_action(app, user, status_id, action, method='post'): + if action is None: + url = '/api/v1/statuses/{}'.format(status_id) + method = 'get' + else: + url = '/api/v1/statuses/{}/{}'.format(status_id, action) - return http.post(app, user, url).json() + if method == 'post': + return http.post(app, user, url).json() + elif method == 'get': + return http.get(app, user, url).json() def create_app(domain, scheme='https'): @@ -143,6 +150,8 @@ def pin(app, user, status_id): def unpin(app, user, status_id): return _status_action(app, user, status_id, 'unpin') +def context(app, user, status_id): + return _status_action(app, user, status_id, 'context', method='get') def timeline_home(app, user): return http.get(app, user, '/api/v1/timelines/home').json() @@ -245,6 +254,9 @@ def verify_credentials(app, user): return http.get(app, user, '/api/v1/accounts/verify_credentials').json() +def single_status(app, user, status_id): + return _status_action(app, user, status_id, None, method='get') + def get_notifications(app, user): return http.get(app, user, '/api/v1/notifications').json() diff --git a/toot/commands.py b/toot/commands.py index 4d9342b..9542f04 100644 --- a/toot/commands.py +++ b/toot/commands.py @@ -29,6 +29,19 @@ def timeline(app, user, args): print_timeline(items) +def thread(app, user, args): + toot = api.single_status(app, user, args.status_id) + context = api.context(app, user, args.status_id) + thread = [] + for item in context['ancestors']: + thread.append(item) + + thread.append(toot) + + for item in context['descendants']: + thread.append(item) + + print_timeline(thread) def curses(app, user, args): from toot.ui.app import TimelineApp diff --git a/toot/console.py b/toot/console.py index c6e5363..5b45eff 100644 --- a/toot/console.py +++ b/toot/console.py @@ -154,6 +154,16 @@ READ_COMMANDS = [ ], require_auth=True, ), + Command( + name="thread", + description="Show toot thfread items", + arguments=[ + (["status_id"], { + "help": "Show thread for toot.", + }), + ], + require_auth=True, + ), Command( name="timeline", description="Show recent items in a timeline (home by default)", diff --git a/toot/output.py b/toot/output.py index 36fec5f..60c034d 100644 --- a/toot/output.py +++ b/toot/output.py @@ -137,6 +137,11 @@ def print_timeline(items): if item['reblogged']: left_column.append("Reblogged @{}".format(item['reblogged'])) + if item['reply_to_toot'] is not None: + left_column.append('[RE]') + + left_column.append("id: {}".format(item['id'])) + right_column = wrap_text(item['text'], 80) return zip_longest(left_column, right_column, fillvalue="") @@ -153,10 +158,12 @@ def print_timeline(items): time = datetime.strptime(item['created_at'], "%Y-%m-%dT%H:%M:%S.%fZ") return { + "id": item['id'], "account": item['account'], "text": text, "time": time, "reblogged": reblogged, + "reply_to_toot": item['in_reply_to_id'] } print_out("─" * 31 + "┬" + "─" * 88)