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:
parent
ec95ef3584
commit
ee417df60e
5 changed files with 134 additions and 3 deletions
|
@ -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'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'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'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):
|
||||||
|
|
18
toot/api.py
18
toot/api.py
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)",
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue