Documentation
¶
Overview ¶
Package twitter is an unofficial client for the X (Twitter) web API.
A Client holds a pool of accounts and exposes typed methods for timelines, search, profiles, posting and direct messages. Reads pick an account from the pool and rotate on failure; writes run on the account you pass. The client follows the rate limits X reports per account and endpoint, and failures surface as *APIError values with predicates such as IsRateLimited.
The response types (Tweet, TwitterUser, DMConversation, DMMessage) are defined in package model and aliased here, so either import path works.
Accounts authenticate with the auth_token and ct0 cookies of a logged-in x.com session. Treat both as passwords.
See the docs directory of the repository for the guides on configuration, errors, rate limits and metrics.
Index ¶
- Constants
- Variables
- func ApplyEnvOverrides()
- func AssignBrowserProfile(acc *Account, _ int)
- func EndpointLimits() map[string]int
- func EndpointURL(operation string) (string, error)
- func FlushSocialReports(ctx context.Context) error
- func GenerateCT0() string
- func GetTweetConversationWithSocial(ctx context.Context, sc *social.Client, tweetID string, opts ...ClientOption) (*Tweet, []*Tweet, error)
- func IsAuthExpired(err error) bool
- func IsAutomated(err error) bool
- func IsBanned(err error) bool
- func IsBlocked(err error) bool
- func IsCSRF(err error) bool
- func IsEndpointFault(err error) bool
- func IsForbidden(err error) bool
- func IsInternal(err error) bool
- func IsLocked(err error) bool
- func IsNotAuthorized(err error) bool
- func IsRateLimited(err error) bool
- func IsSuspended(err error) bool
- func SetSocialMeterProvider(mp metric.MeterProvider)
- func SnowflakeTime(id string) (time.Time, error)
- func WireFor(p stealth.TLSProfile) string
- func WithRequestTrace(ctx context.Context, t *RequestTrace) context.Context
- type APIError
- type Account
- func (a *Account) AllowRequest(endpoint string) bool
- func (a *Account) BudgetPct() int
- func (a *Account) Credentials() (authToken, ct0, userAgent string)
- func (a *Account) EndpointAvailableAt(endpoint string) time.Time
- func (a *Account) ID() string
- func (a *Account) InFlight() int
- func (a *Account) IsActive() bool
- func (a *Account) IsEndpointRateLimited(endpoint string) bool
- func (a *Account) MarkEndpointRateLimited(endpoint string, until time.Time)
- func (a *Account) PickPace() stealth.Jitter
- func (a *Account) ReactivateAt() time.Time
- func (a *Account) RecordSuccess()
- func (a *Account) SetActive(v bool)
- func (a *Account) SetBudgetPct(pct int)
- func (a *Account) SetCT0(ct0 string)
- func (a *Account) SetCredentials(authToken, ct0 string)
- func (a *Account) SetPickPace(j stealth.Jitter)
- func (a *Account) SetReactivateAt(t time.Time)
- func (a *Account) SyncRateLimit(endpoint string, respHdrs map[string]string)
- func (a *Account) UpdateEndpointLimit(endpoint string, limit int)
- func (a *Account) Wire() string
- type AccountHealth
- type AutoReloginGate
- type AutoReloginGateFunc
- type CallTrace
- type Client
- func (c *Client) AccountByUsername(username string) *Account
- func (c *Client) CheckSession(ctx context.Context, acc *Account) (*SessionInfo, error)
- func (c *Client) Close() error
- func (c *Client) CreateTweet(ctx context.Context, acc *Account, text string) (string, error)
- func (c *Client) CreateTweetWithMedia(ctx context.Context, acc *Account, text string, mediaIDs []string) (string, error)
- func (c *Client) FollowUser(ctx context.Context, acc *Account, userID string) error
- func (c *Client) FriendshipState(ctx context.Context, acc *Account, userID string) (following, notificationsEnabled bool, err error)
- func (c *Client) GetBookmarks(ctx context.Context, maxCount int) ([]*Tweet, error)
- func (c *Client) GetCommunityTweets(ctx context.Context, communityID string, maxCount int) ([]*Tweet, error)
- func (c *Client) GetDMInbox(ctx context.Context) ([]*DMConversation, error)
- func (c *Client) GetDeviceFollowTweets(ctx context.Context, count int) ([]*Tweet, error)
- func (c *Client) GetDeviceNotifications(ctx context.Context, count int) ([]*Tweet, error)
- func (c *Client) GetFollowers(ctx context.Context, userID string, maxCount int) ([]*TwitterUser, error)
- func (c *Client) GetFollowing(ctx context.Context, userID string, maxCount int) ([]*TwitterUser, error)
- func (c *Client) GetHomeLatestTimeline(ctx context.Context, count int) ([]*Tweet, error)
- func (c *Client) GetHomeTimeline(ctx context.Context, count int) ([]*Tweet, error)
- func (c *Client) GetListTweets(ctx context.Context, listID string, maxCount int) ([]*Tweet, error)
- func (c *Client) GetRetweeters(ctx context.Context, tweetID string, maxCount int) ([]*TwitterUser, error)
- func (c *Client) GetTweetByID(ctx context.Context, tweetID string) (*Tweet, error)
- func (c *Client) GetTweetConversation(ctx context.Context, tweetID string) (*Tweet, []*Tweet, error)
- func (c *Client) GetTweetNotifications(ctx context.Context, count int) ([]*Tweet, error)
- func (c *Client) GetUserByScreenName(ctx context.Context, handle string) (*TwitterUser, error)
- func (c *Client) GetUserTweets(ctx context.Context, userID string, count int) ([]*Tweet, error)
- func (c *Client) GetVerifiedFollowers(ctx context.Context, userID string, maxCount int) ([]*TwitterUser, error)
- func (c *Client) HealthReport() []AccountHealth
- func (c *Client) HoldAccount(username string, until time.Time) bool
- func (c *Client) LikeTweet(ctx context.Context, acc *Account, tweetID string) error
- func (c *Client) Pool() *pool.Pool[*Account]
- func (c *Client) PostWithAccount(ctx context.Context, username, text string) (string, error)
- func (c *Client) PostWithMedia(ctx context.Context, username, text string, paths ...string) (string, error)
- func (c *Client) ReleaseAccount(username string) bool
- func (c *Client) ReplyTweet(ctx context.Context, acc *Account, tweetID, text string) (string, error)
- func (c *Client) Retweet(ctx context.Context, acc *Account, tweetID string) (string, error)
- func (c *Client) SearchTimeline(ctx context.Context, query string, count int) ([]*Tweet, error)
- func (c *Client) SearchTweets(ctx context.Context, query string, product SearchProduct, count int) ([]*Tweet, error)
- func (c *Client) SearchUsers(ctx context.Context, query string, count int) ([]*TwitterUser, error)
- func (c *Client) SendDM(ctx context.Context, acc *Account, conversationID, text string) (string, error)
- func (c *Client) SetAutoReloginGate(g AutoReloginGate)
- func (c *Client) SetDeviceNotifications(ctx context.Context, acc *Account, userID string, on bool) (bool, error)
- func (c *Client) UnlikeTweet(ctx context.Context, acc *Account, tweetID string) error
- func (c *Client) Unretweet(ctx context.Context, acc *Account, tweetID string) (string, error)
- func (c *Client) UploadMedia(ctx context.Context, acc *Account, data []byte, mediaType string) (string, error)
- func (c *Client) UploadMediaFile(ctx context.Context, acc *Account, path string) (string, error)
- func (c *Client) ValidateAccount(ctx context.Context, acc *Account) error
- type ClientConfig
- type ClientOption
- type Cursor
- type DMConversation
- type DMMessage
- type Endpoint
- type FileSessionStore
- type RequestEvent
- type RequestRateLimit
- type RequestTrace
- type SearchProduct
- type Session
- type SessionInfo
- type SessionStore
- type Tweet
- func GetTweetByIDWithSocial(ctx context.Context, sc *social.Client, tweetID string, opts ...ClientOption) (*Tweet, error)
- func GetUserTweetsByIDWithSocial(ctx context.Context, sc *social.Client, userID string, limit int, ...) ([]*Tweet, error)
- func GetUserTweetsWithSocial(ctx context.Context, sc *social.Client, handle string, limit int, ...) ([]*Tweet, error)
- func SearchTweetsWithSocial(ctx context.Context, sc *social.Client, query string, product SearchProduct, ...) ([]*Tweet, error)
- func SearchWithSocial(ctx context.Context, sc *social.Client, query string, limit int, ...) ([]*Tweet, error)
- type TwitterUser
- func GetFollowersWithSocial(ctx context.Context, sc *social.Client, userID string, limit int, ...) ([]*TwitterUser, error)
- func GetFollowingWithSocial(ctx context.Context, sc *social.Client, userID string, limit int, ...) ([]*TwitterUser, error)
- func GetRetweetersWithSocial(ctx context.Context, sc *social.Client, tweetID string, limit int, ...) ([]*TwitterUser, error)
- func GetUserWithSocial(ctx context.Context, sc *social.Client, handle string, opts ...ClientOption) (*TwitterUser, error)
- func SearchUsersWithSocial(ctx context.Context, sc *social.Client, query string, limit int, ...) ([]*TwitterUser, error)
Examples ¶
Constants ¶
const ( WireChrome = "chrome" WireLegacy = "legacy" )
Wire names of the generations, as WireFor and Account.Wire report them.
const RateLimitHeadroom = 2
RateLimitHeadroom is the number of requests X must still report as remaining for an endpoint to stay eligible. An endpoint whose window shows remaining <= RateLimitHeadroom is held back until the window resets. It is exported so consumers do not have to duplicate the constant to compute an account's effective budget (see Account.SetBudgetPct).
Why 2 and not 0: a response's remaining counts requests X had served when it answered, not requests already in flight. With the 50-per-window caps X gives UserTweets/SearchTimeline, two spare slots absorb the handful of concurrent calls that share an account (4% of the window) so those calls do not land on a 429, which is what draws X's attention to an account. Why not nitter's 10: on a 50-request window that would waste a fifth of the budget.
Variables ¶
var BearerToken = bearerTokens[0]
BearerToken is the active bearer token (first in list).
var Endpoints = endpointsFromTable(operations.Table())
Endpoints maps operation names to their current GraphQL IDs and feature flags. The committed table and the generated queryIDs live in internal/operations.
var ErrBadRequest = errors.New("twitter: bad request (input rejected)")
ErrBadRequest marks deterministic CALLER-side input errors: a 400 whose error entries carry X's input-validation code (214 — a bad variable, bad cursor, malformed request field). Rotating to another account reproduces the identical response, so the class is never retried, never charged to the serving account, and never counted as an endpoint fault. Match with errors.Is.
var ErrEndpoint = errors.New("twitter: endpoint fault")
ErrEndpoint marks endpoint-wide faults (HTTP 404 or 410, or 400 carrying a GraphQL-shaped errors body that is NOT a recognized caller-input rejection): the committed request shape is wrong for every account (stale queryId, missing xtid, feature mismatch), so the serving account must not be charged — reads rotate to the next account. Match with errors.Is.
var ErrLeaseIdentity = errors.New("twitter: leased account's browser identity is unusable")
ErrLeaseIdentity marks a lease whose stated browser identity cannot be presented as stated: it is incomplete, or names a TLS profile this build does not know. The lease is skipped rather than served under a different browser.
var ErrNotFound = model.ErrNotFound
ErrNotFound marks deterministic "the resource does not exist" outcomes — deleted tweets, suspended/unavailable handles. Pool callers treat it as a healthy-account response (no retry, no auth_error report): the credential worked; the thing asked for simply isn't there.
var ErrOverloaded = errors.New("twitter: x overloaded")
ErrOverloaded marks X-side overload (HTTP 5xx, or a LoadShed error entry — code -1 / message containing "LoadShed"): retryable on another account with backoff, never charged to the serving account. Match with errors.Is.
ErrProxyClientUnavailable is returned when an account is pinned to its own proxy (Account.Proxy != "") but its per-account transport could not be built. The shared client may be direct, so falling back to it would send the account's cookies from this host's IP, ignoring the proxy the caller asked for: the request fails closed instead.
var ErrSessionNotFound = errors.New("twitter: session not found")
ErrSessionNotFound is returned by SessionStore.Load when no session is stored for the username (or a store-side policy such as a TTL has expired it).
var ErrUnexpectedResponse = model.ErrUnexpectedResponse
ErrUnexpectedResponse marks a 2xx response the library cannot read as the requested data. Either X answered with only errors the library does not recognise: the query ran — the errors[] envelope proves the committed request shape executed — but no error entry matches a known class or a deterministic-miss marker. The class is terminal and account/endpoint-neutral: no retry, no rotation, no guest fallback, no relogin, no account charge, and NOT an endpoint fault. Or the data arrived without the timeline object a timeline read requires (for example {"data":{"user":{}}}): an empty list there would read as "no new tweets", so the parser returns this error instead. Match with errors.Is.
Functions ¶
func ApplyEnvOverrides ¶
func ApplyEnvOverrides()
ApplyEnvOverrides reads TWITTER_QID_* env vars and overrides queryIds in Endpoints. Called automatically by init(); can also be called manually in tests.
func AssignBrowserProfile ¶
AssignBrowserProfile gives acc the browser profile derived from its username, and the profile's User-Agent. The profile is a function of the account, so it is the same on every run and whatever the account's position in a list.
The index parameter is ignored and kept only for source compatibility: an index-based pick changed an account's fingerprint whenever the pool was reordered or resized.
func EndpointLimits ¶ added in v0.7.2
EndpointLimits returns the measured per-account-per-15-min request caps the client seeds every account's per-endpoint limiter with (see endpointDefaultLimit). It returns a COPY: mutating the result does not change the table or what new accounts are seeded with. The map holds only measured ceilings; an endpoint absent from it falls back to the account's configured limiter cap (ClientConfig.RateLimit.RequestsPerWindow), not to a fixed constant.
func EndpointURL ¶
EndpointURL returns the URL for a named operation, or an error if unknown.
func FlushSocialReports ¶ added in v0.8.4
FlushSocialReports waits until the usage reports that *WithSocial calls have queued in the background are delivered, or ctx is done. The helpers return as soon as their result is ready and send the report afterwards; call this at shutdown (with a short deadline) so a final report is not lost with the process. Reports submitted while it waits may or may not be waited for; it returns once nothing is in flight. It returns ctx.Err() when reports are still in flight at the deadline.
func GenerateCT0 ¶
func GenerateCT0() string
GenerateCT0 generates a random 32-byte hex string for use as a ct0 CSRF token.
func GetTweetConversationWithSocial ¶ added in v0.6.11
func GetTweetConversationWithSocial(ctx context.Context, sc *social.Client, tweetID string, opts ...ClientOption) (*Tweet, []*Tweet, error)
GetTweetConversationWithSocial fetches the focal tweet and its conversation entries (replies/ancestors in page order).
func IsAuthExpired ¶ added in v0.6.9
IsAuthExpired reports an expired-authentication condition (code 32).
func IsAutomated ¶ added in v0.8.11
IsAutomated reports X's "this request looks like it might be automated" verdict (code 226). The serving account has been benched for a cooldown; callers should treat it as an account-scoped warning, not retry it.
func IsBanned ¶ added in v0.6.9
IsBanned reports an account-banned condition (code 88 while the rate-limit window still has requests remaining).
func IsEndpointFault ¶ added in v0.6.19
IsEndpointFault reports an endpoint-wide fault — the failure belongs to the committed endpoint shape (404, GraphQL-shaped 400) or to X itself (5xx, LoadShed), never to the serving account. Covers ErrEndpoint and ErrOverloaded; callers must not charge the account or report it as an auth_error.
func IsForbidden ¶ added in v0.6.9
IsForbidden reports a 403 response without a recognized JSON code.
func IsInternal ¶ added in v0.6.9
IsInternal reports a Twitter internal error (code 131).
func IsNotAuthorized ¶ added in v0.6.9
IsNotAuthorized reports a not-authorized condition (codes 37 / 179 / 219).
func IsRateLimited ¶ added in v0.6.9
IsRateLimited reports a rate-limit condition (HTTP 429, or code 88 with an exhausted window).
func IsSuspended ¶ added in v0.6.9
IsSuspended reports an account-suspended condition (code 64).
func SetSocialMeterProvider ¶ added in v0.6.15
func SetSocialMeterProvider(mp metric.MeterProvider)
SetSocialMeterProvider sets the OpenTelemetry MeterProvider that the *WithSocial helpers record to (request.duration, timeline.freshness, relogins, proxy.failures; see docs/metrics.md). Call it once at start-up. nil (the default) falls back to otel.GetMeterProvider(). The per-call clients behind these helpers never register the pool.accounts gauge.
func SnowflakeTime ¶ added in v0.6.13
SnowflakeTime returns the creation time encoded in a Twitter/X snowflake ID (a tweet ID): (id >> 22) + 1288834974657 ms. Consumers use it to compute their own publication-to-first-seen lag.
func WireFor ¶ added in v0.8.9
func WireFor(p stealth.TLSProfile) string
WireFor reports which request shape an account on TLS profile p sends: WireChrome for the current desktop Chrome profiles, WireLegacy (the frozen v0.8.6 shape) for every other profile. It is the rule the client itself applies, so a consumer labelling identities calls this instead of keeping its own copy of the profile set.
func WithRequestTrace ¶ added in v0.7.0
func WithRequestTrace(ctx context.Context, t *RequestTrace) context.Context
WithRequestTrace returns a context that makes client calls record their phase timeline into t.
Types ¶
type APIError ¶ added in v0.6.9
APIError represents a Twitter/X API error with classification.
func (*APIError) Is ¶ added in v0.6.19
Is reports sentinel membership: ErrEndpoint matches class errEndpoint, ErrOverloaded matches errOverloaded, ErrBadRequest matches errBadRequest, ErrNotFound matches errNotFound and ErrUnexpectedResponse matches errUnexpected, so errors.Is(err, ErrEndpoint) reaches through fmt.Errorf("...: %w") wrappers.
type Account ¶
type Account struct {
Username string
Password string
AuthToken string
CT0 string
TOTPSecret string
Proxy string
UserAgent string
Profile stealth.BrowserProfile
pool.HealthTracker
// contains filtered or unexported fields
}
Account represents a Twitter account with credentials for the pool.
func ParseAccounts ¶
ParseAccounts parses a comma-separated list of accounts. Format: "user1:pass1,user2:pass2" or "user1:pass1:auth_token:ct0,..." or "user1:pass1:auth_token:ct0:totp_secret,...".
func (*Account) AllowRequest ¶
AllowRequest checks if this account can make a request to the given endpoint. While the endpoint's header window is live (see endpointGate) the account is allowed unless explicitly blocked: the headers are the authority and the local counter is skipped. Otherwise (cold start, headers absent, or the window expired) the local counter decides.
Under header authority each allowed pick spends one slot of the window's remaining estimate, so N concurrent callers cannot all pass on the strength of one stale response: when the estimate reaches the headroom the endpoint is held until the reset. The next response folds in by the recordWindow merge rule (minimum within a window), so the estimate never rises inside a window. A pick that never produces a response (transport error) keeps its slot spent until the window resets: conservative, bounded by the window.
func (*Account) BudgetPct ¶ added in v0.7.2
BudgetPct returns the percentage of each endpoint's window this account may spend: the SetBudgetPct value, or 100 when never set or set out of range.
func (*Account) Credentials ¶
Credentials returns a snapshot of (authToken, ct0, userAgent) under lock.
func (*Account) EndpointAvailableAt ¶
EndpointAvailableAt returns when this account will be available for the given endpoint.
func (*Account) InFlight ¶ added in v0.6.24
InFlight reports the account's current in-flight request count. The twitter.client.pool.inflight gauge sums it across the pool; tests assert on it.
func (*Account) IsActive ¶
IsActive implements pool.Identity.
Guarded by a.mu: the pool flips these fields (SetActive/SetReactivateAt) under its own lock while readers such as the metrics gauge callback run unlocked relative to the pool, so the Account must synchronise them itself.
func (*Account) IsEndpointRateLimited ¶
IsEndpointRateLimited returns true if the endpoint is currently blocked — by an explicit block, an exhausted window, a spent local budget, or an account hold.
func (*Account) MarkEndpointRateLimited ¶
MarkEndpointRateLimited marks an endpoint as rate-limited for this account.
func (*Account) PickPace ¶ added in v0.8.0
PickPace returns the SetPickPace range; the zero Jitter means off.
func (*Account) ReactivateAt ¶
ReactivateAt implements pool.Identity.
func (*Account) RecordSuccess ¶ added in v0.6.18
func (a *Account) RecordSuccess()
RecordSuccess records a successful request and clears the consecutive header-less-88 counter: a successful response proves the credentials work, so a later run of header-less 88s starts from zero. It shadows the embedded HealthTracker method on purpose — every success path (200, post-relogin, CSRF recovery, accepted 131) funnels through it, so the reset cannot be missed at a new call site.
func (*Account) SetBudgetPct ¶ added in v0.7.2
SetBudgetPct sets the percentage of each endpoint's per-account X window this account may spend (1-100). Every out-of-range value — zero, negative, above 100, including the never-set zero value — means 100: the FULL window (today's behaviour). A share of zero cannot be expressed here on purpose; benching an account entirely is Client.HoldAccount's job. It is safe to call at runtime on a live pooled account: endpointGate re-derives each endpoint's gate under the account lock on every eligibility check — and never persists the budget band — so the next pick sees the new share, including the release of a budget-only hold (X's own exhaustion still stands until the window resets).
func (*Account) SetCredentials ¶
SetCredentials atomically updates auth_token and ct0.
func (*Account) SetPickPace ¶ added in v0.8.0
SetPickPace sets the pace of pool picks for this account: after each pick the pool skips the account for a random gap drawn uniformly from [Min, Max) (stealth.Jitter, the shared range type), so an account in warm-up is picked at most once per gap. A call that has no other account to go to waits (ctx-bounded) for exactly the end of the drawn gap.
The zero Jitter means off, which is the default. A degenerate range (Max <= Min, e.g. {3s, 3s}) is a FIXED gap of Min, unlike Jitter.Duration, which returns 0 for it. A negative Min counts as 0; Min and Max both <= 0 is off. It is safe to call at runtime on a live pooled account: the gap of the current interval is drawn again from the new range (so a lower range releases the account sooner, and the zero value releases it at once), and every later pick draws from it. Pool picks only: account-pinned writes and media uploads never consult it (like Client.HoldAccount).
func (*Account) SetReactivateAt ¶
SetReactivateAt implements pool.Identity.
func (*Account) SyncRateLimit ¶ added in v0.6.6
SyncRateLimit reads the x-rate-limit-limit header from a response and updates this account's per-endpoint cap when the header carries a valid positive value. A no-op when the header is absent or malformed (the prior cap stands). Called on every observed response (200 and 429) so the cap tracks X's real, possibly-shifting per-account budget.
func (*Account) UpdateEndpointLimit ¶ added in v0.6.6
UpdateEndpointLimit sets this account's per-window request cap for the given endpoint, overriding the static default. Driven by the x-rate-limit-limit response header so the limiter self-tunes to X's real per-account-per-endpoint budget. Non-positive limits are ignored (the prior cap stands). The raw cap is what is recorded; the limiter enforces its budget-scaled value.
type AccountHealth ¶
type AccountHealth struct {
Username string
Active bool
Total int
Failed int
ConsecFails int
// ProxyConsecFails is the count of consecutive proxy-layer failures for this
// account. Non-zero while a dead proxy is being backed off; reset to 0 on
// the next successful HTTP response. Distinguishes a dead proxy from a dead
// account — the go-twitter #43 incident mis-attributed 5881 proxy failures
// to the account layer because this distinction was not observable.
ProxyConsecFails int
// ProxyBackoffUntil is when the account's proxy becomes eligible again after
// exponential backoff. Zero when no proxy backoff is in effect.
ProxyBackoffUntil time.Time
}
AccountHealth describes the health state of a single pool account.
type AutoReloginGate ¶
type AutoReloginGate interface {
Allowed(ctx context.Context, username string) (allowed bool, reason string)
}
AutoReloginGate lets an external controller veto automatic re-login flows. A nil gate is treated as "always allow" — preserves backward compatibility for consumers that don't use go-social.
type AutoReloginGateFunc ¶ added in v0.6.7
AutoReloginGateFunc adapts a plain function to the AutoReloginGate interface.
OPT-OUT: as of v0.6.7 a client that never calls SetAutoReloginGate gets the internal per-account relogin breaker by default (see wireDefaultReloginGate), which stops auto-relogin for an account after reloginBreakerThreshold consecutive failures for reloginBreakerWindow. A consumer that wants the pre-v0.6.7 "always relogin, unlimited" behavior must install an always-allow gate explicitly:
c.SetAutoReloginGate(AutoReloginGateFunc(
func(context.Context, string) (bool, string) { return true, "" }))
type CallTrace ¶ added in v0.7.0
type CallTrace struct {
Endpoint string
Entry time.Time // doPoolRequest entered
// JitterDone is after the opt-in pre-request jitter (WithRequestJitter);
// it equals Entry's instant when no jitter is configured.
JitterDone time.Time
// Picked is after the pool handed out an account (includes any pool wait).
Picked time.Time
// Paced is after the per-account human-pace wait (accountPacer).
Paced time.Time
// HeadersStart/Ready bracket x-client-transaction-id + x-xp-forwarded-for
// generation; Ready is the instant before the transport is called.
HeadersStart time.Time
Ready time.Time
// Done is when the transport returned the full response body.
Done time.Time
Attempts int
Status int
Bytes int
Proxied bool // the serving account has its own proxy
// contains filtered or unexported fields
}
CallTrace is the timeline of one logical request (one doGET/doPOST, including its internal retries). Phases are wall-clock instants; a zero value means the phase was not reached. Entry and JitterDone are first-write; the rest describe the LAST HTTP attempt, so a retried request still has a coherent final-attempt timeline (Attempts says how many there were).
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client is the top-level Twitter scraping client.
func NewClient ¶
func NewClient(cfg ClientConfig, clientOpts ...ClientOption) (*Client, error)
NewClient creates a fully-wired Twitter client.
Optional ClientOptions (WithRequestJitter, WithoutRequestJitter, WithAccountPace) adjust the config after it is passed in. Without WithRequestJitter pooled requests are sent without a pre-request delay.
Example ¶
This example has no Output comment, so it is compiled but not run: it needs a real session.
package main
import (
"context"
"fmt"
"log"
"os"
twitter "github.com/anatolykoptev/go-twitter"
)
func main() {
client, err := twitter.NewClient(twitter.ClientConfig{
Accounts: []*twitter.Account{{
Username: "your_handle",
AuthToken: os.Getenv("X_AUTH_TOKEN"),
CT0: os.Getenv("X_CT0"),
}},
})
if err != nil {
log.Fatal(err)
}
defer client.Close()
tweets, err := client.SearchTweets(context.Background(), "golang", twitter.SearchLatest, 20)
if err != nil {
log.Fatal(err)
}
for _, t := range tweets {
fmt.Printf("@%s: %s\n", t.AuthorHandle, t.Text)
}
}
Output:
func (*Client) AccountByUsername ¶
AccountByUsername returns the pool account matching the given username (case-insensitive). Returns nil if not found.
func (*Client) CheckSession ¶ added in v0.8.11
CheckSession verifies acc's own session the way the web app does on load: it asks X for the signed-in viewer's settings with the account's own identity, cookie jar and proxy. A fixed public profile lookup proves only that a token is accepted; this proves THIS account's session is alive, and it is the request a real browser makes on load.
The result is classified by the same taxonomy as every other call: 32 IsAuthExpired, 64 IsSuspended, 326 IsLocked, 226 IsAutomated, 353 IsCSRF, a 429 IsRateLimited. CheckSession reports the verdict and leaves the pool alone — it never relogins and never benches — so the caller decides.
The call spends from the account's per-endpoint window like any other and is refused with IsRateLimited while that window is exhausted or held.
func (*Client) Close ¶ added in v0.6.13
Close closes the idle connections of every HTTP client this Client built (the shared one, each account's, and the open-account login client) and unregisters the pool.accounts gauge callback so the client can be garbage collected and a replacement client can report on the same MeterProvider. It is idempotent and safe on a nil client.
It does not stop in-flight requests or background xtid / queryId refreshes, and it does not close the HTTP clients: the Client stays usable, a connection opened after Close idles out on its own, and the process-wide xtid manager, pacer and fetch client of the *WithSocial path are left alone.
func (*Client) CreateTweet ¶
CreateTweet posts a tweet from a specific account. Returns the tweet ID on success.
func (*Client) CreateTweetWithMedia ¶ added in v0.6.0
func (c *Client) CreateTweetWithMedia(ctx context.Context, acc *Account, text string, mediaIDs []string) (string, error)
CreateTweetWithMedia posts a tweet with attached media IDs from a specific account. With no media IDs it is equivalent to CreateTweet. Returns the tweet ID on success.
func (*Client) FollowUser ¶ added in v0.7.0
FollowUser follows userID from acc through the REST 1.1 friendships/create endpoint. It is a write: one attempt, pinned to acc, no retry and no relogin, so a failure surfaces to the caller instead of repeating.
func (*Client) FriendshipState ¶ added in v0.7.0
func (c *Client) FriendshipState(ctx context.Context, acc *Account, userID string) (following, notificationsEnabled bool, err error)
FriendshipState reports acc's relationship to userID — whether acc follows it and whether device notifications are on — read from one friendships/show.json call (target_id only; the source is the authenticating account). Same one-shot pinned-account pattern as FollowUser: no retry, no relogin. It is the per-target alternative to the GetFollowing list when that GraphQL op's rate window is contended by co-tenants.
func (*Client) GetBookmarks ¶ added in v0.6.0
GetBookmarks fetches the authenticated pool account's bookmarked tweets (paginated up to maxCount). Paging ends at maxCount or the end of the list. It also ends early, returning what was collected with a nil error, on a page with no items, a repeated or terminal ("0|...") cursor, or several consecutive pages that add nothing new; those cases log a warning when they cut the read short of maxCount. The result may exceed maxCount by up to one page.
func (*Client) GetCommunityTweets ¶ added in v0.6.0
func (c *Client) GetCommunityTweets(ctx context.Context, communityID string, maxCount int) ([]*Tweet, error)
GetCommunityTweets fetches the latest tweets in a community (paginated up to maxCount). Paging ends at maxCount or the end of the list. It also ends early, returning what was collected with a nil error, on a page with no items, a repeated or terminal ("0|...") cursor, or several consecutive pages that add nothing new; those cases log a warning when they cut the read short of maxCount. The result may exceed maxCount by up to one page.
func (*Client) GetDMInbox ¶ added in v0.6.0
func (c *Client) GetDMInbox(ctx context.Context) ([]*DMConversation, error)
GetDMInbox fetches the authenticated pool account's DM inbox. The inbox is per-account state, so doGET pins a real account from the pool (DMInbox is in requiresAuth — no guest fallback). Returns the conversations, each with its participants and the messages present in the initial inbox state. An empty inbox (0 conversations) is a valid, non-error result.
func (*Client) GetDeviceFollowTweets ¶ added in v0.7.0
GetDeviceFollowTweets fetches the merged device-notifications timeline via the legacy REST endpoint.
func (*Client) GetDeviceNotifications ¶ added in v0.7.0
GetDeviceNotifications polls the merged device-notifications feed: REST device_follow.json, with GraphQL tweetNotificationsQuery as the fallback when REST is gone (HTTP 404/410 or another endpoint fault). An empty REST answer is a normal quiet poll and does NOT trigger the fallback — that would double the request volume in the steady state and hide failures. If the fallback fails too, both sources are broken and the joined error is returned; a cancelled or expired ctx is always returned as is.
func (*Client) GetFollowers ¶
func (c *Client) GetFollowers(ctx context.Context, userID string, maxCount int) ([]*TwitterUser, error)
GetFollowers fetches followers for a user (paginated). Paging ends at maxCount or the end of the list. It also ends early, returning what was collected with a nil error, on a page with no items, a repeated or terminal ("0|...") cursor, or several consecutive pages that add nothing new; those cases log a warning when they cut the read short of maxCount. The result may exceed maxCount by up to one page.
func (*Client) GetFollowing ¶
func (c *Client) GetFollowing(ctx context.Context, userID string, maxCount int) ([]*TwitterUser, error)
GetFollowing fetches accounts a user follows (paginated). Paging ends at maxCount or the end of the list. It also ends early, returning what was collected with a nil error, on a page with no items, a repeated or terminal ("0|...") cursor, or several consecutive pages that add nothing new; those cases log a warning when they cut the read short of maxCount. The result may exceed maxCount by up to one page.
func (*Client) GetHomeLatestTimeline ¶ added in v0.6.0
GetHomeLatestTimeline fetches the reverse-chronological "Following" home timeline. POST (pool), mirroring SearchTimeline.
func (*Client) GetHomeTimeline ¶ added in v0.6.0
GetHomeTimeline fetches the algorithmic "For you" home timeline. POST (pool), mirroring SearchTimeline.
func (*Client) GetListTweets ¶ added in v0.6.0
GetListTweets fetches the latest tweets in a list (paginated up to maxCount). Paging ends at maxCount or the end of the list. It also ends early, returning what was collected with a nil error, on a page with no items, a repeated or terminal ("0|...") cursor, or several consecutive pages that add nothing new; those cases log a warning when they cut the read short of maxCount. The result may exceed maxCount by up to one page.
func (*Client) GetRetweeters ¶
func (c *Client) GetRetweeters(ctx context.Context, tweetID string, maxCount int) ([]*TwitterUser, error)
GetRetweeters fetches users who retweeted a tweet (paginated). Paging ends at maxCount or the end of the list. It also ends early, returning what was collected with a nil error, on a page with no items, a repeated or terminal ("0|...") cursor, or several consecutive pages that add nothing new; those cases log a warning when they cut the read short of maxCount. The result may exceed maxCount by up to one page.
func (*Client) GetTweetByID ¶
GetTweetByID fetches a single tweet by its ID.
func (*Client) GetTweetConversation ¶ added in v0.6.11
func (c *Client) GetTweetConversation(ctx context.Context, tweetID string) (*Tweet, []*Tweet, error)
GetTweetConversation fetches the focal tweet plus its conversation entries (replies and ancestors, in page order) via TweetDetail.
func (*Client) GetTweetNotifications ¶ added in v0.7.0
GetTweetNotifications fetches the same merged feed via GraphQL tweetNotificationsQuery — the fallback when device_follow.json is gone or empty. It carries its own endpoint key so a fault on one source cannot quarantine the account from the other.
func (*Client) GetUserByScreenName ¶
GetUserByScreenName fetches a user profile by Twitter handle.
func (*Client) GetUserTweets ¶
GetUserTweets fetches recent tweets for a user.
func (*Client) GetVerifiedFollowers ¶ added in v0.6.0
func (c *Client) GetVerifiedFollowers(ctx context.Context, userID string, maxCount int) ([]*TwitterUser, error)
GetVerifiedFollowers fetches a user's blue-verified followers (paginated up to maxCount). The response shares the followers shape (data.user.result.timeline.timeline), so it reuses the existing fetchUserList helper + parse.UserList parser. Paging ends at maxCount or the end of the list. It also ends early, returning what was collected with a nil error, on a page with no items, a repeated or terminal ("0|...") cursor, or several consecutive pages that add nothing new; those cases log a warning when they cut the read short of maxCount. The result may exceed maxCount by up to one page.
func (*Client) HealthReport ¶
func (c *Client) HealthReport() []AccountHealth
HealthReport returns health stats for all accounts in the pool.
func (*Client) HoldAccount ¶ added in v0.7.2
HoldAccount benches a pooled account until the given time: the pool pick skips it for every endpoint while the account clock is before until, and it becomes eligible again afterwards. Returns false when no pool account matches username (the match is case-insensitive, like AccountByUsername). Re-holding is max-wins: a later call with an earlier until never shortens an existing hold — a deliberate early lift goes through ReleaseAccount. The hold gates only the pool pick: account-pinned write paths that take the account directly (doPOST-backed writes, FollowUser, UploadMedia) do not consult it. A hold is not a failure — it applies no health penalty, no failure counters and no relogin, so it deliberately bypasses the SetActive/reactivateAt cooldown machinery. When every account is held the caller gets the same error a fully-spent pool returns today: "pool exhausted for <endpoint> (requires auth): all N items are unavailable".
func (*Client) LikeTweet ¶ added in v0.6.0
LikeTweet favorites a tweet from a specific account. Returns nil on the "Done" ack, else an error.
func (*Client) PostWithAccount ¶
PostWithAccount posts a tweet from a named account (by username). Returns the tweet ID on success.
func (*Client) PostWithMedia ¶ added in v0.6.0
func (c *Client) PostWithMedia(ctx context.Context, username, text string, paths ...string) (string, error)
PostWithMedia uploads the given files and posts a tweet attaching them, from a named account (by username). Returns the tweet ID on success.
func (*Client) ReleaseAccount ¶ added in v0.7.2
ReleaseAccount lifts a HoldAccount hold early: the account becomes eligible for the next pool pick at once. Returns false when no pool account matches username. Releasing an account that was not held is a no-op.
func (*Client) ReplyTweet ¶ added in v0.6.0
func (c *Client) ReplyTweet(ctx context.Context, acc *Account, tweetID, text string) (string, error)
ReplyTweet posts a reply to tweetID from a specific account. Returns the reply tweet ID. It routes through the CreateTweet op (reply.in_reply_to_tweet_id set) and reuses parse.CreateTweet for the result.
func (*Client) Retweet ¶ added in v0.6.0
Retweet retweets a tweet from a specific account. Returns the new retweet ID.
func (*Client) SearchTimeline ¶
SearchTimeline searches for tweets matching a query on the Latest tab. Uses POST (Twitter migrated this endpoint from GET in March 2026).
func (*Client) SearchTweets ¶ added in v0.6.11
func (c *Client) SearchTweets(ctx context.Context, query string, product SearchProduct, count int) ([]*Tweet, error)
SearchTweets searches tweets on an explicit tab — Top or Latest. Anything else is rejected before the request so a typo can't silently read Latest.
func (*Client) SearchUsers ¶ added in v0.6.11
SearchUsers searches the People tab.
func (*Client) SendDM ¶ added in v0.6.0
func (c *Client) SendDM(ctx context.Context, acc *Account, conversationID, text string) (string, error)
SendDM sends a text direct message into an existing conversation from a specific account (account-pinned write, mirroring CreateTweet/engagement mutations). Returns the created message id.
FAIL-CLOSED: a 200 response with no usable message id (empty body, a DM validation failure, or a surfaced API error) returns an error — never a silent "sent". Mirrors parse.CreateTweet's empty-result guard. conversationID is a CONVERSATION id (1:1 form "<recipientID>-<selfID>", per twikit), NOT a user id; there is no derive helper — the caller forms it.
func (*Client) SetAutoReloginGate ¶
func (c *Client) SetAutoReloginGate(g AutoReloginGate)
SetAutoReloginGate registers a gate checked before relogin attempts.
func (*Client) SetDeviceNotifications ¶ added in v0.7.0
func (c *Client) SetDeviceNotifications(ctx context.Context, acc *Account, userID string, on bool) (bool, error)
SetDeviceNotifications turns acc's device notifications for userID on or off via friendships/update.json (the "bell" toggle) and returns the notifications_enabled flag it reads back from the response. It is a write: one attempt, pinned to acc, no retry and no relogin, so a failure surfaces to the caller instead of repeating. The readback is advisory only — X has answered notifications_enabled=false on a write that did land; verify the effect with FriendshipState when it matters.
func (*Client) UnlikeTweet ¶ added in v0.6.0
UnlikeTweet un-favorites a tweet from a specific account. Returns nil on the "Done" ack, else an error.
func (*Client) Unretweet ¶ added in v0.6.0
Unretweet removes a retweet from a specific account. Returns the source tweet ID. The DeleteRetweet op keys the target by "source_tweet_id" (the ID of the ORIGINAL tweet, not the retweet's own ID) — confirmed live 2026-06-20: sending "tweet_id" yields HTTP 422 GRAPHQL_VALIDATION_FAILED ("source_tweet_id must be defined"). Matches trevorhobenshield/twitter-api-client + dimdenGD/OldTweetDeck.
func (*Client) UploadMedia ¶ added in v0.6.0
func (c *Client) UploadMedia(ctx context.Context, acc *Account, data []byte, mediaType string) (string, error)
UploadMedia uploads raw media bytes via the chunked INIT/APPEND/FINALIZE flow using the given account's authenticated stealth client, and returns the media_id string for use in CreateTweetWithMedia. mediaType is the MIME type (e.g. "image/jpeg", "image/gif", "video/mp4").
func (*Client) UploadMediaFile ¶ added in v0.6.0
UploadMediaFile reads a file from disk, infers its MIME type, and uploads it.
func (*Client) ValidateAccount ¶
ValidateAccount checks that an account's auth_token and ct0 are still valid by calling the authenticated account/settings.json REST endpoint. Returns nil on HTTP 200, an error on 401/403, or a wrapped error on network failure.
type ClientConfig ¶
type ClientConfig struct {
// Accounts is the list of Twitter accounts to use.
Accounts []*Account
// DefaultProxy is the proxy URL for accounts without per-account proxies.
DefaultProxy string
// SessionTTL controls how long saved sessions are considered valid. It
// configures the default file store only; a custom SessionStore owns its
// own expiry policy.
SessionTTL time.Duration
// AuthCooldown is the soft-deactivation duration for auth errors. It is also
// the bench for a first "looks automated" (226) verdict.
AuthCooldown time.Duration
// BanCooldown is the soft-deactivation duration for banned/locked accounts.
// A repeat 226 uses max(BanCooldown, 2*AuthCooldown), so a repeat is never
// shorter than the first.
BanCooldown time.Duration
// NonResponsiveCooldown is the BASE backoff for accounts that trip the
// consecutive-failure threshold against a transiently-broken endpoint. The
// effective cooldown grows per trip (x2, capped at 30m, +/-30% jitter) so a
// flapping upstream self-heals once it recovers instead of latching the pool
// permanently. Default: 5m.
NonResponsiveCooldown time.Duration
// CaptchaSolver is the optional CAPTCHA solver for locked accounts.
CaptchaSolver captcha.Solver
// RateLimit configures per-account per-endpoint rate limiting.
RateLimit ratelimit.Config
// OpenAccountCount is the number of anonymous guest accounts to create at startup.
OpenAccountCount int
// MetricsHook is called on each API request for external metrics collection.
// endpoint is the operation name, success and rateLimited indicate the outcome.
//
// Deprecated: use MeterProvider. MetricsHook carries no latency or error
// class; it keeps working unchanged for existing consumers.
MetricsHook func(endpoint string, success, rateLimited bool)
// OnRequest, when set, is called once per HTTP attempt that was served by a
// pooled account — on success AND on every error path — at the same
// observation point that feeds the request-duration metric, after the
// response is classified. Guest-token fallback requests carry no account
// and emit no event; media-upload attempts emit under the "UploadMedia"
// endpoint label. It is called synchronously on the request path and
// without a recover, so it must not block (offload heavy work to a channel
// or queue) and must not panic — a panic propagates to the request's
// caller. nil = no-op, zero cost.
OnRequest func(RequestEvent)
// MeterProvider supplies the OpenTelemetry meter provider for the client's
// metrics (see docs/metrics.md). nil = otel.GetMeterProvider(), which is a
// no-op until the application installs an SDK provider.
MeterProvider metric.MeterProvider
// SessionDir overrides the default session persistence directory used by
// the default file store. Ignored when SessionStore is set.
// Default: ~/.go-twitter/sessions
SessionDir string
// SessionStore persists per-account sessions. nil = a FileSessionStore
// built from SessionDir and SessionTTL (the historical behaviour). Set it
// to make an external credential store the single source of truth.
SessionStore SessionStore
// ProxyBackoffInitial is the initial backoff for proxy failures.
ProxyBackoffInitial time.Duration
// ProxyBackoffMax is the maximum backoff for proxy failures.
ProxyBackoffMax time.Duration
// PoolAlertHook is called when the pool emits alerts (account deactivation, proxy failures, etc.).
// topic is the alert type (e.g. "pool.deactivated"), payload contains details.
PoolAlertHook func(topic string, payload any)
// DisableGuestFallback disables the guest-token fallback path entirely.
// When true, endpoints that would normally fall back to guest mode after
// pool exhaustion will return an error instead. Recommended in production
// where guest tokens from datacenter IPs return persistent 403 errors.
// Default: false (guest fallback enabled for backward compatibility).
DisableGuestFallback bool
// ChromeWireForAccountlessClients opts the shared account-less client (the
// guest-token fallback and warm fetches that carry no account) and the
// open-account login client into the current Chrome request shape (header
// order, fetch metadata, origin and priority as the X web client in Chrome
// sends them). By default they send the request shape of v0.8.6 unchanged, so
// a deployment that hosts only older accounts presents one consistent shape.
// Accounts choose their shape by their own profile (chrome_146/150/152/156 get
// the current one) and are not affected by this field. Default: false.
//
// The header shape is not the whole identity: these clients, and an account
// whose caller-set user agent no built-in profile presents, take their TLS
// profile and user agent from go-stealth's default, which is chrome_156 since
// go-stealth v1.27.0 (chrome_152 before); only the header shape stays legacy.
// An open-account login runs on the shape chosen here (legacy by default); the
// account it mints then sends the shape of its own profile.
ChromeWireForAccountlessClients bool
// AccountPaceMin is the minimum human-pace delay between consecutive requests
// MADE BY THE SAME ACCOUNT, applied AFTER the pool selects an account and IN
// ADDITION TO the anti-fingerprint jitter. Pacing is keyed by account, not by
// domain: each account self-paces its own request rhythm via an independent
// timestamp, so a low-frequency caller (seed) is never starved by a global
// gate that a high-frequency caller (KOL/VC) holds. It is anti-burst stealth
// spacing UNDER the per-account-per-endpoint rate-limit ceiling, never a
// throughput cap below it.
//
// Together with AccountPaceRandom the realized inter-request spacing for one
// account is [AccountPaceMin, AccountPaceMin+AccountPaceRandom) (variable,
// never below AccountPaceMin). Default: 800ms. Set AccountPaceDisabled to off.
AccountPaceMin time.Duration
// AccountPaceRandom is the random jitter added on top of AccountPaceMin for
// the per-account spacing (see AccountPaceMin). Default: 1.2s.
AccountPaceRandom time.Duration
// AccountPaceDisabled turns off the per-account human pace entirely. When
// true only the anti-fingerprint jitter applies. Default: false (pacing
// enabled with safe defaults).
AccountPaceDisabled bool
// RequestJitter opts in to a random delay before every pool GET/POST (see
// WithRequestJitter). nil (the default) and a zero Jitter send immediately.
// The pinned write path (doPOST) and media upload keep stealth.DefaultJitter
// regardless, and the per-account pacer is independent of this field.
RequestJitter *stealth.Jitter
// MaxInFlightPerAccount caps the number of requests a single account may
// have in flight at once — the in-process counterpart of nitter's
// maxConcurrentReqs. A pool pick that would exceed the cap skips the
// account; when every eligible account is at the cap the call waits for a
// slot (bounded by the caller's ctx and the shared pool-wait budget, see
// twitter.client.pool.wait.duration). Account-pinned paths (doPOST, media
// upload) wait for the pinned account's slot the same way.
// Default: 2. A negative value disables the cap.
// The cap bounds in-flight SLOTS, not sockets: a cancelled request releases
// its slot at once, but its socket can outlive the slot until go-stealth's
// own request timeout.
MaxInFlightPerAccount int
// XtidKeySource selects the page x-client-transaction-id keys are derived
// from. The zero value keeps the account-cookie page; xtid.KeySourceLoggedOut
// reads the cookie-less https://x.com/i/jf/ page like the reference
// implementation. The GO_TWITTER_XTID_KEY_SOURCE=logged-out env var selects
// the same for managers this client does not own. Default: account page.
XtidKeySource xtid.KeySource
// contains filtered or unexported fields
}
ClientConfig holds all configuration for the Twitter client.
type ClientOption ¶ added in v0.7.0
type ClientOption func(*ClientConfig)
ClientOption adjusts a ClientConfig before NewClient builds the client.
func WithAccountPace ¶ added in v0.7.0
func WithAccountPace(min, random time.Duration) ClientOption
WithAccountPace overrides the per-account human-pace spacing for this client only: consecutive requests of one account are spaced by [min, min+random). WithAccountPace(0, 0) disables the pacer. A zero min or zero random is treated as a 1ns floor (a zero field would otherwise be replaced by the 800ms / 1.2s default), so WithAccountPace(100ms, 0) spaces by exactly ~100ms. Removing the pace makes one account's request rhythm more regular; the per-endpoint rate limiter still applies.
func WithRequestJitter ¶ added in v0.7.0
func WithRequestJitter(min, max time.Duration) ClientOption
WithRequestJitter opts back in to a random delay before every pool-rotated GET/POST, drawn uniformly from [min, max). By default a client sends immediately: account rotation and quotas belong to the service that leases accounts, and the old 500ms-2.5s sleep dominated read latency (median ~1.5s of a ~2s call). The per-account pacer (WithAccountPace) is unaffected and still spaces consecutive requests of one account. The pinned write path (doPOST) and media upload keep their own human pacing regardless.
stealth.DefaultJitter reproduces the previous default:
twitter.WithRequestJitter(stealth.DefaultJitter.Min, stealth.DefaultJitter.Max)
func WithoutRequestJitter ¶ added in v0.7.0
func WithoutRequestJitter() ClientOption
WithoutRequestJitter removes the pre-request delay; equivalent to WithRequestJitter(0, 0). This is already the default and exists to state the intent explicitly (and to override an earlier WithRequestJitter).
type DMConversation ¶ added in v0.6.0
type DMConversation = model.DMConversation
DMConversation is a single direct-message conversation from the 1.1 inbox.
type FileSessionStore ¶ added in v0.6.17
type FileSessionStore struct {
// Dir is the session directory. Empty means ~/.go-twitter/sessions.
Dir string
// TTL expires sessions older than this on Load (reported as
// ErrSessionNotFound). TTL <= 0 means no expiry; note NewClient defaults
// ClientConfig.SessionTTL 0 to 24h before building the default store.
TTL time.Duration
}
FileSessionStore keeps one JSON file per account at <Dir>/<username>.json. Writes are atomic (temp file + fsync + rename) so a concurrent Load sees the previous or the new complete file, never a truncated one. Files are 0600 in a 0700 directory.
func (*FileSessionStore) Delete ¶ added in v0.6.17
func (f *FileSessionStore) Delete(_ context.Context, username string) error
Delete implements SessionStore.
func (*FileSessionStore) Load ¶ added in v0.6.17
Load implements SessionStore. A missing, TTL-expired or undecodable (truncated/corrupt) file yields ErrSessionNotFound, as does a username that is not a valid file name: the file holds nothing usable, and treating it as a miss lets the client log in and overwrite it (Save re-creates a corrupt file; for an invalid name Save still rejects it and logs, exactly as before the store was pluggable). Genuine I/O failures, permission denied included, are returned: the file may hold a newer valid session that cannot be read, and a Save would most likely fail too, so the client must not log in blindly.
type RequestEvent ¶ added in v0.7.2
type RequestEvent struct {
Username string
Endpoint string
Status int
Outcome string
RateLimit RequestRateLimit
}
RequestEvent describes one HTTP attempt served by a pooled account, emitted to ClientConfig.OnRequest. Status is the HTTP status code (0 when the transport failed before a response). Outcome is the same bounded classification the client records to its metrics (e.g. "ok", "rate_limited", "transport") — see the twitter.outcome values in docs/metrics.md.
type RequestRateLimit ¶ added in v0.7.2
RequestRateLimit mirrors one response's x-rate-limit-* headers inside a RequestEvent: Limit 0, Remaining -1 and a zero Reset each mean "header absent or unparseable".
type RequestTrace ¶ added in v0.7.0
type RequestTrace struct {
// contains filtered or unexported fields
}
RequestTrace is an opt-in collector of per-phase timestamps for the requests issued under a context (see WithRequestTrace). It exists for latency diagnosis: nothing is recorded, and the request path pays only a nil check, unless a caller attaches one. Read the calls only after the traced client method has returned.
func (*RequestTrace) Calls ¶ added in v0.7.0
func (t *RequestTrace) Calls() []CallTrace
Calls returns the traced requests in issue order.
type SearchProduct ¶ added in v0.6.11
type SearchProduct string
SearchProduct selects the SearchTimeline results tab.
const ( SearchTop SearchProduct = "Top" SearchLatest SearchProduct = "Latest" SearchPeople SearchProduct = "People" SearchMedia SearchProduct = "Media" )
type Session ¶ added in v0.6.17
type Session struct {
AuthToken string `json:"auth_token"`
CT0 string `json:"ct0"`
SavedAt time.Time `json:"saved_at"`
}
Session is the persisted credential pair of one account. The JSON tags are the on-disk format of FileSessionStore and are kept stable.
String and LogValue redact the tokens so a Session printed or logged by accident does not leak them; JSON marshalling is unaffected.
func (Session) GoString ¶ added in v0.6.17
GoString implements fmt.GoStringer so %#v does not leak the tokens.
type SessionInfo ¶ added in v0.8.11
type SessionInfo struct {
// ScreenName is the handle the session is signed in as, as X reports it.
ScreenName string
}
SessionInfo is the identity a live session belongs to.
type SessionStore ¶ added in v0.6.17
type SessionStore interface {
// Load returns the stored session, or ErrSessionNotFound when there is none.
Load(ctx context.Context, username string) (Session, error)
// Save stores the session, replacing any previous one.
Save(ctx context.Context, username string, s Session) error
// Delete removes the session; deleting a missing session is not an error.
Delete(ctx context.Context, username string) error
}
SessionStore persists per-account sessions (auth_token + ct0) across process restarts and ct0 rotations. Implementations must be safe for concurrent use.
The client never lets a store failure fail an API request: errors are logged (username only, never tokens) and the request continues. Every call carries a context bounded to a few seconds.
Implementations MUST honour ctx: the client bounds every call with a 5s timeout and calls Save synchronously on the request path right after a ct0 rotation, so a store that ignores ctx stalls requests. Save is last-write-wins; the client sets Session.SavedAt (stores may compare it). Load must return ErrSessionNotFound for a miss; any other Load error is NOT treated as a miss: an account without provided credentials then fails instead of logging in with a password, so a flaky store cannot cause a login storm.
Session lifetime is the store's concern: the client applies no TTL of its own. FileSessionStore honours ClientConfig.SessionTTL; a database-backed store can ignore Session.SavedAt entirely or enforce its own policy.
Accounts leased by the *WithSocial helpers are credential-service owned and never touch the store.
type Tweet ¶
Tweet represents a single tweet.
func GetTweetByIDWithSocial ¶ added in v0.6.25
func GetTweetByIDWithSocial(ctx context.Context, sc *social.Client, tweetID string, opts ...ClientOption) (*Tweet, error)
GetTweetByIDWithSocial fetches a single tweet via the TweetDetail page.
func GetUserTweetsByIDWithSocial ¶ added in v0.6.25
func GetUserTweetsByIDWithSocial(ctx context.Context, sc *social.Client, userID string, limit int, opts ...ClientOption) ([]*Tweet, error)
GetUserTweetsByIDWithSocial fetches recent tweets for a numeric user ID — the caller already resolved the handle, so no UserByScreenName lookup burns the lease's budget.
func GetUserTweetsWithSocial ¶ added in v0.6.11
func GetUserTweetsWithSocial(ctx context.Context, sc *social.Client, handle string, limit int, opts ...ClientOption) ([]*Tweet, error)
GetUserTweetsWithSocial resolves the handle and fetches its recent tweets — two GraphQL calls inside one account attempt.
func SearchTweetsWithSocial ¶ added in v0.6.11
func SearchTweetsWithSocial(ctx context.Context, sc *social.Client, query string, product SearchProduct, limit int, opts ...ClientOption) ([]*Tweet, error)
SearchTweetsWithSocial searches tweets on an explicit tab (Top or Latest).
func SearchWithSocial ¶
func SearchWithSocial(ctx context.Context, sc *social.Client, query string, limit int, opts ...ClientOption) ([]*Tweet, error)
SearchWithSocial acquires accounts from go-social and searches Twitter on the Latest tab, retrying with different accounts on failure. go-social rotates accounts on each call, so retries naturally use different credentials.
type TwitterUser ¶
type TwitterUser = model.TwitterUser
TwitterUser represents a Twitter/X account profile.
func GetFollowersWithSocial ¶ added in v0.6.25
func GetFollowersWithSocial(ctx context.Context, sc *social.Client, userID string, limit int, opts ...ClientOption) ([]*TwitterUser, error)
GetFollowersWithSocial lists the accounts following userID.
func GetFollowingWithSocial ¶ added in v0.6.25
func GetFollowingWithSocial(ctx context.Context, sc *social.Client, userID string, limit int, opts ...ClientOption) ([]*TwitterUser, error)
GetFollowingWithSocial lists the accounts userID follows.
func GetRetweetersWithSocial ¶ added in v0.6.25
func GetRetweetersWithSocial(ctx context.Context, sc *social.Client, tweetID string, limit int, opts ...ClientOption) ([]*TwitterUser, error)
GetRetweetersWithSocial lists the accounts that retweeted tweetID.
func GetUserWithSocial ¶ added in v0.6.11
func GetUserWithSocial(ctx context.Context, sc *social.Client, handle string, opts ...ClientOption) (*TwitterUser, error)
GetUserWithSocial fetches a profile by handle.
func SearchUsersWithSocial ¶ added in v0.6.11
func SearchUsersWithSocial(ctx context.Context, sc *social.Client, query string, limit int, opts ...ClientOption) ([]*TwitterUser, error)
SearchUsersWithSocial searches the People tab.
Source Files
¶
- account.go
- auth.go
- automated.go
- client.go
- config.go
- cookies.go
- ct0.go
- devicefollow.go
- dm.go
- doc.go
- endpoints.go
- errors.go
- follow.go
- graphql.go
- headers.go
- identity.go
- media.go
- metrics.go
- options.go
- pagination.go
- probe_hooks.go
- queryid_refresh.go
- ratelimit_adaptive.go
- ratelimit_state.go
- relogin_breaker.go
- relogin_gate.go
- request.go
- session_check.go
- session_store.go
- social.go
- social_identity.go
- social_report.go
- trace.go
- types.go
- validate.go
- warm_nav.go
- wire.go
- xtid_failure.go
- xtid_refresh.go
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
gql-sync
command
Command gql-sync auto-extracts Twitter GraphQL queryIDs from x.com's webpack bundles and regenerates internal/operations/queryids_gen.go — the "generated" layer of the endpoints override chain (env > generated > committed).
|
Command gql-sync auto-extracts Twitter GraphQL queryIDs from x.com's webpack bundles and regenerates internal/operations/queryids_gen.go — the "generated" layer of the endpoints override chain (env > generated > committed). |
|
xtid-probe
command
Command xtid-probe measures which x-client-transaction-id source X accepts.
|
Command xtid-probe measures which x-client-transaction-id source X accepts. |
|
xweb-report
command
Command xweb-report crawls x.com's PUBLIC logged-out web shell, parses the x-web (Vite + Relay) operation artifacts out of its chunk graph and writes a markdown comparison against every GraphQL operation go-twitter issues (docs/xweb-migration.md).
|
Command xweb-report crawls x.com's PUBLIC logged-out web shell, parses the x-web (Vite + Relay) operation artifacts out of its chunk graph and writes a markdown comparison against every GraphQL operation go-twitter issues (docs/xweb-migration.md). |
|
internal
|
|
|
bundle
Package bundle fetches x.com warm pages, reassembles the webpack chunk map (chunkID->name, chunkID->hash), resolves a module name to its abs.twimg.com bundle URL, BFS-follows import() refs, and caches the snapshot to disk with a TTL.
|
Package bundle fetches x.com warm pages, reassembles the webpack chunk map (chunkID->name, chunkID->hash), resolves a module name to its abs.twimg.com bundle URL, BFS-follows import() refs, and caches the snapshot to disk with a TTL. |
|
gqlextract
Package gqlextract extracts GraphQL queryIDs and feature flags from x.com's own bundles.
|
Package gqlextract extracts GraphQL queryIDs and feature flags from x.com's own bundles. |
|
operations
Package operations holds the compiled GraphQL operation data: the committed queryIDs and per-operation feature flags, the cmd/gql-sync generated layer (queryids_gen.go, features_gen.go) and the TWITTER_QID_* variable names.
|
Package operations holds the compiled GraphQL operation data: the committed queryIDs and per-operation feature flags, the cmd/gql-sync generated layer (queryids_gen.go, features_gen.go) and the TWITTER_QID_* variable names. |
|
parse
Package parse decodes X API response bodies into the model types.
|
Package parse decodes X API response bodies into the model types. |
|
telemetry
Package telemetry holds the OpenTelemetry instruments, their names and label values for go-twitter.
|
Package telemetry holds the OpenTelemetry instruments, their names and label values for go-twitter. |
|
xtidprobe
Package xtidprobe is the seam between cmd/xtid-probe and the packages whose unexported internals it measures.
|
Package xtidprobe is the seam between cmd/xtid-probe and the packages whose unexported internals it measures. |
|
xwebnative
Package xwebnative is the single list of GraphQL operations go-twitter issues against x.com's x-web (Vite + Relay) stack, as opposed to the legacy responsive-web stack.
|
Package xwebnative is the single list of GraphQL operations go-twitter issues against x.com's x-web (Vite + Relay) stack, as opposed to the legacy responsive-web stack. |
|
Package model holds the response types returned by the go-twitter client.
|
Package model holds the response types returned by the go-twitter client. |
|
Package slogsample provides sampling utilities for slog handlers.
|
Package slogsample provides sampling utilities for slog handlers. |
|
Package social provides a thin HTTP client for an external account management service.
|
Package social provides a thin HTTP client for an external account management service. |