Merge pull request #65 from ihabunek/timelines
Add support for public, tag and list timelines
This commit is contained in:
commit
2391a39855
6 changed files with 71 additions and 18 deletions
|
@ -3,6 +3,7 @@ Changelog
|
||||||
|
|
||||||
**0.18.0 (TBA)**
|
**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)
|
* Add `--sensitive` and `--spoiler-text` options to `toot post` (#63)
|
||||||
|
|
||||||
**0.17.1 (2018-01-15)**
|
**0.17.1 (2018-01-15)**
|
||||||
|
|
|
@ -109,7 +109,8 @@ def test_timeline(mock_get, monkeypatch, capsys):
|
||||||
|
|
||||||
out, err = capsys.readouterr()
|
out, err = capsys.readouterr()
|
||||||
assert "The computer can't tell you the emotional story." in out
|
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')
|
@mock.patch('toot.http.post')
|
||||||
|
|
20
toot/api.py
20
toot/api.py
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import re
|
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 import http, CLIENT_NAME, CLIENT_WEBSITE
|
||||||
from toot.exceptions import AuthenticationError
|
from toot.exceptions import AuthenticationError
|
||||||
|
@ -55,7 +55,7 @@ def get_browser_login_url(app):
|
||||||
return "{}/oauth/authorize/?{}".format(app.base_url, urlencode({
|
return "{}/oauth/authorize/?{}".format(app.base_url, urlencode({
|
||||||
"response_type": "code",
|
"response_type": "code",
|
||||||
"redirect_uri": "urn:ietf:wg:oauth:2.0:oob",
|
"redirect_uri": "urn:ietf:wg:oauth:2.0:oob",
|
||||||
"scope": "read write follow",
|
"scope": SCOPES,
|
||||||
"client_id": app.client_id,
|
"client_id": app.client_id,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
@ -91,6 +91,22 @@ 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()
|
||||||
|
|
||||||
|
|
||||||
|
def timeline_public(app, user, local=False):
|
||||||
|
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):
|
def get_next_path(headers):
|
||||||
"""Given timeline response headers, returns the path to the next batch"""
|
"""Given timeline response headers, returns the path to the next batch"""
|
||||||
links = headers.get('Link', '')
|
links = headers.get('Link', '')
|
||||||
|
|
|
@ -8,7 +8,22 @@ from toot.utils import assert_domain_exists
|
||||||
|
|
||||||
|
|
||||||
def timeline(app, user, args):
|
def timeline(app, user, args):
|
||||||
items = api.timeline_home(app, user)
|
# Make sure tag, list and public are not used simultaneously
|
||||||
|
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.")
|
||||||
|
|
||||||
|
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:
|
||||||
|
items = api.timeline_list(app, user, args.list)
|
||||||
|
else:
|
||||||
|
items = api.timeline_home(app, user)
|
||||||
|
|
||||||
print_timeline(items)
|
print_timeline(items)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -131,8 +131,27 @@ READ_COMMANDS = [
|
||||||
),
|
),
|
||||||
Command(
|
Command(
|
||||||
name="timeline",
|
name="timeline",
|
||||||
description="Show recent items in your public timeline",
|
description="Show recent items in a timeline (home by default)",
|
||||||
arguments=[],
|
arguments=[
|
||||||
|
(["-p", "--public"], {
|
||||||
|
"action": "store_true",
|
||||||
|
"default": False,
|
||||||
|
"help": "Show public timeline.",
|
||||||
|
}),
|
||||||
|
(["-t", "--tag"], {
|
||||||
|
"type": str,
|
||||||
|
"help": "Show timeline for given hashtag.",
|
||||||
|
}),
|
||||||
|
(["-i", "--list"], {
|
||||||
|
"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,
|
require_auth=True,
|
||||||
),
|
),
|
||||||
Command(
|
Command(
|
||||||
|
|
|
@ -9,7 +9,7 @@ from itertools import chain
|
||||||
from itertools import zip_longest
|
from itertools import zip_longest
|
||||||
from textwrap import wrap, TextWrapper
|
from textwrap import wrap, TextWrapper
|
||||||
|
|
||||||
from toot.utils import format_content, get_text
|
from toot.utils import format_content, get_text, trunc
|
||||||
|
|
||||||
START_CODES = {
|
START_CODES = {
|
||||||
'red': '\033[31m',
|
'red': '\033[31m',
|
||||||
|
@ -124,33 +124,34 @@ def print_timeline(items):
|
||||||
return chain(*[wrapper.wrap(l) for l in text.split("\n")])
|
return chain(*[wrapper.wrap(l) for l in text.split("\n")])
|
||||||
|
|
||||||
def timeline_rows(item):
|
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')
|
time = item['time'].strftime('%Y-%m-%d %H:%M%Z')
|
||||||
|
|
||||||
left_column = [name, time]
|
left_column = [display_name]
|
||||||
if 'reblogged' in item:
|
if display_name != username:
|
||||||
left_column.append(item['reblogged'])
|
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(item['text'], 80)
|
||||||
|
|
||||||
right_column = wrap_text(text, 80)
|
|
||||||
|
|
||||||
return zip_longest(left_column, right_column, fillvalue="")
|
return zip_longest(left_column, right_column, fillvalue="")
|
||||||
|
|
||||||
for left, right in timeline_rows(item):
|
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):
|
def _parse_item(item):
|
||||||
content = item['reblog']['content'] if item['reblog'] else item['content']
|
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")
|
soup = BeautifulSoup(content, "html.parser")
|
||||||
text = soup.get_text().replace(''', "'")
|
text = soup.get_text().replace(''', "'")
|
||||||
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 {
|
||||||
"name": name,
|
"account": item['account'],
|
||||||
"text": text,
|
"text": text,
|
||||||
"time": time,
|
"time": time,
|
||||||
"reblogged": reblogged,
|
"reblogged": reblogged,
|
||||||
|
|
Loading…
Reference in a new issue