threads

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 10, 2026 License: Apache-2.0 Imports: 21 Imported by: 0

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

View Source
const (
	DefaultDelay   = 1 * time.Second
	DefaultRetries = 4
	DefaultTimeout = 30 * time.Second
)

Default request parameters.

View Source
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.

View Source
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.

View Source
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.

View Source
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

func Code

func Code(err error) int

Code returns the exit code an error maps to: a CodeError's own code, or the generic code for anything else. A nil error is ExitOK.

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

func NewCache(dir string, enabled bool, ttl time.Duration) *Cache

NewCache returns a cache rooted at dir. When enabled is false every operation is a no-op.

func (*Cache) Clear

func (c *Cache) Clear() error

Clear removes the whole cache directory.

func (*Cache) Dir

func (c *Cache) Dir() string

Dir returns the cache root.

func (*Cache) Get

func (c *Cache) Get(key string) ([]byte, bool)

Get returns a cached body if present and fresh.

func (*Cache) Put

func (c *Cache) Put(key string, body []byte)

Put stores a body. Failures are silent: the cache is best-effort.

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client speaks the Threads web surface as a crawler.

func NewClient

func NewClient(cfg Config) (*Client, error)

NewClient builds a Client from cfg, resolving the proxy.

func (*Client) Cache

func (c *Client) Cache() *Cache

Cache exposes the client's blob cache (for the cache command).

func (*Client) GetRaw

func (c *Client) GetRaw(ctx context.Context, rawURL string) ([]byte, error)

GetRaw returns the raw crawler response body for --raw.

func (*Client) Mode

func (c *Client) Mode() string

Mode reports the access mode: "anonymous" or "session".

func (*Client) Post

func (c *Client) Post(ctx context.Context, input string) (*Post, error)

Post fetches a single post in full from its crawler-rendered page.

func (*Client) PostReplies

func (c *Client) PostReplies(ctx context.Context, input string, limit int) iter.Seq2[Reply, error]

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) Profile

func (c *Client) Profile(ctx context.Context, handleOrURL string) (*Profile, error)

Profile fetches a user's profile from the crawler-rendered page.

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.

func (*Client) UserAgent

func (c *Client) UserAgent() string

UserAgent reports the crawler identity requests are sent with.

type CodeError

type CodeError struct {
	Code int
	Msg  string
	Err  error
}

CodeError carries an exit code alongside a message so main can map a library failure to the documented exit-code table.

func Usagef

func Usagef(format string, args ...any) *CodeError

Usagef builds a usage CodeError (exit 2) for command-line misuse such as a wrong argument count or an unknown flag.

func (*CodeError) Error

func (e *CodeError) Error() string

func (*CodeError) Unwrap

func (e *CodeError) Unwrap() error

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.

func OpenStore

func OpenStore(path string) (*Store, error)

OpenStore opens (creating if needed) a SQLite dataset at path and ensures the posts table exists.

func (*Store) Close

func (s *Store) Close() error

Close closes the underlying database.

func (*Store) PutPost

func (s *Store) PutPost(p Post) error

PutPost upserts one post row.

func (*Store) Query

func (s *Store) Query(query string) ([]string, [][]string, error)

Query runs an arbitrary SQL query and returns column names plus rows of string-rendered values.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL