kopia lustrzana https://github.com/ihabunek/toot
				
				
				
			
						commit
						1098f63170
					
				| 
						 | 
				
			
			@ -137,6 +137,7 @@ def test_post(app, user, run):
 | 
			
		|||
    assert status["visibility"] == "public"
 | 
			
		||||
    assert status["sensitive"] is False
 | 
			
		||||
    assert status["spoiler_text"] == ""
 | 
			
		||||
    assert status["poll"] is None
 | 
			
		||||
 | 
			
		||||
    # Pleroma doesn't return the application
 | 
			
		||||
    if status["application"]:
 | 
			
		||||
| 
						 | 
				
			
			@ -196,6 +197,92 @@ def test_post_scheduled_in(app, user, run):
 | 
			
		|||
        assert delta.total_seconds() < 5
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_post_poll(app, user, run):
 | 
			
		||||
    text = str(uuid.uuid4())
 | 
			
		||||
 | 
			
		||||
    out = run(
 | 
			
		||||
        "post", text,
 | 
			
		||||
        "--poll-option", "foo",
 | 
			
		||||
        "--poll-option", "bar",
 | 
			
		||||
        "--poll-option", "baz",
 | 
			
		||||
        "--poll-option", "qux",
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    status_id = _posted_status_id(out)
 | 
			
		||||
 | 
			
		||||
    status = api.fetch_status(app, user, status_id)
 | 
			
		||||
    assert status["poll"]["expired"] is False
 | 
			
		||||
    assert status["poll"]["multiple"] is False
 | 
			
		||||
    assert status["poll"]["options"] == [
 | 
			
		||||
        {"title": "foo", "votes_count": 0},
 | 
			
		||||
        {"title": "bar", "votes_count": 0},
 | 
			
		||||
        {"title": "baz", "votes_count": 0},
 | 
			
		||||
        {"title": "qux", "votes_count": 0}
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    # Test expires_at is 24h by default
 | 
			
		||||
    actual = datetime.strptime(status["poll"]["expires_at"], "%Y-%m-%dT%H:%M:%S.%f%z")
 | 
			
		||||
    expected = datetime.now(timezone.utc) + timedelta(days=1)
 | 
			
		||||
    delta = actual - expected
 | 
			
		||||
    assert delta.total_seconds() < 5
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_post_poll_multiple(app, user, run):
 | 
			
		||||
    text = str(uuid.uuid4())
 | 
			
		||||
 | 
			
		||||
    out = run(
 | 
			
		||||
        "post", text,
 | 
			
		||||
        "--poll-option", "foo",
 | 
			
		||||
        "--poll-option", "bar",
 | 
			
		||||
        "--poll-multiple"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    status_id = _posted_status_id(out)
 | 
			
		||||
 | 
			
		||||
    status = api.fetch_status(app, user, status_id)
 | 
			
		||||
    assert status["poll"]["multiple"] is True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_post_poll_expires_in(app, user, run):
 | 
			
		||||
    text = str(uuid.uuid4())
 | 
			
		||||
 | 
			
		||||
    out = run(
 | 
			
		||||
        "post", text,
 | 
			
		||||
        "--poll-option", "foo",
 | 
			
		||||
        "--poll-option", "bar",
 | 
			
		||||
        "--poll-expires-in", "8h",
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    status_id = _posted_status_id(out)
 | 
			
		||||
 | 
			
		||||
    status = api.fetch_status(app, user, status_id)
 | 
			
		||||
    actual = datetime.strptime(status["poll"]["expires_at"], "%Y-%m-%dT%H:%M:%S.%f%z")
 | 
			
		||||
    expected = datetime.now(timezone.utc) + timedelta(hours=8)
 | 
			
		||||
    delta = actual - expected
 | 
			
		||||
    assert delta.total_seconds() < 5
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_post_poll_hide_totals(app, user, run):
 | 
			
		||||
    text = str(uuid.uuid4())
 | 
			
		||||
 | 
			
		||||
    out = run(
 | 
			
		||||
        "post", text,
 | 
			
		||||
        "--poll-option", "foo",
 | 
			
		||||
        "--poll-option", "bar",
 | 
			
		||||
        "--poll-hide-totals"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    status_id = _posted_status_id(out)
 | 
			
		||||
 | 
			
		||||
    status = api.fetch_status(app, user, status_id)
 | 
			
		||||
 | 
			
		||||
    # votes_count is None when totals are hidden
 | 
			
		||||
    assert status["poll"]["options"] == [
 | 
			
		||||
        {"title": "foo", "votes_count": None},
 | 
			
		||||
        {"title": "bar", "votes_count": None},
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_post_language(app, user, run):
 | 
			
		||||
    out = run("post", "test", "--language", "hr")
 | 
			
		||||
    status_id = _posted_status_id(out)
 | 
			
		||||
| 
						 | 
				
			
			@ -232,7 +319,7 @@ def test_media_thumbnail(app, user, run):
 | 
			
		|||
    assert media["preview_url"].endswith(".png")
 | 
			
		||||
 | 
			
		||||
    # Video properties
 | 
			
		||||
    assert media["meta"]["original"]["duration"] == 5.58
 | 
			
		||||
    assert int(media["meta"]["original"]["duration"]) == 5
 | 
			
		||||
    assert media["meta"]["original"]["height"] == 320
 | 
			
		||||
    assert media["meta"]["original"]["width"] == 560
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										18
									
								
								toot/api.py
								
								
								
								
							
							
						
						
									
										18
									
								
								toot/api.py
								
								
								
								
							| 
						 | 
				
			
			@ -172,6 +172,10 @@ def post_status(
 | 
			
		|||
    language=None,
 | 
			
		||||
    scheduled_at=None,
 | 
			
		||||
    content_type=None,
 | 
			
		||||
    poll_options=None,
 | 
			
		||||
    poll_expires_in=None,
 | 
			
		||||
    poll_multiple=None,
 | 
			
		||||
    poll_hide_totals=None,
 | 
			
		||||
):
 | 
			
		||||
    """
 | 
			
		||||
    Publish a new status.
 | 
			
		||||
| 
						 | 
				
			
			@ -184,7 +188,7 @@ def post_status(
 | 
			
		|||
 | 
			
		||||
    # Strip keys for which value is None
 | 
			
		||||
    # Sending null values doesn't bother Mastodon, but it breaks Pleroma
 | 
			
		||||
    json = drop_empty_values({
 | 
			
		||||
    data = drop_empty_values({
 | 
			
		||||
        'status': status,
 | 
			
		||||
        'media_ids': media_ids,
 | 
			
		||||
        'visibility': visibility,
 | 
			
		||||
| 
						 | 
				
			
			@ -193,10 +197,18 @@ def post_status(
 | 
			
		|||
        'language': language,
 | 
			
		||||
        'scheduled_at': scheduled_at,
 | 
			
		||||
        'content_type': content_type,
 | 
			
		||||
        'spoiler_text': spoiler_text
 | 
			
		||||
        'spoiler_text': spoiler_text,
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    return http.post(app, user, '/api/v1/statuses', json=json, headers=headers).json()
 | 
			
		||||
    if poll_options:
 | 
			
		||||
        data["poll"] = {
 | 
			
		||||
            "options": poll_options,
 | 
			
		||||
            "expires_in": poll_expires_in,
 | 
			
		||||
            "multiple": poll_multiple,
 | 
			
		||||
            "hide_totals": poll_hide_totals,
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    return http.post(app, user, '/api/v1/statuses', json=data, headers=headers).json()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def fetch_status(app, user, id):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -87,7 +87,7 @@ def post(app, user, args):
 | 
			
		|||
        raise ConsoleError("Cannot attach more than 4 files.")
 | 
			
		||||
 | 
			
		||||
    media_ids = _upload_media(app, user, args)
 | 
			
		||||
    status_text = _get_status_text(args.text, args.editor)
 | 
			
		||||
    status_text = _get_status_text(args.text, args.editor, args.media)
 | 
			
		||||
    scheduled_at = _get_scheduled_at(args.scheduled_at, args.scheduled_in)
 | 
			
		||||
 | 
			
		||||
    if not status_text and not media_ids:
 | 
			
		||||
| 
						 | 
				
			
			@ -102,7 +102,11 @@ def post(app, user, args):
 | 
			
		|||
        in_reply_to_id=args.reply_to,
 | 
			
		||||
        language=args.language,
 | 
			
		||||
        scheduled_at=scheduled_at,
 | 
			
		||||
        content_type=args.content_type
 | 
			
		||||
        content_type=args.content_type,
 | 
			
		||||
        poll_options=args.poll_option,
 | 
			
		||||
        poll_expires_in=args.poll_expires_in,
 | 
			
		||||
        poll_multiple=args.poll_multiple,
 | 
			
		||||
        poll_hide_totals=args.poll_hide_totals,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    if "scheduled_at" in response:
 | 
			
		||||
| 
						 | 
				
			
			@ -115,7 +119,7 @@ def post(app, user, args):
 | 
			
		|||
    delete_tmp_status_file()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _get_status_text(text, editor):
 | 
			
		||||
def _get_status_text(text, editor, media):
 | 
			
		||||
    isatty = sys.stdin.isatty()
 | 
			
		||||
 | 
			
		||||
    if not text and not isatty:
 | 
			
		||||
| 
						 | 
				
			
			@ -124,7 +128,7 @@ def _get_status_text(text, editor):
 | 
			
		|||
    if isatty:
 | 
			
		||||
        if editor:
 | 
			
		||||
            text = editor_input(editor, text)
 | 
			
		||||
        elif not text:
 | 
			
		||||
        elif not text and not media:
 | 
			
		||||
            print_out("Write or paste your toot. Press <yellow>{}</yellow> to post it.".format(EOF_KEY))
 | 
			
		||||
            text = multiline_input()
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -105,6 +105,10 @@ DURATION_UNITS = {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
DURATION_EXAMPLES = """e.g. "1 day", "2 hours 30 minutes", "5 minutes 30
 | 
			
		||||
seconds" or any combination of above. Shorthand: "1d", "2h30m", "5m30s\""""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def duration(value: str):
 | 
			
		||||
    match = re.match(r"""^
 | 
			
		||||
        (([0-9]+)\s*(days|day|d))?\s*
 | 
			
		||||
| 
						 | 
				
			
			@ -520,16 +524,35 @@ POST_COMMANDS = [
 | 
			
		|||
            }),
 | 
			
		||||
            (["--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
 | 
			
		||||
                "help": f"""Schedule the toot to be posted after a given amount
 | 
			
		||||
                        of time, {DURATION_EXAMPLES}. Must be at least 5
 | 
			
		||||
                        minutes.""",
 | 
			
		||||
            }),
 | 
			
		||||
            (["-t", "--content-type"], {
 | 
			
		||||
                "type": str,
 | 
			
		||||
                "help": "MIME type for the status text (not supported on all instances)",
 | 
			
		||||
            }),
 | 
			
		||||
            (["--poll-option"], {
 | 
			
		||||
                "action": "append",
 | 
			
		||||
                "type": str,
 | 
			
		||||
                "help": "Possible answer to the poll"
 | 
			
		||||
            }),
 | 
			
		||||
            (["--poll-expires-in"], {
 | 
			
		||||
                "type": duration,
 | 
			
		||||
                "help": f"""Duration that the poll should be open,
 | 
			
		||||
                        {DURATION_EXAMPLES}. Defaults to 24h.""",
 | 
			
		||||
                "default": 24 * 60 * 60,
 | 
			
		||||
            }),
 | 
			
		||||
            (["--poll-multiple"], {
 | 
			
		||||
                "action": "store_true",
 | 
			
		||||
                "default": False,
 | 
			
		||||
                "help": "Allow multiple answers to be selected."
 | 
			
		||||
            }),
 | 
			
		||||
            (["--poll-hide-totals"], {
 | 
			
		||||
                "action": "store_true",
 | 
			
		||||
                "default": False,
 | 
			
		||||
                "help": "Hide vote counts until the poll ends. Defaults to false."
 | 
			
		||||
            }),
 | 
			
		||||
        ],
 | 
			
		||||
        require_auth=True,
 | 
			
		||||
    ),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Ładowanie…
	
		Reference in New Issue