From e55474158aa0d64e453025a90b0ac017d456a501 Mon Sep 17 00:00:00 2001 From: Daniel Schwarz Date: Wed, 17 May 2023 20:23:46 -0400 Subject: [PATCH] Added toot timeline --account_tl console command Displays the timeline of any account (public timeline for users who are not you, public+private for you) Right now it does not display reblogs or replies, this could be configurable if we add more command line flags --- toot/api.py | 31 +++++++++++++++++++++++++++++ toot/commands.py | 51 ++++++++++++++---------------------------------- toot/console.py | 9 +++++++++ 3 files changed, 55 insertions(+), 36 deletions(-) diff --git a/toot/api.py b/toot/api.py index 8557c74..f0857a9 100644 --- a/toot/api.py +++ b/toot/api.py @@ -10,9 +10,33 @@ from toot import App, User, http, CLIENT_NAME, CLIENT_WEBSITE from toot.exceptions import AuthenticationError, ConsoleError from toot.utils import drop_empty_values, str_bool, str_bool_nullable + SCOPES = 'read write follow' +def find_account(app, user, account_name): + if not account_name: + raise ConsoleError("Empty account name given") + + normalized_name = account_name.lstrip("@").lower() + + # Strip @ from accounts on the local instance. The `acct` + # field in account object contains the qualified name for users of other + # instances, but only the username for users of the local instance. This is + # required in order to match the account name below. + if "@" in normalized_name: + [username, instance] = normalized_name.split("@", maxsplit=1) + if instance == app.instance: + normalized_name = username + + response = search(app, user, account_name, type="accounts", resolve=True) + for account in response["accounts"]: + if account["acct"].lower() == normalized_name: + return account + + raise ConsoleError("Account not found") + + def _account_action(app, user, account, action): url = f"/api/v1/accounts/{account}/{action}" return http.post(app, user, url).json() @@ -350,6 +374,13 @@ def conversation_timeline_generator(app, user, limit=20): return _conversation_timeline_generator(app, user, path, params) +def account_timeline_generator(app: App, user: User, account_name: str, replies=False, reblogs=False, limit=20): + account = find_account(app, user, account_name) + path = f"/api/v1/accounts/{account['id']}/statuses" + params = {"limit": limit, "exclude_replies": not replies, "exclude_reblogs": not reblogs} + return _timeline_generator(app, user, path, params) + + def timeline_list_generator(app, user, list_id, limit=20): path = f"/api/v1/timelines/list/{list_id}" return _timeline_generator(app, user, path, {'limit': limit}) diff --git a/toot/commands.py b/toot/commands.py index 0cc92d2..9df6a6f 100644 --- a/toot/commands.py +++ b/toot/commands.py @@ -16,8 +16,8 @@ from toot.utils import args_get_instance, delete_tmp_status_file, editor_input, def get_timeline_generator(app, user, args): # 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 len([arg for arg in [args.tag, args.list, args.public, args.account_tl] if arg]) > 1: + raise ConsoleError("Only one of --public, --tag, --account_tl, 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.") @@ -35,6 +35,8 @@ def get_timeline_generator(app, user, args): return api.anon_tag_timeline_generator(args.instance, args.tag, limit=args.count) else: return api.tag_timeline_generator(app, user, args.tag, local=args.local, limit=args.count) + elif args.account_tl: + return api.account_timeline_generator(app, user, args.account, limit=args.count) elif args.list: return api.timeline_list_generator(app, user, args.list, limit=args.count) else: @@ -360,49 +362,26 @@ def _do_upload(app, user, file, description, thumbnail): return api.upload_media(app, user, file, description=description, thumbnail=thumbnail) -def find_account(app, user, account_name): - if not account_name: - raise ConsoleError("Empty account name given") - - normalized_name = account_name.lstrip("@").lower() - - # Strip @ from accounts on the local instance. The `acct` - # field in account object contains the qualified name for users of other - # instances, but only the username for users of the local instance. This is - # required in order to match the account name below. - if "@" in normalized_name: - [username, instance] = normalized_name.split("@", maxsplit=1) - if instance == app.instance: - normalized_name = username - - response = api.search(app, user, account_name, type="accounts", resolve=True) - for account in response["accounts"]: - if account["acct"].lower() == normalized_name: - return account - - raise ConsoleError("Account not found") - - def follow(app, user, args): - account = find_account(app, user, args.account) + account = api.find_account(app, user, args.account) api.follow(app, user, account['id']) print_out("✓ You are now following {}".format(args.account)) def unfollow(app, user, args): - account = find_account(app, user, args.account) + account = api.find_account(app, user, args.account) api.unfollow(app, user, account['id']) print_out("✓ You are no longer following {}".format(args.account)) def following(app, user, args): - account = find_account(app, user, args.account) + account = api.find_account(app, user, args.account) response = api.following(app, user, account['id']) print_acct_list(response) def followers(app, user, args): - account = find_account(app, user, args.account) + account = api.find_account(app, user, args.account) response = api.followers(app, user, account['id']) print_acct_list(response) @@ -452,7 +431,7 @@ def list_delete(app, user, args): def list_add(app, user, args): list_id = _get_list_id(app, user, args) - account = find_account(app, user, args.account) + account = api.find_account(app, user, args.account) try: api.add_accounts_to_list(app, user, list_id, [account['id']]) @@ -477,7 +456,7 @@ def list_add(app, user, args): def list_remove(app, user, args): list_id = _get_list_id(app, user, args) - account = find_account(app, user, args.account) + account = api.find_account(app, user, args.account) api.remove_accounts_from_list(app, user, list_id, [account['id']]) print_out(f"✓ Removed account \"{args.account}\"") @@ -490,25 +469,25 @@ def _get_list_id(app, user, args): def mute(app, user, args): - account = find_account(app, user, args.account) + account = api.find_account(app, user, args.account) api.mute(app, user, account['id']) print_out("✓ You have muted {}".format(args.account)) def unmute(app, user, args): - account = find_account(app, user, args.account) + account = api.find_account(app, user, args.account) api.unmute(app, user, account['id']) print_out("✓ {} is no longer muted".format(args.account)) def block(app, user, args): - account = find_account(app, user, args.account) + account = api.find_account(app, user, args.account) api.block(app, user, account['id']) print_out("✓ You are now blocking {}".format(args.account)) def unblock(app, user, args): - account = find_account(app, user, args.account) + account = api.find_account(app, user, args.account) api.unblock(app, user, account['id']) print_out("✓ {} is no longer blocked".format(args.account)) @@ -519,7 +498,7 @@ def whoami(app, user, args): def whois(app, user, args): - account = find_account(app, user, args.account) + account = api.find_account(app, user, args.account) print_account(account) diff --git a/toot/console.py b/toot/console.py index 5c84d97..f1eab39 100644 --- a/toot/console.py +++ b/toot/console.py @@ -238,6 +238,11 @@ common_timeline_args = [ "type": str, "help": "show hashtag timeline (does not require auth)", }), + (["-at", "--account_tl"], { + "action": "store_true", + "default": False, + "help": "show account timeline (requires account name)", + }), (["-l", "--local"], { "action": "store_true", "default": False, @@ -251,6 +256,10 @@ common_timeline_args = [ "type": str, "help": "show timeline for given list.", }), + (["account"], { + "nargs": "?", + "help": "account name, e.g. 'Gargron@mastodon.social'", + }), ] timeline_and_bookmark_args = [