Skip to content

Commit

Permalink
Add post --reply-last option
Browse files Browse the repository at this point in the history
fixes #464
  • Loading branch information
ihabunek committed Sep 22, 2024
1 parent 3ff9bc7 commit 76bb1b5
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 2 deletions.
38 changes: 37 additions & 1 deletion tests/integration/test_post.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from tests.integration.conftest import ASSETS_DIR, Run, assert_ok, posted_status_id
from toot import CLIENT_NAME, CLIENT_WEBSITE, api, cli
from toot.cache import clear_last_post_id, get_last_post_id
from toot.utils import get_text
from unittest import mock

Expand Down Expand Up @@ -340,7 +341,7 @@ def test_media_attachment_without_text(mock_read, mock_ml, app, user, run):
assert attachment["meta"]["original"]["size"] == "50x50"


def test_reply_thread(app, user, friend, run):
def test_reply_to(app, user, friend, run):
status = api.post_status(app, friend, "This is the status").json()

result = run(cli.post.post, "--reply-to", status["id"], "This is the reply")
Expand All @@ -362,3 +363,38 @@ def test_reply_thread(app, user, friend, run):
assert user.username in s2
assert status["id"] in s1
assert reply["id"] in s2


def test_reply_last(app, user, run):
result_1 = run(cli.post.post, "one")
status_id_1 = posted_status_id(result_1.stdout)
assert get_last_post_id(app, user) == status_id_1

result_2 = run(cli.post.post, "two", "--reply-last")
status_id_2 = posted_status_id(result_2.stdout)
assert get_last_post_id(app, user) == status_id_2

result_3 = run(cli.post.post, "two", "--reply-last")
status_id_3 = posted_status_id(result_3.stdout)
assert get_last_post_id(app, user) == status_id_3

status_1 = api.fetch_status(app, user, status_id_1).json()
status_2 = api.fetch_status(app, user, status_id_2).json()
status_3 = api.fetch_status(app, user, status_id_3).json()

assert status_1["in_reply_to_id"] is None
assert status_2["in_reply_to_id"] == status_id_1
assert status_3["in_reply_to_id"] == status_id_2


def test_reply_last_fails_if_no_last_id(app, user, run: Run):
clear_last_post_id(app, user)
result = run(cli.post.post, "one", "--reply-last")
assert result.exit_code == 1
assert result.stderr.strip() == f"Error: Cannot reply-last, no previous post ID found for {user.username}@{app.instance}"


def test_reply_last_and_reply_to_are_exclusive(app, user, run: Run):
result = run(cli.post.post, "one", "--reply-last", "--reply-to", "123")
assert result.exit_code == 1
assert result.stderr.strip() == f"Error: --reply-last and --reply-to are mutually exclusive"
61 changes: 61 additions & 0 deletions toot/cache.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import os
import sys

from pathlib import Path
from typing import Optional

from toot import App, User

CACHE_SUBFOLDER = "toot"


def save_last_post_id(app: App, user: User, id: str) -> None:
"""Save ID of the last post posted to this instance"""
path = _last_post_id_path(app, user)
with open(path, "w") as f:
f.write(id)


def get_last_post_id(app: App, user: User) -> Optional[str]:
"""Retrieve ID of the last post posted to this instance"""
path = _last_post_id_path(app, user)
if path.exists():
with open(path, "r") as f:
return f.read()


def clear_last_post_id(app: App, user: User) -> None:
"""Delete the cached last post ID for this instance"""
path = _last_post_id_path(app, user)
path.unlink(missing_ok=True)


def _last_post_id_path(app: App, user: User):
return get_cache_dir("last_post_ids") / f"{user.username}_{app.instance}"


def get_cache_dir(subdir: Optional[str] = None) -> Path:
path = _cache_dir_path()
if subdir:
path = path / subdir
path.mkdir(parents=True, exist_ok=True)
return path


def _cache_dir_path() -> Path:
"""Returns the path to the cache directory"""

# Windows
if sys.platform == "win32" and "LOCALAPPDATA" in os.environ:
return Path(os.environ["LOCALAPPDATA"], CACHE_SUBFOLDER)

# Mac OS
if sys.platform == "darwin":
return Path.home() / "Library" / "Caches" / CACHE_SUBFOLDER

# Respect XDG_CONFIG_HOME env variable if set
# https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
if "XDG_CACHE_HOME" in os.environ:
return Path(os.environ["XDG_CACHE_HOME"], CACHE_SUBFOLDER)

return Path.home() / ".cache" / CACHE_SUBFOLDER
27 changes: 26 additions & 1 deletion toot/cli/post.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from typing import BinaryIO, Optional, Tuple

from toot import api, config
from toot.cache import get_last_post_id, save_last_post_id
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 ?
Expand Down Expand Up @@ -60,6 +61,12 @@
"--reply-to", "-r",
help="ID of the status being replied to, if status is a reply.",
)
@click.option(
"--reply-last", "-R",
help="Reply to the last posted status to continue the thread.",
is_flag=True,
default=False,
)
@click.option(
"--language", "-l",
help="ISO 639-1 language code of the toot, to skip automatic detection.",
Expand Down Expand Up @@ -137,7 +144,8 @@ def post(
poll_multiple: bool,
poll_hide_totals: bool,
json: bool,
using: str
using: str,
reply_last: bool,
):
"""Post a new status"""
if len(media) > 4:
Expand All @@ -153,6 +161,7 @@ def post(
media_ids = _upload_media(app, user, media, descriptions, thumbnails)
status_text = _get_status_text(text, editor, media)
scheduled_at = _get_scheduled_at(scheduled_at, scheduled_in)
reply_to = _get_reply_to(app, user, reply_to, reply_last)

if not status_text and not media_ids:
raise click.ClickException("You must specify either text or media to post.")
Expand Down Expand Up @@ -186,6 +195,8 @@ def post(
else:
click.echo(f"Toot posted: {status['url']}")

save_last_post_id(app, user, status["id"])

delete_tmp_status_file()


Expand Down Expand Up @@ -245,6 +256,20 @@ def _get_scheduled_at(scheduled_at, scheduled_in):
return None


def _get_reply_to(app, user, reply_to, reply_last):
if reply_last and reply_to:
raise click.ClickException("--reply-last and --reply-to are mutually exclusive")

if reply_last:
last_id = get_last_post_id(app, user)
if last_id:
return last_id
else:
raise click.ClickException(f"Cannot reply-last, no previous post ID found for {user.username}@{app.instance}")

return reply_to


def _upload_media(app, user, media, descriptions, thumbnails):
# Match media to corresponding descriptions and thumbnail
media = media or []
Expand Down

0 comments on commit 76bb1b5

Please sign in to comment.