Features and limits¶
What a network can do is fixed, and stated once by its platform file. What a particular post is allowed to contain changes while the app runs - a Mastodon server's post length, an Instagram account's posts left today - so it is looked up instead. See Networks for what each one actually supports.
Feature¶
Feature
¶
Bases: Flag
Things a network either can or cannot do.
A platform file lists the ones it supports. Combine them with | and
test them with in:
supported = Feature.POST_TEXT | Feature.POST_IMAGE
Feature.POST_VIDEO in supported # False
CREATE_APP
class-attribute
instance-attribute
¶
socialchimp can register your app automatically.
True for Mastodon only, today. Everywhere else you create the app by hand in the network's developer portal first.
NEEDS_NO_APP
class-attribute
instance-attribute
¶
There is no app to register, so nothing has to be saved first.
Bluesky only, today. It has no developer portal at all - a person signs
in with their handle and an app password they made themselves - so there
is no client id and no secret, and nothing for Storage.save_app to
hold. SocialChimp.start_login works against empty storage there, and
the platform is handed LoginRequest.app as None.
Leave it off for a network that does need credentials, including one
that can register itself: Mastodon creates the app for you, but the app
still exists and a sign-in without it fails. A sign-in with none saved is
then refused with a message naming Storage.save_app, which is the
right answer everywhere but here.
DELETE_POST
class-attribute
instance-attribute
¶
Can remove a post it published.
READ_STATS
class-attribute
instance-attribute
¶
Can read numbers such as likes and views.
PUSH_UPDATES
class-attribute
instance-attribute
¶
Tells us when something happens, instead of us having to check.
Where this is missing, socialchimp checks on a timer and gives you the same updates anyway. Your code does not need to know which is happening.
READ_POST
class-attribute
instance-attribute
¶
Can read one post back in full, with Account.read_post.
READ_THREAD
class-attribute
instance-attribute
¶
Can read a post together with its replies, with Account.read_thread.
REPLY_TO_COMMENTS
class-attribute
instance-attribute
¶
Can reply to any post or comment at any depth, with Account.reply.
publish(Post(reply_to=...)) keeps working without this; reply() is
the recommended way once a network has it.
LIKE
class-attribute
instance-attribute
¶
Can like and unlike a post or a comment, with Account.like and
Account.unlike.
READ_LIKES
class-attribute
instance-attribute
¶
Can list who liked a post, with Account.read_likes.
Some networks that can like cannot list who did - a Facebook Page can
like a comment but only ever sees the count, so it lists LIKE without
this.
READ_UPDATES_AFTER
class-attribute
instance-attribute
¶
Can be asked for everything new since a marker, with
Account.fetch_updates_after, and told a marker has been seen with
Account.mark_seen.
Unlike Feature.PUSH_UPDATES, this is a resumable poll rather than
something the network sends us - the marker is what makes it resumable
across a restart.
MESSAGES
class-attribute
instance-attribute
¶
Can read and send direct messages, with Account.read_conversations,
Account.read_messages, Account.send_message and Account.mark_read.
START_CONVERSATIONS
class-attribute
instance-attribute
¶
Can start a new conversation, with Account.start_conversation.
Meta cannot: the customer has to write first. A network that has
Feature.MESSAGES but not this one can still be replied to - it just
cannot open the first message.
Limits¶
Limits
dataclass
¶
Limits(
max_text_length: int | None = None,
max_text_bytes: int | None = None,
text_counted_in: TextCount = CHARACTERS,
max_images: int | None = None,
max_image_bytes: int | None = None,
max_title_length: int | None = None,
max_videos: int | None = None,
max_video_bytes: int | None = None,
posts_left_today: int | None = None,
)
Numbers that a network enforces right now.
Every field may be None, which means "we do not know" - never "zero".
An unknown limit is not checked.
Attributes:
| Name | Type | Description |
|---|---|---|
max_text_length |
int | None
|
Longest post this network or server accepts,
counted the way |
max_text_bytes |
int | None
|
Longest post once written out, for a network that has this limit as well as the first. Bluesky has both: 300 letters and 3,000 bytes, and a post has to be inside both. |
text_counted_in |
TextCount
|
What |
max_images |
int | None
|
Most pictures allowed on one post. |
max_image_bytes |
int | None
|
Largest picture file allowed. |
max_title_length |
int | None
|
Longest title allowed, on the networks that have a title separate from the post itself. YouTube does, and requires one; Pinterest and Reddit have them too. Worth reading if you are showing somebody a character count as they type. |
max_videos |
int | None
|
Most videos allowed on one post. |
max_video_bytes |
int | None
|
Largest video file allowed. |
posts_left_today |
int | None
|
How many more posts are allowed today, where the network tells us. Instagram and Threads both do. |
The two file sizes are here to be shown to your users and to size things
before uploading. Nothing here opens a file to check them, because that
would mean reading every picture off disk to send one post - the network
is what enforces those, and a file that is too big comes back as an
InvalidPostError from the platform.
How text is counted¶
Hardly any network means "characters" when it says "300". TextCount says
which counting a network actually uses, and measure_text counts a string
the same way.
TextCount
¶
Bases: Enum
How a network counts the length of a post.
Nearly every network says "300 characters" and means something else by it, and the difference only shows up once somebody posts an emoji.
A platform names one of these on its Limits, and check_post then
counts the same way that network will. Left out, it is CHARACTERS,
which is what Python's own len gives - so a platform written before
this existed behaves exactly as it did.
CHARACTERS
class-attribute
instance-attribute
¶
One per character, which is what len(text) gives.
Mastodon counts this way, and so does anything that has not said otherwise.
GRAPHEMES
class-attribute
instance-attribute
¶
Letters as a person would count them.
A family emoji is seven characters and one letter; a flag is two and
one. Bluesky's 300 is counted this way, so counting characters instead
refuses posts it would happily have taken. See count_graphemes.
UTF8_BYTES
class-attribute
instance-attribute
¶
Bytes, once the text is written out.
An emoji takes four of them and an accented letter two. Threads counts this way, and Bluesky has a second limit of this kind on top of its first.
UTF16_UNITS
class-attribute
instance-attribute
¶
The way Java and JavaScript count, where an emoji is two.
Networks built on either tend to count this way - TikTok does - so a post of 200 emoji is 200 to Python and 400 to them.
measure_text
¶
measure_text(
text: str, counted_in: TextCount = CHARACTERS
) -> int
Measure some text the way one network counts it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text
|
str
|
The words to measure. |
required |
counted_in
|
TextCount
|
How that network counts. Left out, characters. |
CHARACTERS
|
Returns:
| Type | Description |
|---|---|
int
|
How long that network will think this post is. |
Source code in src/socialchimp/features.py
count_graphemes
¶
Count what a person would call the letters in some text.
Bluesky's limit of 300 is counted this way, not in characters. A family emoji is seven characters and one letter; a flag is two and one; an accented letter can be either. Counting characters instead refuses posts Bluesky would have taken.
This is an approximation, and worth being honest about where it sits. Python has no grapheme splitter of its own and we add no dependencies, so this handles accents and other marks, skin tones, joined emoji and flags - which is what people actually type. It over-counts a few writing systems where a syllable is built from several characters, and the cost of that is refusing a post that would have been fine. If you need it exact, count with a library of your own and check before you post.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text
|
str
|
The words to count. |
required |
Returns:
| Type | Description |
|---|---|
int
|
How many letters a person would say that is. |
Source code in src/socialchimp/features.py
Checking a post before it is sent¶
The two checks every platform runs before spending a request. Both refuse in plain words rather than letting the network answer with a code.
check_post
¶
check_post(
post: Post,
*,
platform: str,
features: Feature,
limits: Limits,
words_alone_advice: str | None = None,
) -> None
Check a post against a network's rules before sending it.
Catching problems here means a clear message instead of a network error code, and one less wasted request against a rate limit.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
post
|
Post
|
The post about to be sent. |
required |
platform
|
str
|
Name of the network, used in messages. |
required |
features
|
Feature
|
What the network can do. |
required |
limits
|
Limits
|
What the network currently allows. |
required |
words_alone_advice
|
str | None
|
One more sentence for the message a network with no text-only post gives back, for anything worth saying that "attach a picture or a video" does not cover. YouTube uses it to say its community posts are not in the API at all, which is the first thing somebody argues back. |
None
|
Raises:
| Type | Description |
|---|---|
NotSupportedError
|
If the post needs something the network cannot do. |
InvalidPostError
|
If the post breaks one of the network's limits. |
Source code in src/socialchimp/features.py
check_option_names
¶
check_option_names(
options: RawData,
*,
platform: str,
allowed: Sequence[str],
advice: str | None = None,
) -> None
Refuse a setting in Post.options that this network never heard of.
Every platform needs this and every platform wrote it, so it is written once here instead. It only checks the names. What each value has to be is different on every network - a web address on Facebook, true or false on Instagram, one of four words on Mastodon - and the message that says so is the useful half, so that part stays in the platform file.
Called before anything is sent, so a typo costs no request and no part of a rate limit.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
options
|
RawData
|
What was put in |
required |
platform
|
str
|
Name of the network, used in the message. |
required |
allowed
|
Sequence[str]
|
The settings this network takes, in the order to list them. |
required |
advice
|
str | None
|
One more sentence, for a network where the mistake has an
obvious cause. Pinterest uses it to say that |
None
|
Raises:
| Type | Description |
|---|---|
InvalidPostError
|
If any setting is not one this network takes. The message names the first one and lists what is accepted. |