Status ID + thread view

- Status ID on timeline list view
 - thread command to view a complete thread
   Display order:
    - ancestors
    - status
    - descendants
This commit is contained in:
Balazs Nadasdi 2019-01-19 18:38:17 +01:00
parent ec95ef3584
commit ee417df60e
5 changed files with 134 additions and 3 deletions

View file

@ -121,6 +121,7 @@ def test_delete(mock_delete, capsys):
@mock.patch('toot.http.get') @mock.patch('toot.http.get')
def test_timeline(mock_get, monkeypatch, capsys): def test_timeline(mock_get, monkeypatch, capsys):
mock_get.return_value = MockResponse([{ mock_get.return_value = MockResponse([{
'id': '111111111111111111',
'account': { 'account': {
'display_name': 'Frank Zappa', 'display_name': 'Frank Zappa',
'username': 'fz' 'username': 'fz'
@ -128,6 +129,7 @@ def test_timeline(mock_get, monkeypatch, capsys):
'created_at': '2017-04-12T15:53:18.174Z', 'created_at': '2017-04-12T15:53:18.174Z',
'content': "<p>The computer can&apos;t tell you the emotional story. It can give you the exact mathematical design, but what's missing is the eyebrows.</p>", 'content': "<p>The computer can&apos;t tell you the emotional story. It can give you the exact mathematical design, but what's missing is the eyebrows.</p>",
'reblog': None, 'reblog': None,
'in_reply_to_id': None
}]) }])
console.run_command(app, user, 'timeline', []) 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 "but what's missing is the eyebrows." in out
assert "Frank Zappa" in out assert "Frank Zappa" in out
assert "@fz" 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': "<p>The computer can&apos;t tell you the emotional story. It can give you the exact mathematical design, but what's missing is the eyebrows.</p>",
'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') @mock.patch('toot.http.post')
def test_upload(mock_post, capsys): def test_upload(mock_post, capsys):

View file

@ -17,10 +17,17 @@ def _account_action(app, user, account, action):
return http.post(app, user, url).json() return http.post(app, user, url).json()
def _status_action(app, user, status_id, action): def _status_action(app, user, status_id, action, method='post'):
url = '/api/v1/statuses/{}/{}'.format(status_id, action) 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'): def create_app(domain, scheme='https'):
@ -143,6 +150,8 @@ def pin(app, user, status_id):
def unpin(app, user, status_id): def unpin(app, user, status_id):
return _status_action(app, user, status_id, 'unpin') 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): def timeline_home(app, user):
return http.get(app, user, '/api/v1/timelines/home').json() 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() 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): def get_notifications(app, user):
return http.get(app, user, '/api/v1/notifications').json() return http.get(app, user, '/api/v1/notifications').json()

View file

@ -29,6 +29,19 @@ def timeline(app, user, args):
print_timeline(items) 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): def curses(app, user, args):
from toot.ui.app import TimelineApp from toot.ui.app import TimelineApp

View file

@ -154,6 +154,16 @@ READ_COMMANDS = [
], ],
require_auth=True, require_auth=True,
), ),
Command(
name="thread",
description="Show toot thfread items",
arguments=[
(["status_id"], {
"help": "Show thread for toot.",
}),
],
require_auth=True,
),
Command( Command(
name="timeline", name="timeline",
description="Show recent items in a timeline (home by default)", description="Show recent items in a timeline (home by default)",

View file

@ -137,6 +137,11 @@ def print_timeline(items):
if item['reblogged']: if item['reblogged']:
left_column.append("Reblogged @{}".format(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) right_column = wrap_text(item['text'], 80)
return zip_longest(left_column, right_column, fillvalue="") 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") time = datetime.strptime(item['created_at'], "%Y-%m-%dT%H:%M:%S.%fZ")
return { return {
"id": item['id'],
"account": item['account'], "account": item['account'],
"text": text, "text": text,
"time": time, "time": time,
"reblogged": reblogged, "reblogged": reblogged,
"reply_to_toot": item['in_reply_to_id']
} }
print_out("" * 31 + "" + "" * 88) print_out("" * 31 + "" + "" * 88)