From 2199ca18b5b025b440edb59abe1e83d1c9a0eef0 Mon Sep 17 00:00:00 2001 From: Denis Laxalde Date: Sun, 26 Jan 2020 11:23:39 +0100 Subject: [PATCH 01/31] Document the [M]edia action --- toot/tui/overlays.py | 1 + 1 file changed, 1 insertion(+) diff --git a/toot/tui/overlays.py b/toot/tui/overlays.py index 64f7872..af9fb29 100644 --- a/toot/tui/overlays.py +++ b/toot/tui/overlays.py @@ -131,6 +131,7 @@ class Help(urwid.Padding): yield urwid.Text(h(" [F] - Favourite/unfavourite status")) yield urwid.Text(h(" [R] - Reply to current status")) yield urwid.Text(h(" [S] - Show text marked as sensitive")) + yield urwid.Text(h(" [M] - Show status media")) yield urwid.Text(h(" [T] - Show status thread (replies)")) yield urwid.Text(h(" [U] - Show the status data in JSON as received from the server")) yield urwid.Text(h(" [V] - Open status in default browser")) From 9f0c94bce118e5a904602aae390dfa398cbf7cc1 Mon Sep 17 00:00:00 2001 From: Ivan Habunek Date: Fri, 22 Dec 2023 09:44:13 +0100 Subject: [PATCH 02/31] Update readme --- CHANGELOG.md | 17 ++++++++--------- changelog.yaml | 19 ++++++++++--------- docs/changelog.md | 17 ++++++++--------- 3 files changed, 26 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 032d844..5b260e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,18 +5,17 @@ Changelog **0.40.0 (TBA)** -This release includes a major rewrite to use -[Click](https://click.palletsprojects.com/) for creating the command line -interface. This allows for some new features like nested commands, setting -parameters via environment variables, and shell completion. See docs for -details. Backward compatibility should be mostly preserved, except for cases -noted below please report any issues. +This release includes a rather extensive change to use the Click library +(https://click.palletsprojects.com/) for creating the command line interface. +This allows for some new features like nested commands, setting parameters via +environment variables, and shell completion. Backward compatibility should be +mostly preserved, except for cases noted below. Please report any issues. * BREAKING: Remove deprecated `--disable-https` option for `login` and `login_cli`, pass the base URL instead * BREAKING: Options `--debug`, `--color`, `--quiet` must be specified after `toot` but before the command -* Enable passing params via environment variables, see: +* Add passing parameters via environment variables, see: https://toot.bezdomni.net/environment_variables.html * Add shell completion, see: https://toot.bezdomni.net/shell_completion.html * Add `tags info`, `tags featured`, `tags feature`, and `tags unfeature` @@ -26,11 +25,11 @@ noted below please report any issues. * Add `lists accounts`, `lists add`, `lists create`, `lists delete`, `lists list`, `lists remove` subcommands, deprecate `lists`, `lists_accounts`, `lists_add`, `lists_create`, `lists_delete`, `lists_remove` commands. -* Add `--json` option to tags commands -* Add `--json` option to lists commands +* Add `--json` option to tags and lists commands * Add `toot --width` option for setting your prefered terminal width * Add `--media-viewer` and `--colors` options to `toot tui`. These were previously accessible only via settings. +* TUI: Fix issue where UI did not render until first input (thanks Urwid devs) **0.39.0 (2023-11-23)** diff --git a/changelog.yaml b/changelog.yaml index 2d48e53..0cc21a4 100644 --- a/changelog.yaml +++ b/changelog.yaml @@ -1,23 +1,24 @@ 0.40.0: - date: TBA + date: SOON description: | - This release includes a major rewrite to use [Click](https://click.palletsprojects.com/) for - creating the command line interface. This allows for some new features like nested commands, - setting parameters via environment variables, and shell completion. See docs for details. - Backward compatibility should be mostly preserved, except for cases noted below please report - any issues. + This release includes a rather extensive change to use the Click library + (https://click.palletsprojects.com/) for creating the command line + interface. This allows for some new features like nested commands, setting + parameters via environment variables, and shell completion. Backward + compatibility should be mostly preserved, except for cases noted below. + Please report any issues. changes: - "BREAKING: Remove deprecated `--disable-https` option for `login` and `login_cli`, pass the base URL instead" - "BREAKING: Options `--debug`, `--color`, `--quiet` must be specified after `toot` but before the command" - - "Enable passing params via environment variables, see: https://toot.bezdomni.net/environment_variables.html" + - "Add passing parameters via environment variables, see: https://toot.bezdomni.net/environment_variables.html" - "Add shell completion, see: https://toot.bezdomni.net/shell_completion.html" - "Add `tags info`, `tags featured`, `tags feature`, and `tags unfeature` commands" - "Add `tags followed`, `tags follow`, and `tags unfollow` sub-commands, deprecate `tags_followed`, `tags_follow`, and `tags tags_unfollow`" - "Add `lists accounts`, `lists add`, `lists create`, `lists delete`, `lists list`, `lists remove` subcommands, deprecate `lists`, `lists_accounts`, `lists_add`, `lists_create`, `lists_delete`, `lists_remove` commands." - - "Add `--json` option to tags commands" - - "Add `--json` option to lists commands" + - "Add `--json` option to tags and lists commands" - "Add `toot --width` option for setting your prefered terminal width" - "Add `--media-viewer` and `--colors` options to `toot tui`. These were previously accessible only via settings." + - "TUI: Fix issue where UI did not render until first input (thanks Urwid devs)" 0.39.0: date: 2023-11-23 diff --git a/docs/changelog.md b/docs/changelog.md index 032d844..5b260e9 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -5,18 +5,17 @@ Changelog **0.40.0 (TBA)** -This release includes a major rewrite to use -[Click](https://click.palletsprojects.com/) for creating the command line -interface. This allows for some new features like nested commands, setting -parameters via environment variables, and shell completion. See docs for -details. Backward compatibility should be mostly preserved, except for cases -noted below please report any issues. +This release includes a rather extensive change to use the Click library +(https://click.palletsprojects.com/) for creating the command line interface. +This allows for some new features like nested commands, setting parameters via +environment variables, and shell completion. Backward compatibility should be +mostly preserved, except for cases noted below. Please report any issues. * BREAKING: Remove deprecated `--disable-https` option for `login` and `login_cli`, pass the base URL instead * BREAKING: Options `--debug`, `--color`, `--quiet` must be specified after `toot` but before the command -* Enable passing params via environment variables, see: +* Add passing parameters via environment variables, see: https://toot.bezdomni.net/environment_variables.html * Add shell completion, see: https://toot.bezdomni.net/shell_completion.html * Add `tags info`, `tags featured`, `tags feature`, and `tags unfeature` @@ -26,11 +25,11 @@ noted below please report any issues. * Add `lists accounts`, `lists add`, `lists create`, `lists delete`, `lists list`, `lists remove` subcommands, deprecate `lists`, `lists_accounts`, `lists_add`, `lists_create`, `lists_delete`, `lists_remove` commands. -* Add `--json` option to tags commands -* Add `--json` option to lists commands +* Add `--json` option to tags and lists commands * Add `toot --width` option for setting your prefered terminal width * Add `--media-viewer` and `--colors` options to `toot tui`. These were previously accessible only via settings. +* TUI: Fix issue where UI did not render until first input (thanks Urwid devs) **0.39.0 (2023-11-23)** From 11bc102cc85df88300a79369bad83d50b90a971b Mon Sep 17 00:00:00 2001 From: Ivan Habunek Date: Tue, 26 Dec 2023 09:48:31 +0100 Subject: [PATCH 03/31] Read [tui] section to preserve BC --- toot/cli/__init__.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/toot/cli/__init__.py b/toot/cli/__init__.py index 6e888a6..d57a722 100644 --- a/toot/cli/__init__.py +++ b/toot/cli/__init__.py @@ -8,6 +8,7 @@ from click.testing import Result from functools import wraps from toot import App, User, config, __version__ from toot.settings import get_settings +from toot.output import print_warning if t.TYPE_CHECKING: import typing_extensions as te @@ -47,6 +48,14 @@ def get_default_map(): settings = get_settings() common = settings.get("common", {}) commands = settings.get("commands", {}) + + # TODO: remove in version 1.0 + tui_old = settings.get("tui", {}) + if tui_old: + print_warning("Settings section [tui] has been deprecated in favour of [commands.tui].") + tui_new = commands.get("tui", {}) + commands["tui"] = {**tui_old, **tui_new} + return {**common, **commands} From 44b6f9fcf468b734e391ca04fe8f878fea3480fd Mon Sep 17 00:00:00 2001 From: Sandra Snan Date: Sun, 24 Dec 2023 12:14:48 +0100 Subject: [PATCH 04/31] Drop Pleroma Emoji Reactions I made this a list so you can add other types to drop easily but if this is premature generalization we could make it a scalar instead. --- toot/output.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/toot/output.py b/toot/output.py index 266f467..bc85c0d 100644 --- a/toot/output.py +++ b/toot/output.py @@ -274,8 +274,9 @@ def print_notification(notification: Notification): def print_notifications(notifications: List[Notification]): for notification in notifications: - print_divider() - print_notification(notification) + if notification.type not in ['pleroma:emoji_reaction']: + print_divider() + print_notification(notification) print_divider() From eeb90dc21cff03749d3becff680ee73558cd3440 Mon Sep 17 00:00:00 2001 From: Ivan Habunek Date: Tue, 26 Dec 2023 10:35:41 +0100 Subject: [PATCH 05/31] Remove --quiet flag --- CHANGELOG.md | 7 ++++--- changelog.yaml | 3 ++- docs/changelog.md | 7 ++++--- toot/cli/__init__.py | 9 +++------ 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b260e9..57f379d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ Changelog -**0.40.0 (TBA)** +**0.40.0 (SOON)** This release includes a rather extensive change to use the Click library (https://click.palletsprojects.com/) for creating the command line interface. @@ -13,8 +13,9 @@ mostly preserved, except for cases noted below. Please report any issues. * BREAKING: Remove deprecated `--disable-https` option for `login` and `login_cli`, pass the base URL instead -* BREAKING: Options `--debug`, `--color`, `--quiet` must be specified after - `toot` but before the command +* BREAKING: Options `--debug` and `--color` must be specified after `toot` but + before the command +* BREAKING: Option `--quiet` has been removed. Redirect output instead. * Add passing parameters via environment variables, see: https://toot.bezdomni.net/environment_variables.html * Add shell completion, see: https://toot.bezdomni.net/shell_completion.html diff --git a/changelog.yaml b/changelog.yaml index 0cc21a4..b775c44 100644 --- a/changelog.yaml +++ b/changelog.yaml @@ -9,7 +9,8 @@ Please report any issues. changes: - "BREAKING: Remove deprecated `--disable-https` option for `login` and `login_cli`, pass the base URL instead" - - "BREAKING: Options `--debug`, `--color`, `--quiet` must be specified after `toot` but before the command" + - "BREAKING: Options `--debug` and `--color` must be specified after `toot` but before the command" + - "BREAKING: Option `--quiet` has been removed. Redirect output instead." - "Add passing parameters via environment variables, see: https://toot.bezdomni.net/environment_variables.html" - "Add shell completion, see: https://toot.bezdomni.net/shell_completion.html" - "Add `tags info`, `tags featured`, `tags feature`, and `tags unfeature` commands" diff --git a/docs/changelog.md b/docs/changelog.md index 5b260e9..57f379d 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -3,7 +3,7 @@ Changelog -**0.40.0 (TBA)** +**0.40.0 (SOON)** This release includes a rather extensive change to use the Click library (https://click.palletsprojects.com/) for creating the command line interface. @@ -13,8 +13,9 @@ mostly preserved, except for cases noted below. Please report any issues. * BREAKING: Remove deprecated `--disable-https` option for `login` and `login_cli`, pass the base URL instead -* BREAKING: Options `--debug`, `--color`, `--quiet` must be specified after - `toot` but before the command +* BREAKING: Options `--debug` and `--color` must be specified after `toot` but + before the command +* BREAKING: Option `--quiet` has been removed. Redirect output instead. * Add passing parameters via environment variables, see: https://toot.bezdomni.net/environment_variables.html * Add shell completion, see: https://toot.bezdomni.net/shell_completion.html diff --git a/toot/cli/__init__.py b/toot/cli/__init__.py index d57a722..8e44af0 100644 --- a/toot/cli/__init__.py +++ b/toot/cli/__init__.py @@ -78,14 +78,12 @@ class Context(t.NamedTuple): user: t.Optional[User] = None color: bool = False debug: bool = False - quiet: bool = False class TootObj(t.NamedTuple): """Data to add to Click context""" color: bool = True debug: bool = False - quiet: bool = False # Pass a context for testing purposes test_ctx: t.Optional[Context] = None @@ -111,7 +109,7 @@ def get_context() -> Context: if not user or not app: raise click.ClickException("This command requires you to be logged in.") - return Context(app, user, obj.color, obj.debug, obj.quiet) + return Context(app, user, obj.color, obj.debug) json_option = click.option( @@ -126,12 +124,11 @@ json_option = click.option( @click.option("-w", "--max-width", type=int, default=80, help="Maximum width for content rendered by toot") @click.option("--debug/--no-debug", default=False, help="Log debug info to stderr") @click.option("--color/--no-color", default=sys.stdout.isatty(), help="Use ANSI color in output") -@click.option("--quiet/--no-quiet", default=False, help="Don't print anything to stdout") @click.version_option(__version__, message="%(prog)s v%(version)s") @click.pass_context -def cli(ctx: click.Context, max_width: int, color: bool, debug: bool, quiet: bool): +def cli(ctx: click.Context, max_width: int, color: bool, debug: bool): """Toot is a Mastodon CLI""" - ctx.obj = TootObj(color, debug, quiet) + ctx.obj = TootObj(color, debug) ctx.color = color ctx.max_content_width = max_width From 94d753292984fe98389d79718f3a1b8a30f755cc Mon Sep 17 00:00:00 2001 From: Ivan Habunek Date: Wed, 27 Dec 2023 10:01:29 +0100 Subject: [PATCH 06/31] Set release date --- CHANGELOG.md | 2 +- changelog.yaml | 2 +- docs/changelog.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 57f379d..be8edb5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ Changelog -**0.40.0 (SOON)** +**0.40.0 (2023-12-27)** This release includes a rather extensive change to use the Click library (https://click.palletsprojects.com/) for creating the command line interface. diff --git a/changelog.yaml b/changelog.yaml index b775c44..7de1d4c 100644 --- a/changelog.yaml +++ b/changelog.yaml @@ -1,5 +1,5 @@ 0.40.0: - date: SOON + date: 2023-12-27 description: | This release includes a rather extensive change to use the Click library (https://click.palletsprojects.com/) for creating the command line diff --git a/docs/changelog.md b/docs/changelog.md index 57f379d..be8edb5 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -3,7 +3,7 @@ Changelog -**0.40.0 (SOON)** +**0.40.0 (2023-12-27)** This release includes a rather extensive change to use the Click library (https://click.palletsprojects.com/) for creating the command line interface. From d1d74f47d8c86a090e9988fd3e198a2658bbfb78 Mon Sep 17 00:00:00 2001 From: Ivan Habunek Date: Wed, 27 Dec 2023 10:06:45 +0100 Subject: [PATCH 07/31] Include description in tag commit message --- scripts/tag_version | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/scripts/tag_version b/scripts/tag_version index 56efe8d..6f919fe 100755 --- a/scripts/tag_version +++ b/scripts/tag_version @@ -43,6 +43,7 @@ if dist_version != version: sys.exit(1) release_date = changelog_item["date"] +description = changelog_item.get("description") changes = changelog_item["changes"] if not isinstance(release_date, date): @@ -50,6 +51,11 @@ if not isinstance(release_date, date): sys.exit(1) commit_message = f"toot {version}\n\n" + +if description: + lines = textwrap.wrap(description.strip(), 72) + commit_message += "\n".join(lines) + "\n\n" + for c in changes: lines = textwrap.wrap(c, 70) initial = True From 556741e864e72599cc90876dfbd55c791e845d76 Mon Sep 17 00:00:00 2001 From: Ivan Habunek Date: Wed, 27 Dec 2023 10:15:51 +0100 Subject: [PATCH 08/31] Don't show warning for [tui.palette] setting section --- toot/cli/__init__.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/toot/cli/__init__.py b/toot/cli/__init__.py index 8e44af0..f36d70a 100644 --- a/toot/cli/__init__.py +++ b/toot/cli/__init__.py @@ -51,6 +51,11 @@ def get_default_map(): # TODO: remove in version 1.0 tui_old = settings.get("tui", {}) + + # Remove palette to avoid triggering warning for still valid [tui.palette] section + if "palette" in tui_old: + del tui_old["palette"] + if tui_old: print_warning("Settings section [tui] has been deprecated in favour of [commands.tui].") tui_new = commands.get("tui", {}) From 41b77cc9de77abfb333430295e701f6ab769c806 Mon Sep 17 00:00:00 2001 From: Ivan Habunek Date: Wed, 27 Dec 2023 10:16:19 +0100 Subject: [PATCH 09/31] Help with list command discovery --- toot/cli/lists.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/toot/cli/lists.py b/toot/cli/lists.py index 8bf39fb..f0d4ae6 100644 --- a/toot/cli/lists.py +++ b/toot/cli/lists.py @@ -11,7 +11,8 @@ from toot.output import print_list_accounts, print_lists, print_warning def lists(ctx: click.Context): """Display and manage lists""" if ctx.invoked_subcommand is None: - print_warning("`toot lists` is deprecated in favour of `toot lists list`") + print_warning("`toot lists` is deprecated in favour of `toot lists list`.\n" + + "Run `toot lists -h` to see other list-related commands.") user, app = config.get_active_user_app() if not user or not app: From ca2912fa7819c913d0f5c6c314bec68bfa2ce4f2 Mon Sep 17 00:00:00 2001 From: Ivan Habunek Date: Thu, 28 Dec 2023 12:12:29 +0100 Subject: [PATCH 10/31] Add toot --as option to override active account --- toot/cli/__init__.py | 35 +++++++++++++++++++++++++++++------ toot/cli/auth.py | 17 +---------------- 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/toot/cli/__init__.py b/toot/cli/__init__.py index f36d70a..0c71cb3 100644 --- a/toot/cli/__init__.py +++ b/toot/cli/__init__.py @@ -4,11 +4,14 @@ import os import sys import typing as t +from click.shell_completion import CompletionItem from click.testing import Result +from click.types import StringParamType from functools import wraps + from toot import App, User, config, __version__ -from toot.settings import get_settings from toot.output import print_warning +from toot.settings import get_settings if t.TYPE_CHECKING: import typing_extensions as te @@ -89,10 +92,24 @@ class TootObj(t.NamedTuple): """Data to add to Click context""" color: bool = True debug: bool = False + as_user: t.Optional[str] = None # Pass a context for testing purposes test_ctx: t.Optional[Context] = None +class AccountParamType(StringParamType): + """Custom type to add shell completion for account names""" + name = "account" + + def shell_complete(self, ctx, param, incomplete: str): + users = config.load_config()["users"].keys() + return [ + CompletionItem(u) + for u in users + if u.lower().startswith(incomplete.lower()) + ] + + def pass_context(f: "t.Callable[te.Concatenate[Context, P], R]") -> "t.Callable[P, R]": """Pass the toot Context as first argument.""" @wraps(f) @@ -110,9 +127,14 @@ def get_context() -> Context: if obj.test_ctx: return obj.test_ctx - user, app = config.get_active_user_app() - if not user or not app: - raise click.ClickException("This command requires you to be logged in.") + if obj.as_user: + user, app = config.get_user_app(obj.as_user) + if not user or not app: + raise click.ClickException(f"Account '{obj.as_user}' not found. Run `toot auth` to see available accounts.") + else: + user, app = config.get_active_user_app() + if not user or not app: + raise click.ClickException("This command requires you to be logged in.") return Context(app, user, obj.color, obj.debug) @@ -129,11 +151,12 @@ json_option = click.option( @click.option("-w", "--max-width", type=int, default=80, help="Maximum width for content rendered by toot") @click.option("--debug/--no-debug", default=False, help="Log debug info to stderr") @click.option("--color/--no-color", default=sys.stdout.isatty(), help="Use ANSI color in output") +@click.option("--as", "as_user", type=AccountParamType(), help="The account to use, overrides the active account.") @click.version_option(__version__, message="%(prog)s v%(version)s") @click.pass_context -def cli(ctx: click.Context, max_width: int, color: bool, debug: bool): +def cli(ctx: click.Context, max_width: int, color: bool, debug: bool, as_user: str): """Toot is a Mastodon CLI""" - ctx.obj = TootObj(color, debug) + ctx.obj = TootObj(color, debug, as_user) ctx.color = color ctx.max_content_width = max_width diff --git a/toot/cli/auth.py b/toot/cli/auth.py index c72f0c4..b7d32e6 100644 --- a/toot/cli/auth.py +++ b/toot/cli/auth.py @@ -2,13 +2,10 @@ import click import platform import sys import webbrowser -from click.shell_completion import CompletionItem - -from click.types import StringParamType from toot import api, config, __version__ from toot.auth import get_or_create_app, login_auth_code, login_username_password -from toot.cli import cli +from toot.cli import AccountParamType, cli from toot.cli.validators import validate_instance @@ -22,18 +19,6 @@ instance_option = click.option( ) -class AccountParamType(StringParamType): - """Custom type to add shell completion for account names""" - - def shell_complete(self, ctx, param, incomplete: str): - accounts = config.load_config()["users"].keys() - return [ - CompletionItem(a) - for a in accounts - if a.lower().startswith(incomplete.lower()) - ] - - @cli.command() def auth(): """Show logged in accounts and instances""" From 22c9f387a1d48770dfde169af01157f0f7852a5f Mon Sep 17 00:00:00 2001 From: Ivan Habunek Date: Thu, 28 Dec 2023 12:20:43 +0100 Subject: [PATCH 11/31] Bump version, add changelog --- CHANGELOG.md | 7 +++++++ changelog.yaml | 6 ++++++ docs/changelog.md | 7 +++++++ setup.py | 2 +- toot/__init__.py | 2 +- 5 files changed, 22 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index be8edb5..5f334ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,11 @@ Changelog +**0.40.1 (2023-12-28)** + +* Add `toot --as` option to replace `toot post --using`. This now works for all + commands. + **0.40.0 (2023-12-27)** This release includes a rather extensive change to use the Click library @@ -16,6 +21,8 @@ mostly preserved, except for cases noted below. Please report any issues. * BREAKING: Options `--debug` and `--color` must be specified after `toot` but before the command * BREAKING: Option `--quiet` has been removed. Redirect output instead. +* BREAKING: Removed `toot post --using` option. Use `toot --as post` + instead. * Add passing parameters via environment variables, see: https://toot.bezdomni.net/environment_variables.html * Add shell completion, see: https://toot.bezdomni.net/shell_completion.html diff --git a/changelog.yaml b/changelog.yaml index 7de1d4c..7a9bece 100644 --- a/changelog.yaml +++ b/changelog.yaml @@ -1,3 +1,8 @@ +0.40.1: + date: 2023-12-28 + changes: + - "Add `toot --as` option to replace `toot post --using`. This now works for all commands." + 0.40.0: date: 2023-12-27 description: | @@ -11,6 +16,7 @@ - "BREAKING: Remove deprecated `--disable-https` option for `login` and `login_cli`, pass the base URL instead" - "BREAKING: Options `--debug` and `--color` must be specified after `toot` but before the command" - "BREAKING: Option `--quiet` has been removed. Redirect output instead." + - "BREAKING: Removed `toot post --using` option. Use `toot --as post` instead." - "Add passing parameters via environment variables, see: https://toot.bezdomni.net/environment_variables.html" - "Add shell completion, see: https://toot.bezdomni.net/shell_completion.html" - "Add `tags info`, `tags featured`, `tags feature`, and `tags unfeature` commands" diff --git a/docs/changelog.md b/docs/changelog.md index be8edb5..5f334ce 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -3,6 +3,11 @@ Changelog +**0.40.1 (2023-12-28)** + +* Add `toot --as` option to replace `toot post --using`. This now works for all + commands. + **0.40.0 (2023-12-27)** This release includes a rather extensive change to use the Click library @@ -16,6 +21,8 @@ mostly preserved, except for cases noted below. Please report any issues. * BREAKING: Options `--debug` and `--color` must be specified after `toot` but before the command * BREAKING: Option `--quiet` has been removed. Redirect output instead. +* BREAKING: Removed `toot post --using` option. Use `toot --as post` + instead. * Add passing parameters via environment variables, see: https://toot.bezdomni.net/environment_variables.html * Add shell completion, see: https://toot.bezdomni.net/shell_completion.html diff --git a/setup.py b/setup.py index aca9b65..f3889ed 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ and blocking accounts and other actions. setup( name='toot', - version='0.40.0', + version='0.40.1', description='Mastodon CLI client', long_description=long_description.strip(), author='Ivan Habunek', diff --git a/toot/__init__.py b/toot/__init__.py index 010b17a..a16608a 100644 --- a/toot/__init__.py +++ b/toot/__init__.py @@ -4,7 +4,7 @@ import sys from os.path import join, expanduser from typing import NamedTuple -__version__ = '0.40.0' +__version__ = '0.40.1' class App(NamedTuple): From 2e2945822a2c586d7e62e412842d3b4daa62820d Mon Sep 17 00:00:00 2001 From: Ivan Habunek Date: Thu, 28 Dec 2023 19:02:19 +0100 Subject: [PATCH 12/31] Add shell completion for instances --- toot/cli/__init__.py | 14 ++++++++++++++ toot/cli/read.py | 4 ++-- toot/cli/timelines.py | 3 ++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/toot/cli/__init__.py b/toot/cli/__init__.py index 0c71cb3..1f33d1a 100644 --- a/toot/cli/__init__.py +++ b/toot/cli/__init__.py @@ -110,6 +110,20 @@ class AccountParamType(StringParamType): ] +class InstanceParamType(StringParamType): + """Custom type to add shell completion for instance domains""" + name = "instance" + + def shell_complete(self, ctx, param, incomplete: str): + apps = config.load_config()["apps"] + + return [ + CompletionItem(i) + for i in apps.keys() + if i.lower().startswith(incomplete.lower()) + ] + + def pass_context(f: "t.Callable[te.Concatenate[Context, P], R]") -> "t.Callable[P, R]": """Pass the toot Context as first argument.""" @wraps(f) diff --git a/toot/cli/read.py b/toot/cli/read.py index cfbcea8..32ce49a 100644 --- a/toot/cli/read.py +++ b/toot/cli/read.py @@ -9,7 +9,7 @@ from toot.cli.validators import validate_instance from toot.entities import Instance, Status, from_dict, Account from toot.exceptions import ApiError, ConsoleError from toot.output import print_account, print_instance, print_search_results, print_status, print_timeline -from toot.cli import cli, get_context, json_option, pass_context, Context +from toot.cli import InstanceParamType, cli, get_context, json_option, pass_context, Context @cli.command() @@ -43,7 +43,7 @@ def whois(ctx: Context, account: str, json: bool): @cli.command() -@click.argument("instance", callback=validate_instance, required=False) +@click.argument("instance", type=InstanceParamType(), callback=validate_instance, required=False) @json_option def instance(instance: Optional[str], json: bool): """Display instance details diff --git a/toot/cli/timelines.py b/toot/cli/timelines.py index 86d0ceb..aac2c45 100644 --- a/toot/cli/timelines.py +++ b/toot/cli/timelines.py @@ -2,7 +2,7 @@ import sys import click from toot import api -from toot.cli import cli, get_context, pass_context, Context +from toot.cli import InstanceParamType, cli, get_context, pass_context, Context from typing import Optional from toot.cli.validators import validate_instance @@ -13,6 +13,7 @@ from toot.output import print_notifications, print_timeline @cli.command() @click.option( "--instance", "-i", + type=InstanceParamType(), callback=validate_instance, help="""Domain or base URL of the instance from which to read, e.g. 'mastodon.social' or 'https://mastodon.social'""", From 11aaa1dc2985122e35076b432e4b6b4a24a3f1c7 Mon Sep 17 00:00:00 2001 From: Ivan Habunek Date: Thu, 28 Dec 2023 19:09:48 +0100 Subject: [PATCH 13/31] Reinstate toot post --using option --- toot/cli/post.py | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/toot/cli/post.py b/toot/cli/post.py index af0fa60..afcfa4e 100644 --- a/toot/cli/post.py +++ b/toot/cli/post.py @@ -6,8 +6,8 @@ from datetime import datetime, timedelta, timezone from time import sleep, time from typing import BinaryIO, Optional, Tuple -from toot import api -from toot.cli import cli, json_option, pass_context, Context +from toot import api, config +from toot.cli import AccountParamType, cli, json_option, pass_context, Context from toot.cli import DURATION_EXAMPLES, VISIBILITY_CHOICES from toot.cli.validators import validate_duration, validate_language from toot.entities import MediaAttachment, from_dict @@ -106,6 +106,11 @@ from toot.utils.datetime import parse_datetime is_flag=True, default=False, ) +@click.option( + "-u", "--using", + type=AccountParamType(), + help="The account to use, overrides the active account.", +) @json_option @pass_context def post( @@ -127,12 +132,20 @@ def post( poll_expires_in: int, poll_multiple: bool, poll_hide_totals: bool, - json: bool + json: bool, + using: str ): """Post a new status""" if len(media) > 4: raise click.ClickException("Cannot attach more than 4 files.") + if using: + user, app = config.get_user_app(using) + if not user or not app: + raise click.ClickException(f"Account '{using}' not found. Run `toot auth` to see available accounts.") + else: + user, app = ctx.user, ctx.app + media_ids = _upload_media(ctx.app, ctx.user, media, descriptions, thumbnails) status_text = _get_status_text(text, editor, media) scheduled_at = _get_scheduled_at(scheduled_at, scheduled_in) @@ -141,8 +154,8 @@ def post( raise click.ClickException("You must specify either text or media to post.") response = api.post_status( - ctx.app, - ctx.user, + app, + user, status_text, visibility=visibility, media_ids=media_ids, From 09b29d2b93aaf07962d9b3d7c4715930f4b2f5c2 Mon Sep 17 00:00:00 2001 From: Ivan Habunek Date: Thu, 28 Dec 2023 19:11:28 +0100 Subject: [PATCH 14/31] Bump version, update changelog --- CHANGELOG.md | 7 +++++-- changelog.yaml | 7 ++++++- docs/changelog.md | 7 +++++-- setup.py | 2 +- toot/__init__.py | 2 +- 5 files changed, 18 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f334ce..acf986a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,11 @@ Changelog +**0.40.2 (2023-12-28)** + +* Reinstate `toot post --using` option. +* Add shell completion for instances. + **0.40.1 (2023-12-28)** * Add `toot --as` option to replace `toot post --using`. This now works for all @@ -21,8 +26,6 @@ mostly preserved, except for cases noted below. Please report any issues. * BREAKING: Options `--debug` and `--color` must be specified after `toot` but before the command * BREAKING: Option `--quiet` has been removed. Redirect output instead. -* BREAKING: Removed `toot post --using` option. Use `toot --as post` - instead. * Add passing parameters via environment variables, see: https://toot.bezdomni.net/environment_variables.html * Add shell completion, see: https://toot.bezdomni.net/shell_completion.html diff --git a/changelog.yaml b/changelog.yaml index 7a9bece..34c219d 100644 --- a/changelog.yaml +++ b/changelog.yaml @@ -1,3 +1,9 @@ +0.40.2: + date: 2023-12-28 + changes: + - "Reinstate `toot post --using` option." + - "Add shell completion for instances." + 0.40.1: date: 2023-12-28 changes: @@ -16,7 +22,6 @@ - "BREAKING: Remove deprecated `--disable-https` option for `login` and `login_cli`, pass the base URL instead" - "BREAKING: Options `--debug` and `--color` must be specified after `toot` but before the command" - "BREAKING: Option `--quiet` has been removed. Redirect output instead." - - "BREAKING: Removed `toot post --using` option. Use `toot --as post` instead." - "Add passing parameters via environment variables, see: https://toot.bezdomni.net/environment_variables.html" - "Add shell completion, see: https://toot.bezdomni.net/shell_completion.html" - "Add `tags info`, `tags featured`, `tags feature`, and `tags unfeature` commands" diff --git a/docs/changelog.md b/docs/changelog.md index 5f334ce..acf986a 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -3,6 +3,11 @@ Changelog +**0.40.2 (2023-12-28)** + +* Reinstate `toot post --using` option. +* Add shell completion for instances. + **0.40.1 (2023-12-28)** * Add `toot --as` option to replace `toot post --using`. This now works for all @@ -21,8 +26,6 @@ mostly preserved, except for cases noted below. Please report any issues. * BREAKING: Options `--debug` and `--color` must be specified after `toot` but before the command * BREAKING: Option `--quiet` has been removed. Redirect output instead. -* BREAKING: Removed `toot post --using` option. Use `toot --as post` - instead. * Add passing parameters via environment variables, see: https://toot.bezdomni.net/environment_variables.html * Add shell completion, see: https://toot.bezdomni.net/shell_completion.html diff --git a/setup.py b/setup.py index f3889ed..38eb5b8 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ and blocking accounts and other actions. setup( name='toot', - version='0.40.1', + version='0.40.2', description='Mastodon CLI client', long_description=long_description.strip(), author='Ivan Habunek', diff --git a/toot/__init__.py b/toot/__init__.py index a16608a..53834ff 100644 --- a/toot/__init__.py +++ b/toot/__init__.py @@ -4,7 +4,7 @@ import sys from os.path import join, expanduser from typing import NamedTuple -__version__ = '0.40.1' +__version__ = '0.40.2' class App(NamedTuple): From 741a306c6930136cbc917f54a15d94d34b756f55 Mon Sep 17 00:00:00 2001 From: Lexi Winter Date: Sun, 31 Dec 2023 16:17:42 +0000 Subject: [PATCH 15/31] tui: fix display glitch for reply icon in timeline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In some fonts, "⤶" (U+2936 ARROW POINTING DOWNWARDS THEN CURVING LEFTWARDS) may be a double-width character. To avoid a display glitch where this overlaps with the boosted icon, print a space after it. --- toot/tui/timeline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/toot/tui/timeline.py b/toot/tui/timeline.py index 63eb4b3..a91b039 100644 --- a/toot/tui/timeline.py +++ b/toot/tui/timeline.py @@ -456,7 +456,7 @@ class StatusListItem(SelectableColumns): favourited = ("highlight", "★") if status.original.favourited else " " reblogged = ("highlight", "♺") if status.original.reblogged else " " is_reblog = ("dim", "♺") if status.reblog else " " - is_reply = ("dim", "⤶") if status.original.in_reply_to else " " + is_reply = ("dim", "⤶ ") if status.original.in_reply_to else " " return super().__init__([ ("pack", SelectableText(("status_list_timestamp", created_at), wrap="clip")), From d0f05c7ad9c1e4c7ade4c3c3487ea0a2a77ae0f4 Mon Sep 17 00:00:00 2001 From: Lexi Winter Date: Sun, 31 Dec 2023 16:51:02 +0000 Subject: [PATCH 16/31] tui: show edit date in toot view When viewing a toot which has been edited, show the edit date. While here, fix a bug where the '*' edit marker in the timeline wouldn't show for retoots because it was checking the retoot status instead of the original status. --- toot/tui/entities.py | 4 ++++ toot/tui/timeline.py | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/toot/tui/entities.py b/toot/tui/entities.py index 165ca77..642e953 100644 --- a/toot/tui/entities.py +++ b/toot/tui/entities.py @@ -53,6 +53,10 @@ class Status: self.id = self.data["id"] self.account = self._get_account() self.created_at = parse_datetime(data["created_at"]) + if data["edited_at"]: + self.edited_at = parse_datetime(data["edited_at"]) + else: + self.edited_at = None self.author = self._get_author() self.favourited = data.get("favourited", False) self.reblogged = data.get("reblogged", False) diff --git a/toot/tui/timeline.py b/toot/tui/timeline.py index 63eb4b3..393de5c 100644 --- a/toot/tui/timeline.py +++ b/toot/tui/timeline.py @@ -388,6 +388,8 @@ class StatusDetails(urwid.Pile): yield ("pack", urwid.Text([ ("status_detail_timestamp", f"{status.created_at.strftime('%Y-%m-%d %H:%M')} "), + ("status_detail_timestamp", + f"(edited {status.edited_at.strftime('%Y-%m-%d %H:%M')}) " if status.edited_at else ""), ("status_detail_bookmarked" if status.bookmarked else "dim", "b "), ("dim", f"⤶ {status.data['replies_count']} "), ("highlight" if status.reblogged else "dim", f"♺ {status.data['reblogs_count']} "), @@ -442,7 +444,7 @@ class StatusDetails(urwid.Pile): class StatusListItem(SelectableColumns): def __init__(self, status, relative_datetimes): - edited_at = status.data.get("edited_at") + edited_at = status.original.edited_at # TODO: hacky implementation to avoid creating conflicts for existing # pull reuqests, refactor when merged. From 5dd53b1b9cec240e4867a1087a4f379b2632dce2 Mon Sep 17 00:00:00 2001 From: Lexi Winter Date: Sun, 31 Dec 2023 17:13:42 +0000 Subject: [PATCH 17/31] tui: honour user's default visibility preference Mastodon allows the user to configure a default visibility which should apply to all clients. This setting is returned by the /api/v1/preferences method. Fetch the user preferences when the TUI starts, and use it to set the default visibility when composing a new toot. The preference can be overridden by a new command-line option, toot tui --default-visibility=. If neither the preference nor the option are set, fall back to get_default_visibility(). --- toot/api.py | 4 ++++ toot/cli/tui.py | 9 ++++++++- toot/tui/app.py | 26 +++++++++++++++++++++++++- toot/tui/compose.py | 6 ++---- 4 files changed, 39 insertions(+), 6 deletions(-) diff --git a/toot/api.py b/toot/api.py index 5071df4..0033eb4 100644 --- a/toot/api.py +++ b/toot/api.py @@ -618,6 +618,10 @@ def get_instance(base_url: str) -> Response: return http.anon_get(url) +def get_preferences(app, user) -> Response: + return http.get(app, user, '/api/v1/preferences') + + def get_lists(app, user): return http.get(app, user, "/api/v1/lists").json() diff --git a/toot/cli/tui.py b/toot/cli/tui.py index 1cedd75..391f8e3 100644 --- a/toot/cli/tui.py +++ b/toot/cli/tui.py @@ -1,7 +1,7 @@ import click from typing import Optional -from toot.cli import TUI_COLORS, Context, cli, pass_context +from toot.cli import TUI_COLORS, VISIBILITY_CHOICES, Context, cli, pass_context from toot.cli.validators import validate_tui_colors from toot.tui.app import TUI, TuiOptions @@ -24,12 +24,18 @@ COLOR_OPTIONS = ", ".join(TUI_COLORS.keys()) help=f"""Number of colors to use, one of {COLOR_OPTIONS}, defaults to 16 if using --color, and 1 if using --no-color.""" ) +@click.option( + "-v", "--default-visibility", + type=click.Choice(VISIBILITY_CHOICES), + help="Default visibility when posting new toots; overrides the server-side preference" +) @pass_context def tui( ctx: Context, colors: Optional[int], media_viewer: Optional[str], relative_datetimes: bool, + default_visibility: Optional[str] ): """Launches the toot terminal user interface""" if colors is None: @@ -39,6 +45,7 @@ def tui( colors=colors, media_viewer=media_viewer, relative_datetimes=relative_datetimes, + default_visibility=default_visibility ) tui = TUI.create(ctx.app, ctx.user, options) tui.run() diff --git a/toot/tui/app.py b/toot/tui/app.py index f67dcf5..5d9982b 100644 --- a/toot/tui/app.py +++ b/toot/tui/app.py @@ -31,6 +31,7 @@ class TuiOptions(NamedTuple): colors: int media_viewer: Optional[str] relative_datetimes: bool + default_visibility: Optional[bool] class Header(urwid.WidgetWrap): @@ -137,11 +138,13 @@ class TUI(urwid.Frame): self.can_translate = False self.account = None self.followed_accounts = [] + self.preferences = {} super().__init__(self.body, header=self.header, footer=self.footer) def run(self): self.loop.set_alarm_in(0, lambda *args: self.async_load_instance()) + self.loop.set_alarm_in(0, lambda *args: self.async_load_preferences()) self.loop.set_alarm_in(0, lambda *args: self.async_load_timeline( is_initial=True, timeline_name="home")) self.loop.set_alarm_in(0, lambda *args: self.async_load_followed_accounts()) @@ -326,6 +329,19 @@ class TUI(urwid.Frame): return self.run_in_thread(_load_instance, done_callback=_done) + def async_load_preferences(self): + """ + Attempt to update user preferences from instance. + https://docs.joinmastodon.org/methods/preferences/ + """ + def _load_preferences(): + return api.get_preferences(self.app, self.user).json() + + def _done(preferences): + self.preferences = preferences + + return self.run_in_thread(_load_preferences, done_callback=_done) + def async_load_followed_accounts(self): def _load_accounts(): try: @@ -400,7 +416,15 @@ class TUI(urwid.Frame): def _post(timeline, *args): self.post_status(*args) - composer = StatusComposer(self.max_toot_chars, self.user.username, in_reply_to) + # If the user specified --default-visibility, use that; otherwise, + # try to use the server-side default visibility. If that fails, fall + # back to get_default_visibility(). + visibility = (self.options.default_visibility or + self.preferences.get('posting:default:visibility', + get_default_visibility())) + + composer = StatusComposer(self.max_toot_chars, self.user.username, + visibility, in_reply_to) urwid.connect_signal(composer, "close", _close) urwid.connect_signal(composer, "post", _post) self.open_overlay(composer, title="Compose status") diff --git a/toot/tui/compose.py b/toot/tui/compose.py index c4a038a..ea60fa6 100644 --- a/toot/tui/compose.py +++ b/toot/tui/compose.py @@ -1,8 +1,6 @@ import urwid import logging -from toot.cli import get_default_visibility - from .constants import VISIBILITY_OPTIONS from .widgets import Button, EditBox @@ -15,7 +13,7 @@ class StatusComposer(urwid.Frame): """ signals = ["close", "post"] - def __init__(self, max_chars, username, in_reply_to=None): + def __init__(self, max_chars, username, visibility, in_reply_to=None): self.in_reply_to = in_reply_to self.max_chars = max_chars self.username = username @@ -34,7 +32,7 @@ class StatusComposer(urwid.Frame): on_press=self.remove_content_warning) self.visibility = ( - in_reply_to.visibility if in_reply_to else get_default_visibility() + in_reply_to.visibility if in_reply_to else visibility ) self.visibility_button = Button("Visibility: {}".format(self.visibility), on_press=self.choose_visibility) From f394d78c1e45b75ad4f2dd29cd8e267e73f6fd3e Mon Sep 17 00:00:00 2001 From: Lexi Winter Date: Sun, 31 Dec 2023 13:16:28 +0000 Subject: [PATCH 18/31] tui: keep CW note after opening toot Continue to display 'Marked as sensitive' in the toot view even after the CW has been opened. This matches the behaviour of other clients, and is useful to see because it might affect whether you want to boost the toot or not (for example). --- toot/tui/timeline.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/toot/tui/timeline.py b/toot/tui/timeline.py index 0cc7756..a14ada2 100644 --- a/toot/tui/timeline.py +++ b/toot/tui/timeline.py @@ -340,6 +340,9 @@ class StatusDetails(urwid.Pile): if status.data["spoiler_text"] and not status.show_sensitive: yield ("pack", urwid.Text(("content_warning", "Marked as sensitive. Press S to view."))) else: + if status.data["spoiler_text"]: + yield ("pack", urwid.Text(("content_warning", "Marked as sensitive."))) + content = status.original.translation if status.original.show_translation else status.data["content"] widgetlist = html_to_widgets(content) From 1ed129f5dd393bb4e651d832130df9d829609730 Mon Sep 17 00:00:00 2001 From: Lexi Winter Date: Sun, 31 Dec 2023 13:27:37 +0000 Subject: [PATCH 19/31] tui: add --always-show-sensitive option When enabled, this option expands toots with content warnings automatically, instead of requiring the user to press 'S'. --- toot/cli/tui.py | 9 ++++++++- toot/tui/app.py | 1 + toot/tui/timeline.py | 3 ++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/toot/cli/tui.py b/toot/cli/tui.py index 391f8e3..c13b745 100644 --- a/toot/cli/tui.py +++ b/toot/cli/tui.py @@ -29,11 +29,17 @@ COLOR_OPTIONS = ", ".join(TUI_COLORS.keys()) type=click.Choice(VISIBILITY_CHOICES), help="Default visibility when posting new toots; overrides the server-side preference" ) +@click.option( + "-S", "--always-show-sensitive", + is_flag=True, + help="Expand toots with content warnings automatically" +) @pass_context def tui( ctx: Context, colors: Optional[int], media_viewer: Optional[str], + always_show_sensitive: bool, relative_datetimes: bool, default_visibility: Optional[str] ): @@ -45,7 +51,8 @@ def tui( colors=colors, media_viewer=media_viewer, relative_datetimes=relative_datetimes, - default_visibility=default_visibility + default_visibility=default_visibility, + always_show_sensitive=always_show_sensitive, ) tui = TUI.create(ctx.app, ctx.user, options) tui.run() diff --git a/toot/tui/app.py b/toot/tui/app.py index 5d9982b..b19b7a2 100644 --- a/toot/tui/app.py +++ b/toot/tui/app.py @@ -30,6 +30,7 @@ DEFAULT_MAX_TOOT_CHARS = 500 class TuiOptions(NamedTuple): colors: int media_viewer: Optional[str] + always_show_sensitive: bool relative_datetimes: bool default_visibility: Optional[bool] diff --git a/toot/tui/timeline.py b/toot/tui/timeline.py index a14ada2..1c6090e 100644 --- a/toot/tui/timeline.py +++ b/toot/tui/timeline.py @@ -313,6 +313,7 @@ class StatusDetails(urwid.Pile): def __init__(self, timeline: Timeline, status: Optional[Status]): self.status = status self.followed_accounts = timeline.tui.followed_accounts + self.options = timeline.tui.options reblogged_by = status.author if status and status.reblog else None widget_list = list(self.content_generator(status.original, reblogged_by) @@ -337,7 +338,7 @@ class StatusDetails(urwid.Pile): yield ("pack", urwid.Divider()) # Show content warning - if status.data["spoiler_text"] and not status.show_sensitive: + if status.data["spoiler_text"] and not status.show_sensitive and not self.options.always_show_sensitive: yield ("pack", urwid.Text(("content_warning", "Marked as sensitive. Press S to view."))) else: if status.data["spoiler_text"]: From 84e75347e0c81538450731d171d3a9418cbfe32f Mon Sep 17 00:00:00 2001 From: Ivan Habunek Date: Sun, 31 Dec 2023 21:29:06 +0100 Subject: [PATCH 20/31] Make palettes work again --- toot/cli/__init__.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/toot/cli/__init__.py b/toot/cli/__init__.py index 1f33d1a..92abbc9 100644 --- a/toot/cli/__init__.py +++ b/toot/cli/__init__.py @@ -55,11 +55,8 @@ def get_default_map(): # TODO: remove in version 1.0 tui_old = settings.get("tui", {}) - # Remove palette to avoid triggering warning for still valid [tui.palette] section - if "palette" in tui_old: - del tui_old["palette"] - if tui_old: + # TODO: don't show the warning for [toot.palette] print_warning("Settings section [tui] has been deprecated in favour of [commands.tui].") tui_new = commands.get("tui", {}) commands["tui"] = {**tui_old, **tui_new} From 3a147a5ea028d7dd5a5810a9b65f3845c1386c42 Mon Sep 17 00:00:00 2001 From: Ivan Habunek Date: Tue, 19 Dec 2023 11:28:25 +0100 Subject: [PATCH 21/31] Move Run type alias to conftest It's only used in tests --- tests/integration/conftest.py | 6 +++++- tests/integration/test_auth.py | 2 +- toot/cli/__init__.py | 4 ---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index fea6477..8e3f3be 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -14,10 +14,11 @@ export TOOT_TEST_DATABASE_DSN="dbname=mastodon_development" """ import json -import re import os import psycopg2 import pytest +import re +import typing as t import uuid from click.testing import CliRunner, Result @@ -31,6 +32,9 @@ def pytest_configure(config): toot.settings.DISABLE_SETTINGS = True +# Type alias for run commands +Run = t.Callable[..., Result] + # Mastodon database name, used to confirm user registration without having to click the link DATABASE_DSN = os.getenv("TOOT_TEST_DATABASE_DSN") TOOT_TEST_BASE_URL = os.getenv("TOOT_TEST_BASE_URL") diff --git a/tests/integration/test_auth.py b/tests/integration/test_auth.py index 858d6fb..74db83b 100644 --- a/tests/integration/test_auth.py +++ b/tests/integration/test_auth.py @@ -3,7 +3,7 @@ from unittest import mock from unittest.mock import MagicMock from toot import User, cli -from toot.cli import Run +from tests.integration.conftest import Run # TODO: figure out how to test login diff --git a/toot/cli/__init__.py b/toot/cli/__init__.py index 92abbc9..5ebd196 100644 --- a/toot/cli/__init__.py +++ b/toot/cli/__init__.py @@ -39,10 +39,6 @@ DURATION_EXAMPLES = """e.g. "1 day", "2 hours 30 minutes", "5 minutes 30 seconds" or any combination of above. Shorthand: "1d", "2h30m", "5m30s\"""" -# Type alias for run commands -Run = t.Callable[..., Result] - - def get_default_visibility() -> str: return os.getenv("TOOT_POST_VISIBILITY", "public") From 301c8d21dfb4dfa5a12c84430a81e99907288191 Mon Sep 17 00:00:00 2001 From: Ivan Habunek Date: Mon, 1 Jan 2024 11:14:04 +0100 Subject: [PATCH 22/31] Add test util function for retrying tests --- tests/integration/test_timelines.py | 17 ++++++++--------- tests/utils.py | 23 +++++++++++++++++++++++ 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/tests/integration/test_timelines.py b/tests/integration/test_timelines.py index d4cebbd..1ee3602 100644 --- a/tests/integration/test_timelines.py +++ b/tests/integration/test_timelines.py @@ -2,6 +2,7 @@ import pytest from time import sleep from uuid import uuid4 +from tests.utils import run_with_retries from toot import api, cli from toot.entities import from_dict, Status @@ -40,16 +41,14 @@ def test_timelines(app, user, other_user, friend_user, friend_list, run): status2 = _post_status(app, other_user, "#bar") status3 = _post_status(app, friend_user, "#foo #bar") - # Give mastodon time to process things :/ - # Tests fail if this is removed, required delay depends on server speed - sleep(1) - # Home timeline - result = run(cli.timelines.timeline) - assert result.exit_code == 0 - assert status1.id in result.stdout - assert status2.id not in result.stdout - assert status3.id in result.stdout + def test_home(): + result = run(cli.timelines.timeline) + assert result.exit_code == 0 + assert status1.id in result.stdout + assert status2.id not in result.stdout + assert status3.id in result.stdout + run_with_retries(test_home) # Public timeline result = run(cli.timelines.timeline, "--public") diff --git a/tests/utils.py b/tests/utils.py index cdae09c..817bdb9 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -2,6 +2,9 @@ Helpers for testing. """ +import time +from typing import Any, Callable + class MockResponse: def __init__(self, response_data={}, ok=True, is_redirect=False): @@ -19,3 +22,23 @@ class MockResponse: def retval(val): return lambda *args, **kwargs: val + + +def run_with_retries(fn: Callable[..., Any]): + """ + Run the the given function repeatedly until it finishes without raising an + AssertionError. Sleep a bit between attempts. If the function doesn't + succeed in the given number of tries raises the AssertionError. Used for + tests which should eventually succeed. + """ + + # Wait upto 6 seconds with incrementally longer sleeps + delays = [0.1, 0.2, 0.3, 0.4, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5] + + for delay in delays: + try: + return fn() + except AssertionError: + time.sleep(delay) + + fn() From d1fe0ca92df66fedbe54172d4d3e257b3628ebdd Mon Sep 17 00:00:00 2001 From: Ivan Habunek Date: Mon, 1 Jan 2024 12:12:08 +0100 Subject: [PATCH 23/31] Replace sleeps in tests with retries --- tests/integration/test_status.py | 11 ++++++----- tests/integration/test_timelines.py | 15 +++++++-------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/integration/test_status.py b/tests/integration/test_status.py index 6f9a2a4..e21518f 100644 --- a/tests/integration/test_status.py +++ b/tests/integration/test_status.py @@ -1,6 +1,7 @@ import json import time import pytest +from tests.utils import run_with_retries from toot import api, cli from toot.exceptions import NotFoundError @@ -46,11 +47,11 @@ def test_favourite(app, user, run): assert result.exit_code == 0 assert result.stdout.strip() == "✓ Status unfavourited" - # A short delay is required before the server returns new data - time.sleep(0.2) - - status = api.fetch_status(app, user, status["id"]).json() - assert not status["favourited"] + def test_favourited(): + nonlocal status + status = api.fetch_status(app, user, status["id"]).json() + assert not status["favourited"] + run_with_retries(test_favourited) def test_favourite_json(app, user, run): diff --git a/tests/integration/test_timelines.py b/tests/integration/test_timelines.py index 1ee3602..818ba3d 100644 --- a/tests/integration/test_timelines.py +++ b/tests/integration/test_timelines.py @@ -1,6 +1,5 @@ import pytest -from time import sleep from uuid import uuid4 from tests.utils import run_with_retries @@ -165,13 +164,14 @@ def test_notifications(app, user, other_user, run): text = f"Paging doctor @{user.username}" status = _post_status(app, other_user, text) - sleep(0.5) # grr - result = run(cli.timelines.notifications) - assert result.exit_code == 0 - assert f"@{other_user.username} mentioned you" in result.stdout - assert status.id in result.stdout - assert text in result.stdout + def test_notifications(): + result = run(cli.timelines.notifications) + assert result.exit_code == 0 + assert f"@{other_user.username} mentioned you" in result.stdout + assert status.id in result.stdout + assert text in result.stdout + run_with_retries(test_notifications) result = run(cli.timelines.notifications, "--mentions") assert result.exit_code == 0 @@ -185,7 +185,6 @@ def test_notifications_follow(app, user, friend_user, run_as): assert result.exit_code == 0 assert f"@{user.username} now follows you" in result.stdout - result = run_as(friend_user, cli.timelines.notifications, "--mentions") assert result.exit_code == 0 assert "now follows you" not in result.stdout From 724f27f8606558ca29b41e45114b71755ea25d91 Mon Sep 17 00:00:00 2001 From: Ivan Habunek Date: Mon, 1 Jan 2024 12:14:15 +0100 Subject: [PATCH 24/31] Remove unused imports --- tests/integration/test_status.py | 3 +-- toot/cli/__init__.py | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/integration/test_status.py b/tests/integration/test_status.py index e21518f..8e609a6 100644 --- a/tests/integration/test_status.py +++ b/tests/integration/test_status.py @@ -1,8 +1,7 @@ import json -import time import pytest -from tests.utils import run_with_retries +from tests.utils import run_with_retries from toot import api, cli from toot.exceptions import NotFoundError diff --git a/toot/cli/__init__.py b/toot/cli/__init__.py index 5ebd196..21f4002 100644 --- a/toot/cli/__init__.py +++ b/toot/cli/__init__.py @@ -5,7 +5,6 @@ import sys import typing as t from click.shell_completion import CompletionItem -from click.testing import Result from click.types import StringParamType from functools import wraps From ec48e8eed802d790b892743d5701495adadbb4b7 Mon Sep 17 00:00:00 2001 From: Lexi Winter Date: Sun, 31 Dec 2023 15:45:52 +0000 Subject: [PATCH 25/31] tui: allow editing toots Add new [E]dit command to the timeline: opens an existing toot to allow editing it. Since this is more or less the same operation as posting a new toot, extend the StatusComposer view to support this rather than implementing a new view. Add a new api method, fetch_status_source(), to implement the /api/v1/statuses/{id}/source endpoint used to fetch the original post text. --- toot/api.py | 55 +++++++++++++++++++++++++++++++++++++ toot/cli/__init__.py | 1 - toot/http.py | 18 +++++++++++- toot/tui/app.py | 65 ++++++++++++++++++++++++++++++++++++++++++++ toot/tui/compose.py | 48 +++++++++++++++++++++++--------- toot/tui/timeline.py | 6 ++++ toot/tui/widgets.py | 8 ++++++ 7 files changed, 186 insertions(+), 15 deletions(-) diff --git a/toot/api.py b/toot/api.py index 0033eb4..3509f47 100644 --- a/toot/api.py +++ b/toot/api.py @@ -230,6 +230,52 @@ def post_status( return http.post(app, user, '/api/v1/statuses', json=data, headers=headers) +def edit_status( + app, + user, + id, + status, + visibility='public', + media_ids=None, + sensitive=False, + spoiler_text=None, + in_reply_to_id=None, + language=None, + content_type=None, + poll_options=None, + poll_expires_in=None, + poll_multiple=None, + poll_hide_totals=None, +) -> Response: + """ + Edit an existing status + https://docs.joinmastodon.org/methods/statuses/#edit + """ + + # Strip keys for which value is None + # Sending null values doesn't bother Mastodon, but it breaks Pleroma + data = drop_empty_values({ + 'status': status, + 'media_ids': media_ids, + 'visibility': visibility, + 'sensitive': sensitive, + 'in_reply_to_id': in_reply_to_id, + 'language': language, + 'content_type': content_type, + 'spoiler_text': spoiler_text, + }) + + if poll_options: + data["poll"] = { + "options": poll_options, + "expires_in": poll_expires_in, + "multiple": poll_multiple, + "hide_totals": poll_hide_totals, + } + + return http.put(app, user, f"/api/v1/statuses/{id}", json=data) + + def fetch_status(app, user, id): """ Fetch a single status @@ -238,6 +284,15 @@ def fetch_status(app, user, id): return http.get(app, user, f"/api/v1/statuses/{id}") +def fetch_status_source(app, user, id): + """ + Fetch the source (original text) for a single status. + This only works on local toots. + https://docs.joinmastodon.org/methods/statuses/#source + """ + return http.get(app, user, f"/api/v1/statuses/{id}/source") + + def scheduled_statuses(app, user): """ List scheduled statuses diff --git a/toot/cli/__init__.py b/toot/cli/__init__.py index 5ebd196..21f4002 100644 --- a/toot/cli/__init__.py +++ b/toot/cli/__init__.py @@ -5,7 +5,6 @@ import sys import typing as t from click.shell_completion import CompletionItem -from click.testing import Result from click.types import StringParamType from functools import wraps diff --git a/toot/http.py b/toot/http.py index 14acdd0..ec4b62a 100644 --- a/toot/http.py +++ b/toot/http.py @@ -38,7 +38,7 @@ def _get_error_message(response): except Exception: pass - return "Unknown error" + return f"Unknown error: {response.status_code} {response.reason}" def process_response(response): @@ -81,6 +81,22 @@ def post(app, user, path, headers=None, files=None, data=None, json=None, allow_ return anon_post(url, headers=headers, files=files, data=data, json=json, allow_redirects=allow_redirects) +def anon_put(url, headers=None, files=None, data=None, json=None, allow_redirects=True): + request = Request(method="PUT", url=url, headers=headers, files=files, data=data, json=json) + response = send_request(request, allow_redirects) + + return process_response(response) + + +def put(app, user, path, headers=None, files=None, data=None, json=None, allow_redirects=True): + url = app.base_url + path + + headers = headers or {} + headers["Authorization"] = f"Bearer {user.access_token}" + + return anon_put(url, headers=headers, files=files, data=data, json=json, allow_redirects=allow_redirects) + + def patch(app, user, path, headers=None, files=None, data=None, json=None): url = app.base_url + path diff --git a/toot/tui/app.py b/toot/tui/app.py index 5d9982b..5790d2c 100644 --- a/toot/tui/app.py +++ b/toot/tui/app.py @@ -4,11 +4,13 @@ import urwid from concurrent.futures import ThreadPoolExecutor from typing import NamedTuple, Optional +from datetime import datetime, timezone from toot import api, config, __version__, settings from toot import App, User from toot.cli import get_default_visibility from toot.exceptions import ApiError +from toot.utils.datetime import parse_datetime from .compose import StatusComposer from .constants import PALETTE @@ -18,6 +20,7 @@ from .overlays import StatusDeleteConfirmation, Account from .poll import Poll from .timeline import Timeline from .utils import get_max_toot_chars, parse_content_links, copy_to_clipboard +from .widgets import ModalBox logger = logging.getLogger(__name__) @@ -429,6 +432,32 @@ class TUI(urwid.Frame): urwid.connect_signal(composer, "post", _post) self.open_overlay(composer, title="Compose status") + def async_edit(self, status): + def _fetch_source(): + return api.fetch_status_source(self.app, self.user, status.id).json() + + def _done(source): + self.close_overlay() + self.show_edit(status, source) + + please_wait = ModalBox("Loading status...") + self.open_overlay(please_wait) + + self.run_in_thread(_fetch_source, done_callback=_done) + + def show_edit(self, status, source): + def _close(*args): + self.close_overlay() + + def _edit(timeline, *args): + self.edit_status(status, *args) + + composer = StatusComposer(self.max_toot_chars, self.user.username, + visibility=None, edit=status, source=source) + urwid.connect_signal(composer, "close", _close) + urwid.connect_signal(composer, "post", _edit) + self.open_overlay(composer, title="Edit status") + def show_goto_menu(self): user_timelines = self.config.get("timelines", {}) user_lists = api.get_lists(self.app, self.user) or [] @@ -576,6 +605,42 @@ class TUI(urwid.Frame): self.footer.set_message("Status posted {} \\o/".format(status.id)) self.close_overlay() + def edit_status(self, status, content, warning, visibility, in_reply_to_id): + # We don't support editing polls (yet), so to avoid losing the poll + # data from the original toot, copy it to the edit request. + poll_args = {} + poll = status.original.data.get('poll', None) + + if poll is not None: + poll_args['poll_options'] = [o['title'] for o in poll['options']] + poll_args['poll_multiple'] = poll['multiple'] + + # Convert absolute expiry time into seconds from now. + expires_at = parse_datetime(poll['expires_at']) + expires_in = int((expires_at - datetime.now(timezone.utc)).total_seconds()) + poll_args['poll_expires_in'] = expires_in + + if 'hide_totals' in poll: + poll_args['poll_hide_totals'] = poll['hide_totals'] + + data = api.edit_status( + self.app, + self.user, + status.id, + content, + spoiler_text=warning, + visibility=visibility, + **poll_args + ).json() + + new_status = self.make_status(data) + + self.footer.set_message("Status edited {} \\o/".format(status.id)) + self.close_overlay() + + if self.timeline is not None: + self.timeline.update_status(new_status) + def show_account(self, account_id): account = api.whois(self.app, self.user, account_id) relationship = api.get_relationship(self.app, self.user, account_id) diff --git a/toot/tui/compose.py b/toot/tui/compose.py index ea60fa6..a931fa3 100644 --- a/toot/tui/compose.py +++ b/toot/tui/compose.py @@ -9,21 +9,22 @@ logger = logging.getLogger(__name__) class StatusComposer(urwid.Frame): """ - UI for compose and posting a status message. + UI for composing or editing a status message. + + To edit a status, provide the original status in 'edit', and optionally + provide the status source (from the /status/:id/source API endpoint) in + 'source'; this should have at least a 'text' member, and optionally + 'spoiler_text'. If source is not provided, the formatted HTML will be + presented to the user for editing. """ signals = ["close", "post"] - def __init__(self, max_chars, username, visibility, in_reply_to=None): + def __init__(self, max_chars, username, visibility, in_reply_to=None, + edit=None, source=None): self.in_reply_to = in_reply_to self.max_chars = max_chars self.username = username - - text = self.get_initial_text(in_reply_to) - self.content_edit = EditBox( - edit_text=text, edit_pos=len(text), multiline=True, allow_tab=True) - urwid.connect_signal(self.content_edit.edit, "change", self.text_changed) - - self.char_count = urwid.Text(["0/{}".format(max_chars)]) + self.edit = edit self.cw_edit = None self.cw_add_button = Button("Add content warning", @@ -31,13 +32,34 @@ class StatusComposer(urwid.Frame): self.cw_remove_button = Button("Remove content warning", on_press=self.remove_content_warning) - self.visibility = ( - in_reply_to.visibility if in_reply_to else visibility - ) + if edit: + if source is None: + text = edit.data["content"] + else: + text = source.get("text", edit.data["content"]) + + if 'spoiler_text' in source: + self.cw_edit = EditBox(multiline=True, allow_tab=True, + edit_text=source['spoiler_text']) + + self.visibility = edit.data["visibility"] + + else: # not edit + text = self.get_initial_text(in_reply_to) + self.visibility = ( + in_reply_to.visibility if in_reply_to else visibility + ) + + self.content_edit = EditBox( + edit_text=text, edit_pos=len(text), multiline=True, allow_tab=True) + urwid.connect_signal(self.content_edit.edit, "change", self.text_changed) + + self.char_count = urwid.Text(["0/{}".format(max_chars)]) + self.visibility_button = Button("Visibility: {}".format(self.visibility), on_press=self.choose_visibility) - self.post_button = Button("Post", on_press=self.post) + self.post_button = Button("Edit" if edit else "Post", on_press=self.post) self.cancel_button = Button("Cancel", on_press=self.close) contents = list(self.generate_list_items()) diff --git a/toot/tui/timeline.py b/toot/tui/timeline.py index 0cc7756..679c6b7 100644 --- a/toot/tui/timeline.py +++ b/toot/tui/timeline.py @@ -101,6 +101,7 @@ class Timeline(urwid.Columns): "[A]ccount" if not status.is_mine else "", "[B]oost", "[D]elete" if status.is_mine else "", + "[E]dit" if status.is_mine else "", "B[o]okmark", "[F]avourite", "[V]iew", @@ -189,6 +190,11 @@ class Timeline(urwid.Columns): self.tui.show_delete_confirmation(status) return + if key in ("e", "E"): + if status.is_mine: + self.tui.async_edit(status) + return + if key in ("f", "F"): self.tui.async_toggle_favourite(self, status) return diff --git a/toot/tui/widgets.py b/toot/tui/widgets.py index f2ae4b8..2c8f907 100644 --- a/toot/tui/widgets.py +++ b/toot/tui/widgets.py @@ -67,3 +67,11 @@ class RadioButton(urwid.AttrWrap): button = urwid.RadioButton(*args, **kwargs) padding = urwid.Padding(button, width=len(args[1]) + 4) return super().__init__(padding, "button", "button_focused") + + +class ModalBox(urwid.Frame): + def __init__(self, message): + text = urwid.Text(message) + filler = urwid.Filler(text, valign='top', top=1, bottom=1) + padding = urwid.Padding(filler, left=1, right=1) + return super().__init__(padding) From 03035c31a0ef0ccc17f1ed8719ecb10811f83afb Mon Sep 17 00:00:00 2001 From: Ivan Habunek Date: Tue, 2 Jan 2024 21:02:38 +0100 Subject: [PATCH 26/31] Fix warning --- toot/cli/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/toot/cli/__init__.py b/toot/cli/__init__.py index 21f4002..5cc995b 100644 --- a/toot/cli/__init__.py +++ b/toot/cli/__init__.py @@ -48,8 +48,8 @@ def get_default_map(): commands = settings.get("commands", {}) # TODO: remove in version 1.0 - tui_old = settings.get("tui", {}) - + tui_old = settings.get("tui", {}).copy() + del tui_old["palette"] if tui_old: # TODO: don't show the warning for [toot.palette] print_warning("Settings section [tui] has been deprecated in favour of [commands.tui].") From db266c563dd07a6313cddb95de58e120fd062c7e Mon Sep 17 00:00:00 2001 From: Ivan Habunek Date: Tue, 2 Jan 2024 21:55:56 +0100 Subject: [PATCH 27/31] Don't set default visibility This way the visiblility will default to the one in user preferences. By default this is 'public'. --- toot/api.py | 2 +- toot/cli/post.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/toot/api.py b/toot/api.py index 3509f47..01dcc74 100644 --- a/toot/api.py +++ b/toot/api.py @@ -183,7 +183,7 @@ def post_status( app, user, status, - visibility='public', + visibility=None, media_ids=None, sensitive=False, spoiler_text=None, diff --git a/toot/cli/post.py b/toot/cli/post.py index afcfa4e..8d54dd1 100644 --- a/toot/cli/post.py +++ b/toot/cli/post.py @@ -40,7 +40,6 @@ from toot.utils.datetime import parse_datetime "--visibility", "-v", help="Post visibility", type=click.Choice(VISIBILITY_CHOICES), - default="public", ) @click.option( "--sensitive", "-s", @@ -119,7 +118,7 @@ def post( media: Tuple[str], descriptions: Tuple[str], thumbnails: Tuple[str], - visibility: str, + visibility: Optional[str], sensitive: bool, spoiler_text: Optional[str], reply_to: Optional[str], From 5a26ab4940f7b5214c9c3c55a3c8c5ef1f0f7332 Mon Sep 17 00:00:00 2001 From: Ivan Habunek Date: Tue, 2 Jan 2024 21:56:51 +0100 Subject: [PATCH 28/31] Don't access the database in tests This requires the mastodon instance to be patched so that email confirmation is not required, but makes it possible to run tests on a remote instance. --- tests/integration/conftest.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 8e3f3be..fbb04f5 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -9,13 +9,11 @@ your test server and database: ``` export TOOT_TEST_BASE_URL="localhost:3000" -export TOOT_TEST_DATABASE_DSN="dbname=mastodon_development" ``` """ import json import os -import psycopg2 import pytest import re import typing as t @@ -36,7 +34,6 @@ def pytest_configure(config): Run = t.Callable[..., Result] # Mastodon database name, used to confirm user registration without having to click the link -DATABASE_DSN = os.getenv("TOOT_TEST_DATABASE_DSN") TOOT_TEST_BASE_URL = os.getenv("TOOT_TEST_BASE_URL") # Toot logo used for testing image upload @@ -56,17 +53,9 @@ def register_account(app: App): email = f"{username}@example.com" response = api.register_account(app, username, email, "password", "en") - confirm_user(email) return User(app.instance, username, response["access_token"]) -def confirm_user(email): - conn = psycopg2.connect(DATABASE_DSN) - cursor = conn.cursor() - cursor.execute("UPDATE users SET confirmed_at = now() WHERE email = %s;", (email,)) - conn.commit() - - # ------------------------------------------------------------------------------ # Fixtures # ------------------------------------------------------------------------------ From 081bc0459e305be3f7c59ec4d05063670e91a789 Mon Sep 17 00:00:00 2001 From: Ivan Habunek Date: Tue, 2 Jan 2024 22:03:31 +0100 Subject: [PATCH 29/31] Bump version, add changelog --- CHANGELOG.md | 8 ++++++++ changelog.yaml | 8 ++++++++ docs/changelog.md | 8 ++++++++ setup.py | 2 +- toot/__init__.py | 2 +- 5 files changed, 26 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index acf986a..9aab8f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,14 @@ Changelog +**0.41.0 (2024-01-02)** + +* Honour user's default visibility set in Mastodon preferences instead of always + defaulting to public visibility (thanks Lexi Winter) +* TUI: Add editing toots (thanks Lexi Winter) +* TUI: Fix a bug which made pallette config in settings not work +* TUI: Show edit datetime in status detail (thanks Lexi Winter) + **0.40.2 (2023-12-28)** * Reinstate `toot post --using` option. diff --git a/changelog.yaml b/changelog.yaml index 34c219d..dee9093 100644 --- a/changelog.yaml +++ b/changelog.yaml @@ -1,3 +1,11 @@ +0.41.0: + date: 2024-01-02 + changes: + - "Honour user's default visibility set in Mastodon preferences instead of always defaulting to public visibility (thanks Lexi Winter)" + - "TUI: Add editing toots (thanks Lexi Winter)" + - "TUI: Fix a bug which made pallette config in settings not work" + - "TUI: Show edit datetime in status detail (thanks Lexi Winter)" + 0.40.2: date: 2023-12-28 changes: diff --git a/docs/changelog.md b/docs/changelog.md index acf986a..9aab8f1 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -3,6 +3,14 @@ Changelog +**0.41.0 (2024-01-02)** + +* Honour user's default visibility set in Mastodon preferences instead of always + defaulting to public visibility (thanks Lexi Winter) +* TUI: Add editing toots (thanks Lexi Winter) +* TUI: Fix a bug which made pallette config in settings not work +* TUI: Show edit datetime in status detail (thanks Lexi Winter) + **0.40.2 (2023-12-28)** * Reinstate `toot post --using` option. diff --git a/setup.py b/setup.py index 38eb5b8..3448655 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ and blocking accounts and other actions. setup( name='toot', - version='0.40.2', + version='0.41.0', description='Mastodon CLI client', long_description=long_description.strip(), author='Ivan Habunek', diff --git a/toot/__init__.py b/toot/__init__.py index 53834ff..1abae79 100644 --- a/toot/__init__.py +++ b/toot/__init__.py @@ -4,7 +4,7 @@ import sys from os.path import join, expanduser from typing import NamedTuple -__version__ = '0.40.2' +__version__ = '0.41.0' class App(NamedTuple): From 964efc5b4c4bd483cee1634e8a774934d5af72de Mon Sep 17 00:00:00 2001 From: Ivan Habunek Date: Tue, 2 Jan 2024 22:06:20 +0100 Subject: [PATCH 30/31] Fix bug which causes a crash if palette is not in settings --- toot/cli/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/toot/cli/__init__.py b/toot/cli/__init__.py index 5cc995b..a6af85a 100644 --- a/toot/cli/__init__.py +++ b/toot/cli/__init__.py @@ -49,7 +49,8 @@ def get_default_map(): # TODO: remove in version 1.0 tui_old = settings.get("tui", {}).copy() - del tui_old["palette"] + if "palette" in tui_old: + del tui_old["palette"] if tui_old: # TODO: don't show the warning for [toot.palette] print_warning("Settings section [tui] has been deprecated in favour of [commands.tui].") From b4cbeeedeb090fd8842012c2d9fd4419c3538078 Mon Sep 17 00:00:00 2001 From: Ivan Habunek Date: Tue, 2 Jan 2024 22:07:53 +0100 Subject: [PATCH 31/31] Bump version, add changelog --- CHANGELOG.md | 4 ++++ changelog.yaml | 5 +++++ docs/changelog.md | 4 ++++ setup.py | 2 +- toot/__init__.py | 2 +- 5 files changed, 15 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9aab8f1..436ee03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ Changelog +**0.41.1 (2024-01-02)** + +* Fix a crash in settings parsing code + **0.41.0 (2024-01-02)** * Honour user's default visibility set in Mastodon preferences instead of always diff --git a/changelog.yaml b/changelog.yaml index dee9093..8204466 100644 --- a/changelog.yaml +++ b/changelog.yaml @@ -1,3 +1,8 @@ +0.41.1: + date: 2024-01-02 + changes: + - "Fix a crash in settings parsing code" + 0.41.0: date: 2024-01-02 changes: diff --git a/docs/changelog.md b/docs/changelog.md index 9aab8f1..436ee03 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -3,6 +3,10 @@ Changelog +**0.41.1 (2024-01-02)** + +* Fix a crash in settings parsing code + **0.41.0 (2024-01-02)** * Honour user's default visibility set in Mastodon preferences instead of always diff --git a/setup.py b/setup.py index 3448655..7b3b96f 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ and blocking accounts and other actions. setup( name='toot', - version='0.41.0', + version='0.41.1', description='Mastodon CLI client', long_description=long_description.strip(), author='Ivan Habunek', diff --git a/toot/__init__.py b/toot/__init__.py index 1abae79..2a3f4ab 100644 --- a/toot/__init__.py +++ b/toot/__init__.py @@ -4,7 +4,7 @@ import sys from os.path import join, expanduser from typing import NamedTuple -__version__ = '0.41.0' +__version__ = '0.41.1' class App(NamedTuple):