diff --git a/.flake8 b/.flake8
index ac93b71..8d01264 100644
--- a/.flake8
+++ b/.flake8
@@ -1,4 +1,4 @@
[flake8]
-exclude=build,tests,tmp,venv,toot/tui/scroll.py
+exclude=build,tests,tmp,venv,_env,toot/tui/scroll.py
ignore=E128,W503,W504
max-line-length=120
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f151780..0d92e46 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,20 @@ Changelog
+**0.44.1 (2024-08-12)**
+
+* Make it possible to pass status URL as status_id, experimental (thanks
+ @nemobis)
+* Show statuses in search results (thanks @nemobis)
+
+**0.44.0 (2024-08-12)**
+
+* **BREAKING:** Require Python 3.8+
+* Add `toot diag` for displaying diagnostic info (thanks Dan Schwarz)
+* TUI: Improve image support (thanks @AnonymouX47)
+* TUI: Add support for indexed color image rendering (#483) (thanks Dan Schwarz)
+* TUI: Fix crash bug (#483) (thanks Dan Schwarz)
+
**0.43.0 (2024-04-13)**
* TUI: Support displaying images (thanks Dan Schwarz)
diff --git a/README.rst b/README.rst
index be20e19..1235c34 100644
--- a/README.rst
+++ b/README.rst
@@ -37,11 +37,18 @@ Terminal User Interface
toot includes a terminal user interface (TUI). Run it with ``toot tui``.
+TUI Features:
+-------------
+
+* Block graphic image display (requires optional libraries `pillow `, `term-image `, and `urwidgets `)
+* Bitmapped image display in `kitty ` terminal ``toot tui -f kitty``
+* Bitmapped image display in `iTerm2 `, or `WezTerm ` terminal ``toot tui -f iterm``
+
+
.. image :: https://raw.githubusercontent.com/ihabunek/toot/master/docs/images/tui_list.png
.. image :: https://raw.githubusercontent.com/ihabunek/toot/master/docs/images/tui_compose.png
-
License
-------
diff --git a/changelog.yaml b/changelog.yaml
index 3bf04c6..60e75bd 100644
--- a/changelog.yaml
+++ b/changelog.yaml
@@ -1,7 +1,17 @@
+0.44.1:
+ date: 2024-08-12
+ changes:
+ - "Make it possible to pass status URL as status_id, experimental (thanks @nemobis)"
+ - "Show statuses in search results (thanks @nemobis)"
+
0.44.0:
- date: TBA
+ date: 2024-08-12
changes:
- "**BREAKING:** Require Python 3.8+"
+ - "Add `toot diag` for displaying diagnostic info (thanks Dan Schwarz)"
+ - "TUI: Improve image support (thanks @AnonymouX47)"
+ - "TUI: Add support for indexed color image rendering (#483) (thanks Dan Schwarz)"
+ - "TUI: Fix crash bug (#483) (thanks Dan Schwarz)"
0.43.0:
date: 2024-04-13
diff --git a/docs/changelog.md b/docs/changelog.md
index f151780..0d92e46 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -3,6 +3,20 @@ Changelog
+**0.44.1 (2024-08-12)**
+
+* Make it possible to pass status URL as status_id, experimental (thanks
+ @nemobis)
+* Show statuses in search results (thanks @nemobis)
+
+**0.44.0 (2024-08-12)**
+
+* **BREAKING:** Require Python 3.8+
+* Add `toot diag` for displaying diagnostic info (thanks Dan Schwarz)
+* TUI: Improve image support (thanks @AnonymouX47)
+* TUI: Add support for indexed color image rendering (#483) (thanks Dan Schwarz)
+* TUI: Fix crash bug (#483) (thanks Dan Schwarz)
+
**0.43.0 (2024-04-13)**
* TUI: Support displaying images (thanks Dan Schwarz)
diff --git a/docs/images/tui_compose.png b/docs/images/tui_compose.png
index 0a5ca14..2baaf48 100644
Binary files a/docs/images/tui_compose.png and b/docs/images/tui_compose.png differ
diff --git a/docs/images/tui_list.png b/docs/images/tui_list.png
index db7e593..6dd4f7e 100644
Binary files a/docs/images/tui_list.png and b/docs/images/tui_list.png differ
diff --git a/docs/images/tui_poll.png b/docs/images/tui_poll.png
deleted file mode 100644
index 9886ec9..0000000
Binary files a/docs/images/tui_poll.png and /dev/null differ
diff --git a/pyproject.toml b/pyproject.toml
index d65bcc1..d7055b7 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -81,5 +81,4 @@ packages=[
[tool.setuptools_scm]
[tool.pyright]
-typeCheckingMode = "strict"
-pythonVersion = "3.8"
\ No newline at end of file
+pythonVersion = "3.8"
diff --git a/toot/api.py b/toot/api.py
index 01dcc74..7bd2032 100644
--- a/toot/api.py
+++ b/toot/api.py
@@ -44,10 +44,34 @@ def _account_action(app, user, account, action) -> Response:
def _status_action(app, user, status_id, action, data=None) -> Response:
+ status_id = _resolve_status_id(app, user, status_id)
url = f"/api/v1/statuses/{status_id}/{action}"
return http.post(app, user, url, data=data)
+def _resolve_status_id(app, user, id_or_url) -> str:
+ """
+ If given an URL instead of status ID, attempt to resolve the status ID.
+
+ TODO: Not 100% sure this is the correct way of doing this, but it seems to
+ work for all test cases I've thrown at it. So leaving it undocumented until
+ we're happy it works.
+ """
+ if re.match(r"^https?://", id_or_url):
+ response = search(app, user, id_or_url, resolve=True, type="statuses")
+ statuses = response.json().get("statuses")
+
+ if not statuses:
+ raise ConsoleError(f"Cannot find status matching URL {id_or_url}")
+
+ if len(statuses) > 1:
+ raise ConsoleError(f"Found multiple statuses mathcing URL {id_or_url}")
+
+ return statuses[0]["id"]
+
+ return id_or_url
+
+
def _tag_action(app, user, tag_name, action) -> Response:
url = f"/api/v1/tags/{tag_name}/{action}"
return http.post(app, user, url)
diff --git a/toot/cli/__init__.py b/toot/cli/__init__.py
index a4698ff..f525841 100644
--- a/toot/cli/__init__.py
+++ b/toot/cli/__init__.py
@@ -173,6 +173,7 @@ def cli(ctx: click.Context, max_width: int, color: bool, debug: bool, as_user: s
from toot.cli import accounts # noqa
from toot.cli import auth # noqa
+from toot.cli import diag # noqa
from toot.cli import lists # noqa
from toot.cli import post # noqa
from toot.cli import read # noqa
diff --git a/toot/cli/diag.py b/toot/cli/diag.py
new file mode 100644
index 0000000..fed0079
--- /dev/null
+++ b/toot/cli/diag.py
@@ -0,0 +1,30 @@
+from typing import Optional
+import click
+from toot import api, config
+from toot.entities import Data
+from toot.output import print_diags
+from toot.cli import cli
+
+
+@cli.command()
+@click.option(
+ "-f",
+ "--files",
+ is_flag=True,
+ help="Print contents of the config and settings files in diagnostic output",
+)
+@click.option(
+ "-s",
+ "--server",
+ is_flag=True,
+ help="Print information about the curren server in diagnostic output",
+)
+def diag(files: bool, server: bool):
+ """Display useful information for diagnosing problems"""
+ instance_dict: Optional[Data] = None
+ if server:
+ _, app = config.get_active_user_app()
+ if app:
+ instance_dict = api.get_instance(app.base_url).json()
+
+ print_diags(instance_dict, files)
diff --git a/toot/cli/post.py b/toot/cli/post.py
index f1acb2b..820aab6 100644
--- a/toot/cli/post.py
+++ b/toot/cli/post.py
@@ -9,6 +9,8 @@ from typing import BinaryIO, Optional, Tuple
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.tui.constants import VISIBILITY_OPTIONS # move to top-level ?
+
from toot.cli.validators import validate_duration, validate_language
from toot.entities import MediaAttachment, from_dict
from toot.utils import EOF_KEY, delete_tmp_status_file, editor_input, multiline_input
@@ -38,7 +40,10 @@ from toot.utils.datetime import parse_datetime
)
@click.option(
"--visibility", "-v",
- help="Post visibility",
+ help="Post visibility: " + "; "
+ .join("{} = {}".format(visibility, description)
+ for visibility, caption, description in VISIBILITY_OPTIONS),
+ default=VISIBILITY_CHOICES[0],
type=click.Choice(VISIBILITY_CHOICES),
)
@click.option(
diff --git a/toot/output.py b/toot/output.py
index e9b0812..8f2715f 100644
--- a/toot/output.py
+++ b/toot/output.py
@@ -1,13 +1,18 @@
import click
+import platform
import re
import shutil
import textwrap
import typing as t
-from toot.entities import Account, Instance, Notification, Poll, Status, List
+from datetime import datetime, timezone
+from importlib.metadata import version
+from wcwidth import wcswidth
+
+from toot import __version__, config, settings
+from toot.entities import Account, Data, Instance, Notification, Poll, Status, List
from toot.utils import get_text, html_to_paragraphs
from toot.wcstring import wc_wrap
-from wcwidth import wcswidth
DEFAULT_WIDTH = 80
@@ -154,8 +159,9 @@ def print_list_accounts(accounts):
def print_search_results(results):
- accounts = results["accounts"]
- hashtags = results["hashtags"]
+ accounts = results.get("accounts")
+ hashtags = results.get("hashtags")
+ statuses = results.get("statuses")
if accounts:
click.echo("\nAccounts:")
@@ -165,7 +171,12 @@ def print_search_results(results):
click.echo("\nHashtags:")
click.echo(", ".join([format_tag_name(tag) for tag in hashtags]))
- if not accounts and not hashtags:
+ if statuses:
+ click.echo("\nStatuses:")
+ for status in statuses:
+ click.echo(f" * {green(status['id'])} {status['url']}")
+
+ if not accounts and not hashtags and not statuses:
click.echo("Nothing found")
@@ -314,6 +325,78 @@ def format_account_name(account: Account) -> str:
return acct
+def print_diags(instance_dict: t.Optional[Data], include_files: bool):
+ click.echo(f'{green("## Toot Diagnostics")}')
+ click.echo()
+
+ now = datetime.now(timezone.utc)
+ click.echo(f'{green("Current Date/Time:")} {now.strftime("%Y-%m-%d %H:%M:%S %Z")}')
+
+ click.echo(f'{green("Toot version:")} {__version__}')
+ click.echo(f'{green("Platform:")} {platform.platform()}')
+
+ # print distro - only call if available (python 3.10+)
+ fd_os_release = getattr(platform, "freedesktop_os_release", None) # novermin
+ if callable(fd_os_release): # novermin
+ try:
+ name = platform.freedesktop_os_release()['PRETTY_NAME']
+ click.echo(f'{green("Distro:")} {name}')
+ except: # noqa
+ pass
+
+ click.echo(f'{green("Python version:")} {platform.python_version()}')
+ click.echo()
+
+ click.echo(green("Dependency versions:"))
+
+ deps = sorted(['beautifulsoup4', 'click', 'requests', 'tomlkit', 'urwid', 'wcwidth',
+ 'pillow', 'term-image', 'urwidgets', 'flake8', 'pytest', 'setuptools',
+ 'vermin', 'typing-extensions'])
+
+ for dep in deps:
+ try:
+ ver = version(dep)
+ except: # noqa
+ ver = yellow("not installed")
+
+ click.echo(f" * {dep}: {ver}")
+ click.echo()
+
+ click.echo(f'{green("Settings file path:")} {settings.get_settings_path()}')
+ click.echo(f'{green("Config file path:")} {config.get_config_file_path()}')
+
+ if instance_dict:
+ click.echo(f'{green("Server URI:")} {instance_dict.get("uri")}')
+ click.echo(f'{green("Server version:")} {instance_dict.get("version")}')
+
+ if include_files:
+ click.echo(f'{green("Settings file contents:")}')
+ try:
+ with open(settings.get_settings_path(), 'r') as f:
+ print("```toml")
+ print(f.read())
+ print("```")
+ except: # noqa
+ click.echo(f'{yellow("Could not open settings file")}')
+ click.echo()
+
+ click.echo(f'{green("Config file contents:")}')
+ click.echo("```json")
+ try:
+ with open(config.get_config_file_path(), 'r') as f:
+ for line in f:
+ # Do not output client secret or access token lines
+ if "client_" in line or "token" in line:
+ click.echo(f'{yellow("***CONTENTS REDACTED***")}')
+ else:
+ click.echo(line, nl=False)
+ click.echo()
+
+ except: # noqa
+ click.echo(f'{yellow("Could not open config file")}')
+ click.echo("```")
+
+
# Shorthand functions for coloring output
def blue(text: t.Any) -> str:
diff --git a/toot/tui/timeline.py b/toot/tui/timeline.py
index 018ca40..1aa4936 100644
--- a/toot/tui/timeline.py
+++ b/toot/tui/timeline.py
@@ -484,15 +484,16 @@ class StatusDetails(urwid.Pile):
yield self.image_widget(m["url"], aspect=aspect)
yield urwid.Divider()
# video media may include a preview URL, show that as a fallback
- elif m["preview_url"].lower().endswith(('.jpg', '.jpeg', '.png', '.gif', '.svg', '.webp')):
- yield urwid.Text("")
- try:
- aspect = float(m["meta"]["small"]["aspect"])
- except Exception:
- aspect = None
- if image_support_enabled():
- yield self.image_widget(m["preview_url"], aspect=aspect)
- yield urwid.Divider()
+ elif m["preview_url"]:
+ if m["preview_url"].lower().endswith(('.jpg', '.jpeg', '.png', '.gif', '.svg', '.webp')):
+ yield urwid.Text("")
+ try:
+ aspect = float(m["meta"]["small"]["aspect"])
+ except Exception:
+ aspect = None
+ if image_support_enabled():
+ yield self.image_widget(m["preview_url"], aspect=aspect)
+ yield urwid.Divider()
yield ("pack", url_to_widget(m["url"]))
poll = status.original.data.get("poll")