Documentation
¶
Overview ¶
Package model holds the domain types shared across the server.
It imports nothing from the rest of the project, so every other package can depend on it without creating a cycle.
Index ¶
Constants ¶
const ( NotifLike = "like" NotifRepost = "repost" NotifFollow = "follow" NotifMention = "mention" NotifReply = "reply" NotifRecommended = "recommended" )
Kinds a notification cell can be recognised as.
Variables ¶
This section is empty.
Functions ¶
Types ¶
type Author ¶
type Author struct {
ID string `json:"id,omitempty"`
Name string `json:"name"`
Handle string `json:"handle"`
}
Author is the account that published a post.
type Contributor ¶
type Contributor struct {
Handle string `json:"handle"`
Name string `json:"name"`
Posts int `json:"posts"`
}
Contributor counts how many posts one account contributed to a result set.
func Contributors ¶
func Contributors(posts []Post) []Contributor
Contributors ranks the accounts in posts by how many they contributed, with ties broken by first appearance so the result is stable.
type Metrics ¶
type Metrics struct {
Replies int `json:"replies"`
Reposts int `json:"reposts"`
Likes int `json:"likes"`
Views int `json:"views,omitempty"`
}
Metrics are the engagement counters displayed with a post. They are best effort: X abbreviates large numbers in the DOM, so values may be rounded.
type Notification ¶ added in v0.0.9
type Notification struct {
// Kind is best effort, and empty when it could not be told.
//
// X marks the difference between a like and a follow only with an icon that
// carries no identifier, and with English words in the text, so this is read
// from those words. It will be empty under a different interface language, and
// it is empty for about a fifth of cells on a real account even in English:
// X renders some as nothing but a display name and a time, with the post
// underneath and no verb anywhere. For those, neither this nor Text says what
// happened -- PostText is the only content, and a reader has to treat the cell
// as "something involving this account and this post".
Kind string `json:"kind,omitempty"`
// Actors are the accounts the cell names. More than one is normal.
Actors []Author `json:"actors,omitempty"`
// Text is the line X wrote, such as "Ramyar Khalili and Somnia Lab liked
// your post", including the relative time it renders after it. It is the only
// field always present, though see Kind: for some cells it is only a name.
Text string `json:"text"`
// PostText is the post the notification concerns, when the cell shows one. A
// follow shows none. There is no id or URL to give: the cell links to the
// accounts involved and never to the post.
PostText string `json:"post_text,omitempty"`
CreatedAt time.Time `json:"created_at,omitempty"`
}
Notification is one cell of X's notifications timeline.
It is not a Post and does not reduce to one. X aggregates: a single cell can say two accounts liked one post, or that one account liked two of yours, and it renders no link to the post in either case. So a notification carries what the cell actually said -- who, when, and the words -- rather than a shape invented to look like a post.
func DedupeNotifications ¶ added in v0.0.9
func DedupeNotifications(items []Notification, limit int) []Notification
DedupeNotifications drops repeats and caps the result.
Scrolling re-reads cells that were already collected, and a notification has no id to key on, so identity has to be built from what the cell said.
It is built from everything the cell said, not just the words and the second they arrived. Two likes from the same account on different posts within the same second render the same sentence, and a cell with no timestamp keys on the zero time -- so a coarser fingerprint drops real notifications and reports a shorter list as though the page held no more.
func (Notification) Usable ¶ added in v0.0.9
func (n Notification) Usable() bool
Usable reports whether a notification says anything at all.
Text is the test rather than Kind or Actors, because Text is what X wrote and the other two are read out of it. A cell with no words is a cell that failed to render.
type Post ¶
type Post struct {
ID string `json:"id"`
// Title is set for X Articles, the long-form posts that carry a headline and
// a body instead of the usual short text.
Title string `json:"title,omitempty"`
Text string `json:"text"`
URL string `json:"url"`
CreatedAt time.Time `json:"created_at,omitempty"`
Author Author `json:"author"`
Metrics Metrics `json:"metrics"`
// Media holds the images attached to the post. Plenty of posts carry no
// text at all -- image-only replies are how threads of visual work are
// usually published -- so this is often the entire content.
Media []Media `json:"media,omitempty"`
}
Post is a single X post, normalized to the same shape regardless of whether it came from an API payload or was scraped out of the rendered page.
func Dedupe ¶
Dedupe returns the usable posts in input order with duplicate IDs removed, capped at limit. A limit of zero or less means no cap.
Timelines repeat posts across scroll rounds and merged sources, so callers accumulate through this rather than tracking seen IDs themselves.
func (Post) Usable ¶
Usable reports whether a post carries enough to be worth returning.
Partially rendered timeline entries are common and are dropped rather than surfaced as blanks. Text is not required: an image-only post is complete, and requiring text silently discarded whole self-threads of visual work.