Skip to content

Commit 4d2fe6b

Browse files
committed
move cut text to comment section
1 parent 98f0814 commit 4d2fe6b

3 files changed

Lines changed: 188 additions & 30 deletions

File tree

manual_testing/mastodon_manual_test.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from abstractions import AdoptablePet
66
from social_posters.mastodon import PosterMastodon
77

8-
def postExceed500CharsLimitWithAdoptionLink():
8+
def post_exceed_500_chars_limit_with_adoption_link():
99
pet = AdoptablePet("Brian",
1010
"Labrador Retriever",
1111
"White Labrador",
@@ -20,7 +20,7 @@ def postExceed500CharsLimitWithAdoptionLink():
2020
)
2121
return pet
2222

23-
def postExceed500CharsLimitWithoutAdoptionLink():
23+
def post_exceed_500_chars_limit_without_adoption_link():
2424
pet = AdoptablePet("Vinny",
2525
"Unknown",
2626
"Unknown",
@@ -36,7 +36,7 @@ def postExceed500CharsLimitWithoutAdoptionLink():
3636
return pet
3737

3838

39-
def postWithin500CharsLimitWithAdoptionLink():
39+
def post_within_500_chars_limit_with_adoption_link():
4040
pet = AdoptablePet("Ernie",
4141
"Chicken",
4242
"Unknown",
@@ -51,7 +51,7 @@ def postWithin500CharsLimitWithAdoptionLink():
5151
)
5252
return pet
5353

54-
def postWithin500CharsLimitWithoutAdoptionLink():
54+
def post_within_500_chars_limit_without_adoption_link():
5555
pet = AdoptablePet("Pouncy",
5656
"Cat",
5757
"Unknown",
@@ -66,7 +66,7 @@ def postWithin500CharsLimitWithoutAdoptionLink():
6666
)
6767
return pet
6868

69-
def postUnicode():
69+
def post_unicode():
7070
pet = AdoptablePet("Vinny",
7171
"Unknown",
7272
"Unknown",
@@ -82,11 +82,11 @@ def postUnicode():
8282
return pet
8383

8484
testingCases = [
85-
postExceed500CharsLimitWithAdoptionLink,
86-
postExceed500CharsLimitWithoutAdoptionLink,
87-
postWithin500CharsLimitWithAdoptionLink,
88-
postWithin500CharsLimitWithoutAdoptionLink,
89-
postUnicode
85+
post_exceed_500_chars_limit_with_adoption_link,
86+
post_exceed_500_chars_limit_without_adoption_link,
87+
post_within_500_chars_limit_with_adoption_link,
88+
post_within_500_chars_limit_without_adoption_link,
89+
post_unicode
9090
]
9191

9292
def main():

social_posters/mastodon.py

Lines changed: 56 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from abstractions import Post, PostResult, SocialPoster, AdoptablePet
99
from abstractions import CITY_NAME, CITY_STATE
1010

11+
THREAD_SUFFIX = "\n\nMore details below ⬇️"
1112
MASTODON_CHARACTER_LIMIT = 500
1213
TRUNCATION_SUFFIX = "..."
1314

@@ -69,10 +70,22 @@ def publish(self, post: Post) -> PostResult:
6970
image_path,
7071
description=post.alt_text or "Photo of an adoptable pet",
7172
)
73+
74+
main_caption, replies = self._format_caption_thread(post)
75+
7276
status = self._session.status_post(
73-
self._format_caption(post),
77+
main_caption,
7478
media_ids=[media["id"]],
7579
)
80+
81+
root_status_id = status["id"]
82+
83+
for reply_text in replies:
84+
self._session.status_post(
85+
reply_text,
86+
in_reply_to_id=root_status_id
87+
)
88+
7689
return PostResult(
7790
success=True,
7891
post_id=str(status["id"]),
@@ -85,20 +98,42 @@ def publish(self, post: Post) -> PostResult:
8598
if image_path and os.path.exists(image_path):
8699
os.unlink(image_path)
87100

88-
def _format_caption(self, post: Post) -> str:
101+
def _format_caption_thread(self, post: Post) -> tuple[str, list[str]]:
89102
tags = " ".join(f"#{tag}" for tag in post.tags if tag)
90103
tag_suffix = f"\n\n{tags}" if tags else ""
91-
available_text_length = MASTODON_CHARACTER_LIMIT - len(tag_suffix)
92-
93-
if available_text_length <= len(TRUNCATION_SUFFIX):
94-
return (tag_suffix[-MASTODON_CHARACTER_LIMIT:]).strip()
95104

96105
caption_text = post.text.strip()
97-
if len(caption_text) > available_text_length:
98-
caption_text = caption_text[: available_text_length - len(TRUNCATION_SUFFIX)].rstrip()
99-
caption_text = f"{caption_text}{TRUNCATION_SUFFIX}"
100106

101-
return f"{caption_text}{tag_suffix}"
107+
if len(caption_text) + len(tag_suffix) <= MASTODON_CHARACTER_LIMIT:
108+
return f"{caption_text}{tag_suffix}", []
109+
110+
main_limit = (
111+
MASTODON_CHARACTER_LIMIT
112+
- len(tag_suffix)
113+
- len(THREAD_SUFFIX)
114+
- len(TRUNCATION_SUFFIX)
115+
)
116+
117+
main_text, overflow = self._safe_truncate(caption_text, main_limit)
118+
119+
main_caption = f"{main_text}{TRUNCATION_SUFFIX}{THREAD_SUFFIX}{tag_suffix}"
120+
replies = self._split_reply_chunks(overflow)
121+
122+
123+
return main_caption, replies
124+
125+
def _split_reply_chunks(self, text: str) -> list[str]:
126+
chunks = []
127+
remaining = text.strip()
128+
129+
while remaining:
130+
chunk, remaining = self._safe_truncate(
131+
remaining,
132+
MASTODON_CHARACTER_LIMIT
133+
)
134+
chunks.append(chunk)
135+
136+
return chunks
102137

103138
def _download_image(self, image_url: str) -> str:
104139
parsed_url = urlparse(image_url)
@@ -110,9 +145,18 @@ def _download_image(self, image_url: str) -> str:
110145
if chunk:
111146
tmp.write(chunk)
112147
return tmp.name
148+
149+
def _safe_truncate(self, text: str, limit: int) -> tuple[str, str]:
150+
if len(text) <= limit:
151+
return text, ""
152+
153+
cut = text.rfind(" ", 0, limit)
154+
155+
if cut == -1:
156+
cut = limit
157+
158+
return text[:cut].rstrip(), text[cut:].strip()
113159

114-
# rearrange so that link is at top
115-
# need to test
116160
def format_post(self, pet:AdoptablePet) -> Post:
117161
"""
118162
Create a Post from an AdoptablePet.

tests/test_mastodon.py

Lines changed: 122 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,136 @@ class TestMastodonCaption:
66
def setup_method(self):
77
self.poster = PosterMastodon.__new__(PosterMastodon)
88

9+
def reconstruct_text(self, main_caption: str, replies: list[str]) -> str:
10+
main_without_tags = main_caption.split("\n\n#")[0]
11+
main_without_suffix = (
12+
main_without_tags
13+
.replace("...", "")
14+
.replace("\n\nMore details below ⬇️", "")
15+
.strip()
16+
)
17+
18+
return " ".join([main_without_suffix] + replies).strip()
19+
20+
def test_thread_preserves_original_text_content(self):
21+
original_text = " ".join(f"word{i}" for i in range(300))
22+
post = Post(text=original_text, tags=["AdoptDontShop", "Boston"])
23+
24+
main_caption, replies = self.poster._format_caption_thread(post)
25+
26+
reconstructed = self.reconstruct_text(main_caption, replies)
27+
28+
assert reconstructed == original_text
29+
30+
def test_thread_preserves_original_text_without_spaces(self):
31+
original_text = "x" * 1000
32+
post = Post(text=original_text, tags=["AdoptDontShop", "Boston"])
33+
34+
main_caption, replies = self.poster._format_caption_thread(post)
35+
36+
main_without_tags = main_caption.split("\n\n#")[0]
37+
main_without_suffix = (
38+
main_without_tags
39+
.replace("...", "")
40+
.replace("\n\nMore details below ⬇️", "")
41+
.strip()
42+
)
43+
44+
reconstructed = main_without_suffix + "".join(replies)
45+
46+
assert reconstructed == original_text
47+
948
def test_no_tags(self):
1049
post = Post(text="Hello, world!")
11-
assert self.poster._format_caption(post) == "Hello, world!"
50+
main_caption, replies = self.poster._format_caption_thread(post)
51+
assert main_caption == "Hello, world!"
52+
assert replies == []
1253

1354
def test_with_tags(self):
1455
post = Post(text="Meet Poppy!", tags=["AdoptDontShop", "Boston"])
15-
assert self.poster._format_caption(post) == "Meet Poppy!\n\n#AdoptDontShop #Boston"
56+
main_caption, replies = self.poster._format_caption_thread(post)
57+
58+
assert main_caption == "Meet Poppy!\n\n#AdoptDontShop #Boston"
59+
assert replies == []
1660

17-
def test_caption_stays_under_limit(self):
61+
def test_caption_stays_under_limit_and_creates_reply(self):
62+
post = Post(text="x " * 1000, tags=["AdoptDontShop", "Boston"])
63+
main_caption, replies = self.poster._format_caption_thread(post)
64+
65+
assert len(main_caption) <= MASTODON_CHARACTER_LIMIT
66+
assert main_caption.endswith("\n\n#AdoptDontShop #Boston")
67+
assert "..." in main_caption
68+
assert "More details below" in main_caption
69+
assert replies
70+
assert all(len(reply) <= MASTODON_CHARACTER_LIMIT for reply in replies)
71+
72+
def test_caption_stays_under_limit_creates_reply(self):
1873
post = Post(text="x" * 1000, tags=["AdoptDontShop", "Boston"])
19-
caption = self.poster._format_caption(post)
74+
main_caption, replies = self.poster._format_caption_thread(post)
2075

21-
assert len(caption) <= MASTODON_CHARACTER_LIMIT
22-
assert caption.endswith("\n\n#AdoptDontShop #Boston")
23-
assert "..." in caption
76+
assert len(main_caption) <= MASTODON_CHARACTER_LIMIT
77+
assert main_caption.endswith("\n\n#AdoptDontShop #Boston")
78+
assert "..." in main_caption
79+
assert "More details below" in main_caption
80+
assert replies
81+
assert all(len(reply) <= MASTODON_CHARACTER_LIMIT for reply in replies)
2482

2583
def test_empty_tags_are_ignored(self):
2684
post = Post(text="Meet Poppy!", tags=["AdoptDontShop", "", None, "Boston"])
27-
assert self.poster._format_caption(post) == "Meet Poppy!\n\n#AdoptDontShop #Boston"
85+
main_caption, replies = self.poster._format_caption_thread(post)
86+
87+
assert main_caption == "Meet Poppy!\n\n#AdoptDontShop #Boston"
88+
assert replies == []
89+
90+
def test_long_text_without_tags_creates_replies(self):
91+
post = Post(text="hello " * 300)
92+
main_caption, replies = self.poster._format_caption_thread(post)
93+
94+
assert len(main_caption) <= MASTODON_CHARACTER_LIMIT
95+
assert replies
96+
assert all(len(reply) <= MASTODON_CHARACTER_LIMIT for reply in replies)
97+
98+
def test_safe_truncate_does_not_split_words_when_possible(self):
99+
kept, remaining = self.poster._safe_truncate("hello world again", 12)
100+
assert kept == "hello world"
101+
assert remaining == "again"
102+
103+
104+
105+
106+
107+
108+
109+
110+
111+
112+
113+
114+
115+
116+
117+
118+
119+
120+
121+
122+
123+
124+
125+
126+
127+
128+
129+
130+
131+
132+
133+
134+
135+
136+
137+
138+
139+
140+
141+

0 commit comments

Comments
 (0)