1
0
mirror of https://github.com/ihabunek/toot.git synced 2025-04-18 00:48:47 -04:00

Merge branch 'ihabunek:master' into master

This commit is contained in:
Fipaddict 2024-08-12 14:15:23 +02:00 committed by GitHub
commit 09980470d2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 208 additions and 20 deletions

View File

@ -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

View File

@ -3,6 +3,20 @@ Changelog
<!-- Do not edit. This file is automatically generated from changelog.yaml.-->
**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)

View File

@ -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 <https://pypi.org/project/pillow/>`, `term-image <https://pypi.org/project/term-image/>`, and `urwidgets <https://pypi.org/project/urwidgets/>`)
* Bitmapped image display in `kitty <https://sw.kovidgoyal.net/kitty/>` terminal ``toot tui -f kitty``
* Bitmapped image display in `iTerm2 <https://iterm2.com/>`, or `WezTerm <https://wezfurlong.org/wezterm/index.html>` 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
-------

View File

@ -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

View File

@ -3,6 +3,20 @@ Changelog
<!-- Do not edit. This file is automatically generated from changelog.yaml.-->
**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)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 237 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 192 KiB

After

Width:  |  Height:  |  Size: 617 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 209 KiB

View File

@ -81,5 +81,4 @@ packages=[
[tool.setuptools_scm]
[tool.pyright]
typeCheckingMode = "strict"
pythonVersion = "3.8"

View File

@ -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)

View File

@ -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

30
toot/cli/diag.py Normal file
View File

@ -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)

View File

@ -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(

View File

@ -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:

View File

@ -484,7 +484,8 @@ 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')):
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"])