Skip to content

Commit

Permalink
Add post --scheduled-in option for easier scheduling
Browse files Browse the repository at this point in the history
  • Loading branch information
ihabunek committed Dec 3, 2022
1 parent 66f1883 commit 4f0c367
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 7 deletions.
5 changes: 5 additions & 0 deletions changelog.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
0.31.0:
date: "TBA"
changes:
- "Add `post --scheduled-in` option for easier scheduling"

0.30.1:
date: 2022-11-30
changes:
Expand Down
42 changes: 38 additions & 4 deletions tests/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from toot import CLIENT_NAME, CLIENT_WEBSITE, api, App, User
from toot.console import run_command
from toot.exceptions import ConsoleError, NotFoundError
from toot.tui.utils import parse_datetime
from toot.utils import get_text
from unittest import mock

Expand Down Expand Up @@ -146,17 +147,50 @@ def test_post_visibility(app, user, run):
assert status["visibility"] == visibility


def test_post_scheduled(app, user, run):
def test_post_scheduled_at(app, user, run):
text = str(uuid.uuid4())
scheduled_at = datetime.now(timezone.utc).replace(microsecond=0) + timedelta(minutes=10)

out = run("post", "foo", "--scheduled-at", scheduled_at.isoformat())
out = run("post", text, "--scheduled-at", scheduled_at.isoformat())
assert "Toot scheduled for" in out

[status] = api.scheduled_statuses(app, user)
assert status["params"]["text"] == "foo"
statuses = api.scheduled_statuses(app, user)
[status] = [s for s in statuses if s["params"]["text"] == text]
assert datetime.strptime(status["scheduled_at"], "%Y-%m-%dT%H:%M:%S.%f%z") == scheduled_at


def test_post_scheduled_in(app, user, run):
text = str(uuid.uuid4())

variants = [
("1 day", timedelta(days=1)),
("1 day 6 hours", timedelta(days=1, hours=6)),
("1 day 6 hours 13 minutes", timedelta(days=1, hours=6, minutes=13)),
("1 day 6 hours 13 minutes 51 second", timedelta(days=1, hours=6, minutes=13, seconds=51)),
("2d", timedelta(days=2)),
("2d6h", timedelta(days=2, hours=6)),
("2d6h13m", timedelta(days=2, hours=6, minutes=13)),
("2d6h13m51s", timedelta(days=2, hours=6, minutes=13, seconds=51)),
]

datetimes = []
for scheduled_in, delta in variants:
out = run("post", text, "--scheduled-in", scheduled_in)
dttm = datetime.utcnow() + delta
assert out.startswith(f"Toot scheduled for: {str(dttm)[:16]}")
datetimes.append(dttm)

scheduled = api.scheduled_statuses(app, user)
scheduled = [s for s in scheduled if s["params"]["text"] == text]
scheduled = sorted(scheduled, key=lambda s: s["scheduled_at"])
assert len(scheduled) == 8

for expected, status in zip(datetimes, scheduled):
actual = datetime.strptime(status["scheduled_at"], "%Y-%m-%dT%H:%M:%S.%fZ")
delta = expected - actual
assert delta.total_seconds() < 5


def test_media_attachments(app, user, run):
assets_dir = path.realpath(path.join(path.dirname(__file__), "assets"))

Expand Down
22 changes: 19 additions & 3 deletions toot/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

import sys

from datetime import datetime, timedelta
from toot import api, config
from toot.auth import login_interactive, login_browser_interactive, create_app_interactive
from toot.exceptions import ApiError, ConsoleError
from toot.output import (print_out, print_instance, print_account, print_acct_list,
print_search_results, print_timeline, print_notifications)
from toot.tui.utils import parse_datetime
from toot.utils import editor_input, multiline_input, EOF_KEY


Expand Down Expand Up @@ -84,6 +86,7 @@ def post(app, user, args):

media_ids = _upload_media(app, user, args)
status_text = _get_status_text(args.text, args.editor)
scheduled_at = _get_scheduled_at(args.scheduled_at, args.scheduled_in)

if not status_text and not media_ids:
raise ConsoleError("You must specify either text or media to post.")
Expand All @@ -96,14 +99,16 @@ def post(app, user, args):
spoiler_text=args.spoiler_text,
in_reply_to_id=args.reply_to,
language=args.language,
scheduled_at=args.scheduled_at,
scheduled_at=scheduled_at,
content_type=args.content_type
)

if "scheduled_at" in response:
print_out("Toot scheduled for: <green>{}</green>".format(response["scheduled_at"]))
scheduled_at = parse_datetime(response["scheduled_at"])
scheduled_at = datetime.strftime(scheduled_at, "%Y-%m-%d %H:%M:%S%z")
print_out(f"Toot scheduled for: <green>{scheduled_at}</green>")
else:
print_out("Toot posted: <green>{}</green>".format(response.get('url')))
print_out(f"Toot posted: <green>{response['url']}")


def _get_status_text(text, editor):
Expand All @@ -122,6 +127,17 @@ def _get_status_text(text, editor):
return text


def _get_scheduled_at(scheduled_at, scheduled_in):
if scheduled_at:
return scheduled_at

if scheduled_in:
scheduled_at = datetime.utcnow() + timedelta(seconds=scheduled_in)
return scheduled_at.isoformat()

return None


def _upload_media(app, user, args):
# Match media to corresponding description and upload
media = args.media or []
Expand Down
8 changes: 8 additions & 0 deletions toot/console.py
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,14 @@ def editor(value):
"help": "ISO 8601 Datetime at which to schedule a status. Must "
"be at least 5 minutes in the future.",
}),
(["--scheduled-in"], {
"type": duration,
"help": """Schedule the toot to be posted after a given amount
of time. Examples: "1 day", "2 hours 30 minutes",
"5 minutes 30 seconds" or any combination of above.
Shorthand: "1d", "2h30m", "5m30s". Must be at least 5
minutes.""",
}),
(["-t", "--content-type"], {
"type": str,
"help": "MIME type for the status text (not supported on all instances)",
Expand Down

0 comments on commit 4f0c367

Please sign in to comment.