Documentation
¶
Overview ¶
Package threads is the library behind the th command line: the crawler HTTP client, the server-rendered-page parsers, the logged-out GraphQL queries, and the typed data models for Threads (threads.com).
The default transport presents a crawler user agent, which is what makes Threads answer with server-rendered HTML that carries the post text, the engagement counts, the media, and a window of replies. No login, no cookie, and no browser are involved. The optional session mode can add depth on top of that anonymous floor when the caller supplies their own credentials.
Index ¶
- Constants
- func Code(err error) int
- type Cache
- type Client
- func (c *Client) Cache() *Cache
- func (c *Client) GetRaw(ctx context.Context, rawURL string) ([]byte, error)
- func (c *Client) Mode() string
- func (c *Client) Post(ctx context.Context, input string) (*Post, error)
- func (c *Client) PostReplies(ctx context.Context, input string, limit int) iter.Seq2[Reply, error]
- func (c *Client) Profile(ctx context.Context, handleOrURL string) (*Profile, error)
- func (c *Client) ProfilePosts(ctx context.Context, handleOrURL string, limit int) iter.Seq2[Post, error]
- func (c *Client) ProfileReplies(ctx context.Context, handleOrURL string, limit int) iter.Seq2[Post, error]
- func (c *Client) Search(ctx context.Context, query string, limit int) iter.Seq2[SearchResult, error]
- func (c *Client) UserAgent() string
- type CodeError
- type Config
- type Post
- type Profile
- type Reply
- type SearchResult
- type Store
Constants ¶
const ( DefaultDelay = 1 * time.Second DefaultRetries = 4 DefaultTimeout = 30 * time.Second )
Default request parameters.
const ( WebBase = "https://www.threads.com" GraphQLURL = "https://www.threads.com/api/graphql" APIBase = "https://graph.threads.net/v1.0" )
Web and API hosts. The crawler surface lives on threads.com; the official Graph API on graph.threads.net.
const ( DocIDProfileThreads = "33773912952222602" // a profile's threads tab DocIDPostPage = "7448594591874178" // a single post page and its replies DocIDSearch = "24871030029227550" // keyword/user search )
doc_id values for the logged-out persisted queries. Threads rotates these every two to four weeks; when a query starts returning an unexpected shape, refresh these from a logged-out page load and the anonymous pagination path recovers. The SSR path does not depend on them.
const ( ExitOK = 0 ExitGeneric = 1 ExitUsage = 2 ExitNotFound = 3 ExitLoginWall = 4 ExitRateLimit = 5 ExitNetwork = 6 )
Exit codes, mapped to process exit status by cmd/th.
const CrawlerUA = "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
CrawlerUA is the user agent that makes Threads serve server-rendered HTML. Presenting as a crawler is what unlocks anonymous, no-browser access.
Variables ¶
This section is empty.
Functions ¶
Types ¶
type Cache ¶
type Cache struct {
// contains filtered or unexported fields
}
Cache is a simple on-disk blob cache keyed by request URL with an mtime TTL.
func NewCache ¶
NewCache returns a cache rooted at dir. When enabled is false every operation is a no-op.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client speaks the Threads web surface as a crawler.
func (*Client) PostReplies ¶
PostReplies streams the replies to a post. The crawler page carries the post followed by a window of its replies; the logged-out GraphQL query extends that window where the doc_id is current.
func (*Client) ProfilePosts ¶
func (c *Client) ProfilePosts(ctx context.Context, handleOrURL string, limit int) iter.Seq2[Post, error]
ProfilePosts streams a profile's recent posts. It yields the SSR window first, then continues through the logged-out GraphQL query while the cursor advances and limit (0 = unbounded) is unmet.
func (*Client) ProfileReplies ¶
func (c *Client) ProfileReplies(ctx context.Context, handleOrURL string, limit int) iter.Seq2[Post, error]
ProfileReplies streams the posts on a profile that are replies.
func (*Client) Search ¶
func (c *Client) Search(ctx context.Context, query string, limit int) iter.Seq2[SearchResult, error]
Search streams keyword search hits from the public server-rendered search page. Threads changes the logged-out GraphQL search document frequently, so the SSR surface is the primary path and the persisted query remains a fallback for older responses or authenticated configurations.
type CodeError ¶
CodeError carries an exit code alongside a message so main can map a library failure to the documented exit-code table.
type Config ¶
type Config struct {
Delay time.Duration
Retries int
Timeout time.Duration
UserAgent string
Proxy string
Lang string
CacheDir string
NoCache bool
CacheTTL time.Duration
DataDir string
Verbose int
// Optional session depth, off by default.
Session string // logged-in session id cookie
CSRF string // session CSRF token
}
Config is the resolved runtime configuration for a Client.
func DefaultConfig ¶
func DefaultConfig() Config
DefaultConfig returns the built-in defaults with XDG paths filled in and the optional credentials read from the environment.
type Post ¶
type Post struct {
ID string `json:"id"`
Shortcode string `json:"shortcode,omitempty"`
Text string `json:"text,omitempty"`
MediaType string `json:"media_type,omitempty"` // TEXT_POST, IMAGE, VIDEO, CAROUSEL_ALBUM
MediaURLs []string `json:"media_urls,omitempty"`
Permalink string `json:"permalink,omitempty"`
Username string `json:"username,omitempty"`
UserID string `json:"user_id,omitempty"`
Timestamp time.Time `json:"timestamp,omitempty"`
LikeCount int64 `json:"like_count"`
ReplyCount int64 `json:"reply_count"`
RepostCount int64 `json:"repost_count"`
QuoteCount int64 `json:"quote_count"`
ViewCount int64 `json:"view_count,omitempty"`
IsQuotePost bool `json:"is_quote_post,omitempty"`
IsReply bool `json:"is_reply,omitempty"`
QuotedPostID string `json:"quoted_post_id,omitempty"`
ReplyToID string `json:"reply_to_id,omitempty"`
HasMedia bool `json:"has_media,omitempty"`
FetchedAt time.Time `json:"fetched_at"`
// Count availability is intentionally additive. Existing callers can keep
// using the int64 fields, while research storage can write SQL NULL when a
// metric was absent rather than mistaking it for zero.
LikeCountAvailable bool `json:"like_count_available,omitempty"`
ReplyCountAvailable bool `json:"reply_count_available,omitempty"`
RepostCountAvailable bool `json:"repost_count_available,omitempty"`
QuoteCountAvailable bool `json:"quote_count_available,omitempty"`
ViewCountAvailable bool `json:"view_count_available,omitempty"`
}
Post is a single Threads post.
type Profile ¶
type Profile struct {
ID string `json:"id,omitempty"`
Username string `json:"username"`
Name string `json:"name,omitempty"`
Biography string `json:"biography,omitempty"`
ProfilePicURL string `json:"profile_pic_url,omitempty"`
IsVerified bool `json:"is_verified"`
FollowerCount int64 `json:"follower_count,omitempty"`
FollowingCount int64 `json:"following_count,omitempty"`
URL string `json:"url"`
FetchedAt time.Time `json:"fetched_at"`
// Availability flags preserve the difference between an exposed zero and a
// field that the anonymous surface did not expose. They are omitted from
// normal JSON output when false so existing command output stays compatible.
FollowerCountAvailable bool `json:"follower_count_available,omitempty"`
FollowingCountAvailable bool `json:"following_count_available,omitempty"`
VerifiedAvailable bool `json:"verified_available,omitempty"`
}
Profile is a Threads user.
type Reply ¶
type Reply struct {
ID string `json:"id"`
ParentID string `json:"parent_id,omitempty"`
RootID string `json:"root_id,omitempty"`
Shortcode string `json:"shortcode,omitempty"`
Text string `json:"text,omitempty"`
Username string `json:"username,omitempty"`
UserID string `json:"user_id,omitempty"`
Permalink string `json:"permalink,omitempty"`
Timestamp time.Time `json:"timestamp,omitempty"`
LikeCount int64 `json:"like_count"`
ReplyCount int64 `json:"reply_count"`
MediaType string `json:"media_type,omitempty"`
MediaURLs []string `json:"media_urls,omitempty"`
FetchedAt time.Time `json:"fetched_at"`
}
Reply is a reply to a post, carrying its thread linkage.
type SearchResult ¶
type SearchResult struct {
ID string `json:"id"`
Query string `json:"query"`
Shortcode string `json:"shortcode,omitempty"`
Text string `json:"text,omitempty"`
Username string `json:"username,omitempty"`
UserID string `json:"user_id,omitempty"`
Permalink string `json:"permalink,omitempty"`
Timestamp time.Time `json:"timestamp,omitempty"`
MediaType string `json:"media_type,omitempty"`
LikeCount *int64 `json:"like_count,omitempty"`
ReplyCount *int64 `json:"reply_count,omitempty"`
RepostCount *int64 `json:"repost_count,omitempty"`
QuoteCount *int64 `json:"quote_count,omitempty"`
ViewCount *int64 `json:"view_count,omitempty"`
IsReply bool `json:"is_reply,omitempty"`
IsQuotePost bool `json:"is_quote_post,omitempty"`
FetchedAt time.Time `json:"fetched_at,omitempty"`
SearchedAt time.Time `json:"searched_at"`
}
SearchResult is one hit from a keyword search.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store is a SQLite-backed dataset for the db command. It is pure-Go (modernc.org/sqlite), so the binary stays cgo-free.