import * as crypto from "crypto";
//#region node_modules/.pnpm/@clawnch+clawnx@1.0.0/node_modules/@clawnch/clawnx/dist/clawnx.js
/**
* ClawnX — X/Twitter API v2 client for AI agents
*
* Post tweets, search, read timelines, manage bookmarks, stream, chain
* actions, and upload media — all from TypeScript with zero npm dependencies.
*
* Auth: OAuth 1.0a (HMAC-SHA1) for writes, Bearer token for reads.
* Credentials are loaded from environment variables or passed directly.
*
* @example
* ```typescript
* import { ClawnX } from '@clawnch/clawnx';
*
* const x = new ClawnX();
*
* // Post a tweet
* await x.postTweet({ text: 'Just launched $MYTKN on @clawnch!' });
*
* // Search for mentions of your token
* const results = await x.searchTweets({ query: '$MYTKN' });
*
* // Upload media from a URL and post
* const media = await x.uploadMediaFromUrl({ url: 'https://example.com/img.png' });
* await x.postTweet({ text: 'Check this out!', mediaIds: [media.media_id_string] });
*
* // Stream filtered tweets
* await x.setStreamRules([{ value: '$MYTKN', tag: 'token' }]);
* const streamed = await x.streamFiltered({ durationMs: 30_000 });
* ```
*/
const X_API_BASE = "https://api.x.com/2";
/**
* Extract a tweet ID from an x.com/twitter.com URL or raw numeric string.
*/
function parseTweetId(input) {
	const urlMatch = input.match(/(?:twitter\.com|x\.com)\/\w+\/status\/(\d+)/);
	if (urlMatch) return urlMatch[1];
	const stripped = input.trim();
	if (/^\d+$/.test(stripped)) return stripped;
	throw new Error(`Invalid tweet ID or URL: ${input}`);
}
/**
* Strip leading @ from a username.
*/
function stripAt(username) {
	return username.replace(/^@/, "");
}
function percentEncode(s) {
	return encodeURIComponent(s).replace(/[!'()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`);
}
function generateOAuthHeader(method, url, creds) {
	const oauthParams = {
		oauth_consumer_key: creds.apiKey,
		oauth_nonce: crypto.randomBytes(16).toString("hex"),
		oauth_signature_method: "HMAC-SHA1",
		oauth_timestamp: Math.floor(Date.now() / 1e3).toString(),
		oauth_token: creds.accessToken,
		oauth_version: "1.0"
	};
	const allParams = { ...oauthParams };
	const parsed = new URL(url);
	for (const [k, v] of parsed.searchParams.entries()) allParams[k] = v;
	const paramString = Object.entries(allParams).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${percentEncode(k)}=${percentEncode(v)}`).join("&");
	const baseUrl = `${parsed.protocol}//${parsed.host}${parsed.pathname}`;
	const baseString = `${method.toUpperCase()}&${percentEncode(baseUrl)}&${percentEncode(paramString)}`;
	const signingKey = `${percentEncode(creds.apiSecret)}&${percentEncode(creds.accessTokenSecret)}`;
	oauthParams["oauth_signature"] = crypto.createHmac("sha1", signingKey).update(baseString).digest("base64");
	return `OAuth ${Object.entries(oauthParams).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${percentEncode(k)}="${percentEncode(v)}"`).join(", ")}`;
}
function loadCredentialsFromEnv() {
	const get = (name) => {
		const val = process.env[name];
		if (!val) throw new Error(`Missing env var: ${name}. Set X_API_KEY, X_API_SECRET, X_ACCESS_TOKEN, X_ACCESS_TOKEN_SECRET, X_BEARER_TOKEN or pass credentials directly to ClawnX.`);
		return val;
	};
	return {
		apiKey: get("X_API_KEY"),
		apiSecret: get("X_API_SECRET"),
		accessToken: get("X_ACCESS_TOKEN"),
		accessTokenSecret: get("X_ACCESS_TOKEN_SECRET"),
		bearerToken: get("X_BEARER_TOKEN")
	};
}
var ClawnX = class {
	creds;
	timeout;
	_userId = null;
	/** Active stream abort controller (one stream at a time) */
	_streamController = null;
	constructor(options = {}) {
		this.creds = options.credentials ?? loadCredentialsFromEnv();
		this.timeout = options.timeout ?? 3e4;
	}
	async bearerGet(url) {
		const controller = new AbortController();
		const timer = setTimeout(() => controller.abort(), this.timeout);
		try {
			const resp = await fetch(url, {
				method: "GET",
				headers: { Authorization: `Bearer ${this.creds.bearerToken}` },
				signal: controller.signal
			});
			return this.handle(resp);
		} finally {
			clearTimeout(timer);
		}
	}
	async oauthRequest(method, url, body) {
		const headers = { Authorization: generateOAuthHeader(method, url, this.creds) };
		if (body) headers["Content-Type"] = "application/json";
		const controller = new AbortController();
		const timer = setTimeout(() => controller.abort(), this.timeout);
		try {
			const resp = await fetch(url, {
				method,
				headers,
				body: body ? JSON.stringify(body) : void 0,
				signal: controller.signal
			});
			return this.handle(resp);
		} finally {
			clearTimeout(timer);
		}
	}
	async handle(resp) {
		if (resp.status === 429) throw new ClawnXError(`Rate limited. Resets at ${resp.headers.get("x-rate-limit-reset") ?? "unknown"}.`, 429);
		const data = await resp.json();
		if (!resp.ok) {
			const msg = (data.errors ?? []).map((e) => e.detail ?? e.message ?? "").filter(Boolean).join("; ") || JSON.stringify(data).slice(0, 500);
			throw new ClawnXError(`API error (HTTP ${resp.status}): ${msg}`, resp.status);
		}
		return data;
	}
	/**
	* Get the numeric user ID for the authenticated account.
	* Cached after first call.
	*/
	async getAuthenticatedUserId() {
		if (this._userId) return this._userId;
		this._userId = (await this.oauthRequest("GET", `${X_API_BASE}/users/me`)).data.id;
		return this._userId;
	}
	/**
	* Post a tweet (or reply, quote, poll).
	*
	* @example
	* ```typescript
	* // Simple tweet
	* await x.postTweet({ text: 'gm' });
	*
	* // Reply to a tweet
	* await x.postTweet({ text: 'nice!', replyTo: '123456789' });
	*
	* // Quote tweet
	* await x.postTweet({ text: 'check this out', quoteTweetId: '123456789' });
	*
	* // Poll
	* await x.postTweet({ text: 'Which chain?', pollOptions: ['Base', 'Ethereum', 'Solana'] });
	* ```
	*/
	async postTweet(opts) {
		const body = { text: opts.text };
		if (opts.replyTo) body.reply = { in_reply_to_tweet_id: opts.replyTo };
		if (opts.quoteTweetId) body.quote_tweet_id = opts.quoteTweetId;
		if (opts.pollOptions) body.poll = {
			options: opts.pollOptions,
			duration_minutes: opts.pollDurationMinutes ?? 1440
		};
		if (opts.mediaIds?.length) body.media = { media_ids: opts.mediaIds };
		return this.oauthRequest("POST", `${X_API_BASE}/tweets`, body);
	}
	/**
	* Delete a tweet.
	* @param idOrUrl - Tweet ID or full URL
	*/
	async deleteTweet(idOrUrl) {
		const id = parseTweetId(idOrUrl);
		return this.oauthRequest("DELETE", `${X_API_BASE}/tweets/${id}`);
	}
	/**
	* Get a single tweet by ID or URL with full expansions.
	* @param idOrUrl - Tweet ID or full URL
	*/
	async getTweet(idOrUrl) {
		const id = parseTweetId(idOrUrl);
		const params = new URLSearchParams({
			"tweet.fields": "created_at,public_metrics,author_id,conversation_id,in_reply_to_user_id,referenced_tweets,attachments,entities,lang,note_tweet",
			"expansions": "author_id,referenced_tweets.id,attachments.media_keys",
			"user.fields": "name,username,verified,profile_image_url,public_metrics",
			"media.fields": "url,preview_image_url,type,width,height,alt_text"
		});
		return this.bearerGet(`${X_API_BASE}/tweets/${id}?${params}`);
	}
	/**
	* Search recent tweets.
	*
	* Supports X query syntax: `from:user`, `$TICKER`, `#hashtag`,
	* `"exact phrase"`, `has:media`, `is:reply`, `-is:retweet`, `lang:en`.
	*
	* @example
	* ```typescript
	* // Search for your token ticker
	* const results = await x.searchTweets({ query: '$MYTKN' });
	*
	* // Search for tweets from a user
	* const results = await x.searchTweets({ query: 'from:clawnch', maxResults: 20 });
	* ```
	*/
	async searchTweets(opts) {
		const maxResults = Math.max(10, Math.min(opts.maxResults ?? 10, 100));
		const params = new URLSearchParams({
			query: opts.query,
			max_results: maxResults.toString(),
			"tweet.fields": "created_at,public_metrics,author_id,conversation_id,entities,lang,note_tweet",
			"expansions": "author_id,attachments.media_keys",
			"user.fields": "name,username,verified,profile_image_url",
			"media.fields": "url,preview_image_url,type"
		});
		if (opts.paginationToken) params.set("next_token", opts.paginationToken);
		return this.bearerGet(`${X_API_BASE}/tweets/search/recent?${params}`);
	}
	/**
	* Get tweet engagement metrics (requires OAuth — accesses non-public metrics).
	* @param idOrUrl - Tweet ID or full URL
	*/
	async getTweetMetrics(idOrUrl) {
		const id = parseTweetId(idOrUrl);
		return this.oauthRequest("GET", `${X_API_BASE}/tweets/${id}?tweet.fields=public_metrics,non_public_metrics,organic_metrics`);
	}
	/**
	* Look up a user by username.
	* @param username - Username (with or without @)
	*/
	async getUser(username) {
		const name = stripAt(username);
		return this.bearerGet(`${X_API_BASE}/users/by/username/${name}?user.fields=created_at,description,public_metrics,verified,profile_image_url,url,location,pinned_tweet_id`);
	}
	/**
	* Get a user's recent tweets.
	* @param username - Username (with or without @)
	*/
	async getUserTimeline(username, opts = {}) {
		const userId = (await this.getUser(username)).data.id;
		const maxResults = Math.max(5, Math.min(opts.maxResults ?? 10, 100));
		const params = new URLSearchParams({
			max_results: maxResults.toString(),
			"tweet.fields": "created_at,public_metrics,author_id,conversation_id,entities,lang,note_tweet",
			"expansions": "author_id,attachments.media_keys,referenced_tweets.id",
			"user.fields": "name,username,verified",
			"media.fields": "url,preview_image_url,type"
		});
		if (opts.paginationToken) params.set("pagination_token", opts.paginationToken);
		return this.bearerGet(`${X_API_BASE}/users/${userId}/tweets?${params}`);
	}
	/**
	* Get a user's followers.
	* @param username - Username (with or without @)
	*/
	async getFollowers(username, opts = {}) {
		const userId = (await this.getUser(username)).data.id;
		const maxResults = Math.max(1, Math.min(opts.maxResults ?? 100, 1e3));
		const params = new URLSearchParams({
			max_results: maxResults.toString(),
			"user.fields": "created_at,description,public_metrics,verified,profile_image_url"
		});
		if (opts.paginationToken) params.set("pagination_token", opts.paginationToken);
		return this.bearerGet(`${X_API_BASE}/users/${userId}/followers?${params}`);
	}
	/**
	* Get who a user follows.
	* @param username - Username (with or without @)
	*/
	async getFollowing(username, opts = {}) {
		const userId = (await this.getUser(username)).data.id;
		const maxResults = Math.max(1, Math.min(opts.maxResults ?? 100, 1e3));
		const params = new URLSearchParams({
			max_results: maxResults.toString(),
			"user.fields": "created_at,description,public_metrics,verified,profile_image_url"
		});
		if (opts.paginationToken) params.set("pagination_token", opts.paginationToken);
		return this.bearerGet(`${X_API_BASE}/users/${userId}/following?${params}`);
	}
	/**
	* Get your recent mentions.
	*/
	async getMentions(opts = {}) {
		const userId = await this.getAuthenticatedUserId();
		const maxResults = Math.max(5, Math.min(opts.maxResults ?? 10, 100));
		const params = new URLSearchParams({
			max_results: maxResults.toString(),
			"tweet.fields": "created_at,public_metrics,author_id,conversation_id,entities,note_tweet",
			"expansions": "author_id",
			"user.fields": "name,username,verified"
		});
		if (opts.paginationToken) params.set("pagination_token", opts.paginationToken);
		return this.oauthRequest("GET", `${X_API_BASE}/users/${userId}/mentions?${params}`);
	}
	/**
	* Like a tweet.
	* @param idOrUrl - Tweet ID or full URL
	*/
	async likeTweet(idOrUrl) {
		const tweetId = parseTweetId(idOrUrl);
		const userId = await this.getAuthenticatedUserId();
		return this.oauthRequest("POST", `${X_API_BASE}/users/${userId}/likes`, { tweet_id: tweetId });
	}
	/**
	* Retweet a tweet.
	* @param idOrUrl - Tweet ID or full URL
	*/
	async retweet(idOrUrl) {
		const tweetId = parseTweetId(idOrUrl);
		const userId = await this.getAuthenticatedUserId();
		return this.oauthRequest("POST", `${X_API_BASE}/users/${userId}/retweets`, { tweet_id: tweetId });
	}
	/**
	* Get your bookmarks.
	*/
	async getBookmarks(opts = {}) {
		const userId = await this.getAuthenticatedUserId();
		const maxResults = Math.max(1, Math.min(opts.maxResults ?? 10, 100));
		const params = new URLSearchParams({
			max_results: maxResults.toString(),
			"tweet.fields": "created_at,public_metrics,author_id,conversation_id,entities,lang,note_tweet",
			"expansions": "author_id,attachments.media_keys",
			"user.fields": "name,username,verified,profile_image_url",
			"media.fields": "url,preview_image_url,type"
		});
		if (opts.paginationToken) params.set("pagination_token", opts.paginationToken);
		return this.oauthRequest("GET", `${X_API_BASE}/users/${userId}/bookmarks?${params}`);
	}
	/**
	* Bookmark a tweet.
	* @param idOrUrl - Tweet ID or full URL
	*/
	async bookmarkTweet(idOrUrl) {
		const tweetId = parseTweetId(idOrUrl);
		const userId = await this.getAuthenticatedUserId();
		return this.oauthRequest("POST", `${X_API_BASE}/users/${userId}/bookmarks`, { tweet_id: tweetId });
	}
	/**
	* Remove a bookmark.
	* @param idOrUrl - Tweet ID or full URL
	*/
	async unbookmarkTweet(idOrUrl) {
		const tweetId = parseTweetId(idOrUrl);
		const userId = await this.getAuthenticatedUserId();
		return this.oauthRequest("DELETE", `${X_API_BASE}/users/${userId}/bookmarks/${tweetId}`);
	}
	/**
	* Unlike a tweet.
	* @param idOrUrl - Tweet ID or full URL
	*/
	async unlikeTweet(idOrUrl) {
		const tweetId = parseTweetId(idOrUrl);
		const userId = await this.getAuthenticatedUserId();
		return this.oauthRequest("DELETE", `${X_API_BASE}/users/${userId}/likes/${tweetId}`);
	}
	/**
	* Undo a retweet.
	* @param idOrUrl - Tweet ID or full URL
	*/
	async unretweet(idOrUrl) {
		const tweetId = parseTweetId(idOrUrl);
		const userId = await this.getAuthenticatedUserId();
		return this.oauthRequest("DELETE", `${X_API_BASE}/users/${userId}/retweets/${tweetId}`);
	}
	/**
	* Get users who liked a tweet.
	* @param idOrUrl - Tweet ID or full URL
	*/
	async getLikingUsers(idOrUrl, opts = {}) {
		const id = parseTweetId(idOrUrl);
		const maxResults = Math.max(1, Math.min(opts.maxResults ?? 100, 100));
		const params = new URLSearchParams({
			max_results: maxResults.toString(),
			"user.fields": "created_at,description,public_metrics,verified,profile_image_url"
		});
		if (opts.paginationToken) params.set("pagination_token", opts.paginationToken);
		return this.bearerGet(`${X_API_BASE}/tweets/${id}/liking_users?${params}`);
	}
	/**
	* Get users who retweeted a tweet.
	* @param idOrUrl - Tweet ID or full URL
	*/
	async getRetweetedBy(idOrUrl, opts = {}) {
		const id = parseTweetId(idOrUrl);
		const maxResults = Math.max(1, Math.min(opts.maxResults ?? 100, 100));
		const params = new URLSearchParams({
			max_results: maxResults.toString(),
			"user.fields": "created_at,description,public_metrics,verified,profile_image_url"
		});
		if (opts.paginationToken) params.set("pagination_token", opts.paginationToken);
		return this.bearerGet(`${X_API_BASE}/tweets/${id}/retweeted_by?${params}`);
	}
	/**
	* Get quote tweets for a tweet.
	* @param idOrUrl - Tweet ID or full URL
	*/
	async getQuoteTweets(idOrUrl, opts = {}) {
		const id = parseTweetId(idOrUrl);
		const maxResults = Math.max(10, Math.min(opts.maxResults ?? 10, 100));
		const params = new URLSearchParams({
			max_results: maxResults.toString(),
			"tweet.fields": "created_at,public_metrics,author_id,entities,note_tweet",
			"expansions": "author_id",
			"user.fields": "name,username,verified"
		});
		if (opts.paginationToken) params.set("pagination_token", opts.paginationToken);
		return this.bearerGet(`${X_API_BASE}/tweets/${id}/quote_tweets?${params}`);
	}
	/**
	* Get posts a user has liked.
	* @param username - Username (with or without @)
	*/
	async getLikedTweets(username, opts = {}) {
		const userId = (await this.getUser(username)).data.id;
		const maxResults = Math.max(10, Math.min(opts.maxResults ?? 10, 100));
		const params = new URLSearchParams({
			max_results: maxResults.toString(),
			"tweet.fields": "created_at,public_metrics,author_id,entities,note_tweet",
			"expansions": "author_id",
			"user.fields": "name,username,verified"
		});
		if (opts.paginationToken) params.set("pagination_token", opts.paginationToken);
		return this.bearerGet(`${X_API_BASE}/users/${userId}/liked_tweets?${params}`);
	}
	/**
	* Follow a user.
	* @param username - Username (with or without @)
	*/
	async followUser(username) {
		const targetId = (await this.getUser(username)).data.id;
		const userId = await this.getAuthenticatedUserId();
		return this.oauthRequest("POST", `${X_API_BASE}/users/${userId}/following`, { target_user_id: targetId });
	}
	/**
	* Unfollow a user.
	* @param username - Username (with or without @)
	*/
	async unfollowUser(username) {
		const targetId = (await this.getUser(username)).data.id;
		const userId = await this.getAuthenticatedUserId();
		return this.oauthRequest("DELETE", `${X_API_BASE}/users/${userId}/following/${targetId}`);
	}
	/**
	* Block a user.
	* @param username - Username (with or without @)
	*/
	async blockUser(username) {
		const targetId = (await this.getUser(username)).data.id;
		const userId = await this.getAuthenticatedUserId();
		return this.oauthRequest("POST", `${X_API_BASE}/users/${userId}/blocking`, { target_user_id: targetId });
	}
	/**
	* Unblock a user.
	* @param username - Username (with or without @)
	*/
	async unblockUser(username) {
		const targetId = (await this.getUser(username)).data.id;
		const userId = await this.getAuthenticatedUserId();
		return this.oauthRequest("DELETE", `${X_API_BASE}/users/${userId}/blocking/${targetId}`);
	}
	/**
	* Get users you have blocked.
	*/
	async getBlockedUsers(opts = {}) {
		const userId = await this.getAuthenticatedUserId();
		const maxResults = Math.max(1, Math.min(opts.maxResults ?? 100, 1e3));
		const params = new URLSearchParams({
			max_results: maxResults.toString(),
			"user.fields": "created_at,description,public_metrics,verified,profile_image_url"
		});
		if (opts.paginationToken) params.set("pagination_token", opts.paginationToken);
		return this.oauthRequest("GET", `${X_API_BASE}/users/${userId}/blocking?${params}`);
	}
	/**
	* Mute a user.
	* @param username - Username (with or without @)
	*/
	async muteUser(username) {
		const targetId = (await this.getUser(username)).data.id;
		const userId = await this.getAuthenticatedUserId();
		return this.oauthRequest("POST", `${X_API_BASE}/users/${userId}/muting`, { target_user_id: targetId });
	}
	/**
	* Unmute a user.
	* @param username - Username (with or without @)
	*/
	async unmuteUser(username) {
		const targetId = (await this.getUser(username)).data.id;
		const userId = await this.getAuthenticatedUserId();
		return this.oauthRequest("DELETE", `${X_API_BASE}/users/${userId}/muting/${targetId}`);
	}
	/**
	* Get users you have muted.
	*/
	async getMutedUsers(opts = {}) {
		const userId = await this.getAuthenticatedUserId();
		const maxResults = Math.max(1, Math.min(opts.maxResults ?? 100, 1e3));
		const params = new URLSearchParams({
			max_results: maxResults.toString(),
			"user.fields": "created_at,description,public_metrics,verified,profile_image_url"
		});
		if (opts.paginationToken) params.set("pagination_token", opts.paginationToken);
		return this.oauthRequest("GET", `${X_API_BASE}/users/${userId}/muting?${params}`);
	}
	/**
	* Get the authenticated user's full profile.
	*/
	async getMyProfile() {
		return this.oauthRequest("GET", `${X_API_BASE}/users/me?user.fields=created_at,description,public_metrics,verified,profile_image_url,url,location,pinned_tweet_id`);
	}
	/**
	* Look up multiple users by usernames.
	* @param usernames - Array of usernames (with or without @, max 100)
	*/
	async getUsersByUsernames(usernames) {
		const names = usernames.map(stripAt).join(",");
		const params = new URLSearchParams({
			usernames: names,
			"user.fields": "created_at,description,public_metrics,verified,profile_image_url,url,location"
		});
		return this.bearerGet(`${X_API_BASE}/users/by?${params}`);
	}
	/**
	* Look up multiple users by IDs.
	* @param ids - Array of user IDs (max 100)
	*/
	async getUsersByIds(ids) {
		const params = new URLSearchParams({
			ids: ids.join(","),
			"user.fields": "created_at,description,public_metrics,verified,profile_image_url,url,location"
		});
		return this.bearerGet(`${X_API_BASE}/users?${params}`);
	}
	/**
	* Search for users by keyword.
	* @param query - Search query
	*/
	async searchUsers(query, opts = {}) {
		const maxResults = Math.max(1, Math.min(opts.maxResults ?? 20, 100));
		const params = new URLSearchParams({
			query,
			max_results: maxResults.toString(),
			"user.fields": "created_at,description,public_metrics,verified,profile_image_url,url,location"
		});
		if (opts.paginationToken) params.set("next_token", opts.paginationToken);
		return this.bearerGet(`${X_API_BASE}/users/search?${params}`);
	}
	/**
	* Get the authenticated user's home timeline (reverse chronological).
	*/
	async getHomeTimeline(opts = {}) {
		const userId = await this.getAuthenticatedUserId();
		const maxResults = Math.max(1, Math.min(opts.maxResults ?? 20, 100));
		const params = new URLSearchParams({
			max_results: maxResults.toString(),
			"tweet.fields": "created_at,public_metrics,author_id,conversation_id,entities,lang,note_tweet",
			"expansions": "author_id,attachments.media_keys",
			"user.fields": "name,username,verified,profile_image_url",
			"media.fields": "url,preview_image_url,type"
		});
		if (opts.paginationToken) params.set("pagination_token", opts.paginationToken);
		return this.oauthRequest("GET", `${X_API_BASE}/users/${userId}/reverse_chronological_timeline?${params}`);
	}
	/**
	* Get the full reply thread for a tweet (using search by conversation_id).
	* @param idOrUrl - Tweet ID or URL of the root tweet
	*/
	async getConversation(idOrUrl, opts = {}) {
		const rootTweet = await this.getTweet(idOrUrl);
		const conversationId = rootTweet.data.conversation_id || rootTweet.data.id;
		return this.searchTweets({
			query: `conversation_id:${conversationId}`,
			maxResults: opts.maxResults ?? 100,
			paginationToken: opts.paginationToken
		});
	}
	/**
	* Post a multi-tweet thread in one call.
	*
	* @example
	* ```typescript
	* const thread = await x.postThread([
	*   { text: '1/ Announcing $MYTKN on @clawnch!' },
	*   { text: '2/ We built this for the community.' },
	*   { text: '3/ Trade now on Base.' },
	* ]);
	* console.log(thread.urls); // URLs for each tweet
	* ```
	*/
	async postThread(tweets) {
		if (tweets.length === 0) throw new ClawnXError("Thread must have at least one tweet", 0);
		if (tweets.length > 25) throw new ClawnXError("Thread cannot exceed 25 tweets", 0);
		const tweetIds = [];
		const urls = [];
		let replyTo;
		for (const tweet of tweets) {
			const opts = {
				text: tweet.text,
				replyTo,
				mediaIds: tweet.mediaIds
			};
			if (!replyTo && tweet.pollOptions) {
				opts.pollOptions = tweet.pollOptions;
				opts.pollDurationMinutes = tweet.pollDurationMinutes;
			}
			const id = (await this.postTweet(opts)).data.id;
			tweetIds.push(id);
			urls.push(`https://x.com/i/status/${id}`);
			replyTo = id;
		}
		return {
			tweetIds,
			urls
		};
	}
	/**
	* Create a new list.
	*/
	async createList(opts) {
		const body = { name: opts.name };
		if (opts.description) body.description = opts.description;
		if (opts.private !== void 0) body.private = opts.private;
		return this.oauthRequest("POST", `${X_API_BASE}/lists`, body);
	}
	/**
	* Delete a list you own.
	* @param listId - List ID
	*/
	async deleteList(listId) {
		return this.oauthRequest("DELETE", `${X_API_BASE}/lists/${listId}`);
	}
	/**
	* Update a list you own.
	* @param listId - List ID
	*/
	async updateList(listId, opts) {
		const body = {};
		if (opts.name !== void 0) body.name = opts.name;
		if (opts.description !== void 0) body.description = opts.description;
		if (opts.private !== void 0) body.private = opts.private;
		return this.oauthRequest("PUT", `${X_API_BASE}/lists/${listId}`, body);
	}
	/**
	* Get a list by ID.
	* @param listId - List ID
	*/
	async getList(listId) {
		return this.bearerGet(`${X_API_BASE}/lists/${listId}?list.fields=created_at,follower_count,member_count,owner_id,description,private`);
	}
	/**
	* Get lists owned by a user.
	* @param username - Username (with or without @)
	*/
	async getUserLists(username, opts = {}) {
		const userId = (await this.getUser(username)).data.id;
		const maxResults = Math.max(1, Math.min(opts.maxResults ?? 100, 100));
		const params = new URLSearchParams({
			max_results: maxResults.toString(),
			"list.fields": "created_at,follower_count,member_count,owner_id,description,private"
		});
		if (opts.paginationToken) params.set("pagination_token", opts.paginationToken);
		return this.bearerGet(`${X_API_BASE}/users/${userId}/owned_lists?${params}`);
	}
	/**
	* Add a user to a list.
	* @param listId - List ID
	* @param username - Username to add (with or without @)
	*/
	async addListMember(listId, username) {
		const targetData = await this.getUser(username);
		return this.oauthRequest("POST", `${X_API_BASE}/lists/${listId}/members`, { user_id: targetData.data.id });
	}
	/**
	* Remove a user from a list.
	* @param listId - List ID
	* @param username - Username to remove (with or without @)
	*/
	async removeListMember(listId, username) {
		const targetData = await this.getUser(username);
		return this.oauthRequest("DELETE", `${X_API_BASE}/lists/${listId}/members/${targetData.data.id}`);
	}
	/**
	* Get members of a list.
	* @param listId - List ID
	*/
	async getListMembers(listId, opts = {}) {
		const maxResults = Math.max(1, Math.min(opts.maxResults ?? 100, 100));
		const params = new URLSearchParams({
			max_results: maxResults.toString(),
			"user.fields": "created_at,description,public_metrics,verified,profile_image_url"
		});
		if (opts.paginationToken) params.set("pagination_token", opts.paginationToken);
		return this.bearerGet(`${X_API_BASE}/lists/${listId}/members?${params}`);
	}
	/**
	* Get tweets from a list.
	* @param listId - List ID
	*/
	async getListTweets(listId, opts = {}) {
		const maxResults = Math.max(1, Math.min(opts.maxResults ?? 25, 100));
		const params = new URLSearchParams({
			max_results: maxResults.toString(),
			"tweet.fields": "created_at,public_metrics,author_id,entities,lang,note_tweet",
			"expansions": "author_id",
			"user.fields": "name,username,verified"
		});
		if (opts.paginationToken) params.set("pagination_token", opts.paginationToken);
		return this.bearerGet(`${X_API_BASE}/lists/${listId}/tweets?${params}`);
	}
	/**
	* Send a DM to a user by username.
	* @param username - Recipient username (with or without @)
	* @param opts - Message options (text, optional mediaId)
	*/
	async sendDM(username, opts) {
		const targetData = await this.getUser(username);
		const body = { text: opts.text };
		if (opts.mediaId) body.attachments = [{ media_id: opts.mediaId }];
		return this.oauthRequest("POST", `${X_API_BASE}/dm_conversations/with/${targetData.data.id}/messages`, body);
	}
	/**
	* Send a DM to an existing conversation by conversation ID.
	* @param conversationId - DM conversation ID
	* @param opts - Message options
	*/
	async sendDMToConversation(conversationId, opts) {
		const body = { text: opts.text };
		if (opts.mediaId) body.attachments = [{ media_id: opts.mediaId }];
		return this.oauthRequest("POST", `${X_API_BASE}/dm_conversations/${conversationId}/messages`, body);
	}
	/**
	* Get recent DM events across all conversations.
	*/
	async getDMEvents(opts = {}) {
		const maxResults = Math.max(1, Math.min(opts.maxResults ?? 20, 100));
		const params = new URLSearchParams({
			max_results: maxResults.toString(),
			"dm_event.fields": "id,event_type,text,sender_id,dm_conversation_id,created_at,attachments,referenced_tweets"
		});
		if (opts.paginationToken) params.set("pagination_token", opts.paginationToken);
		return this.oauthRequest("GET", `${X_API_BASE}/dm_events?${params}`);
	}
	/**
	* Get DM events for a specific conversation.
	* @param conversationId - DM conversation ID
	*/
	async getDMConversation(conversationId, opts = {}) {
		const maxResults = Math.max(1, Math.min(opts.maxResults ?? 20, 100));
		const params = new URLSearchParams({
			max_results: maxResults.toString(),
			"dm_event.fields": "id,event_type,text,sender_id,dm_conversation_id,created_at,attachments,referenced_tweets"
		});
		if (opts.paginationToken) params.set("pagination_token", opts.paginationToken);
		return this.oauthRequest("GET", `${X_API_BASE}/dm_conversations/${conversationId}/dm_events?${params}`);
	}
	/**
	* Upload media (image, GIF, or video) for attachment to tweets.
	*
	* Uses the v1.1 upload endpoint (upload.x.com) which is the standard
	* path for media uploads even with API v2 tweets.
	*
	* @param mediaData - Base64-encoded media data or Buffer
	* @param mimeType - MIME type (e.g. 'image/png', 'image/jpeg', 'image/gif', 'video/mp4')
	* @returns MediaUploadResult with media_id_string to pass to postTweet
	*
	* @example
	* ```typescript
	* const media = await x.uploadMedia(imageBuffer, 'image/png');
	* await x.postTweet({ text: 'Check this out!', mediaIds: [media.media_id_string] });
	* ```
	*/
	async uploadMedia(mediaData, mimeType, category) {
		const UPLOAD_BASE = "https://upload.x.com/1.1";
		const base64Data = Buffer.isBuffer(mediaData) ? mediaData.toString("base64") : mediaData;
		const totalBytes = Buffer.from(base64Data, "base64").length;
		let mediaCategory = category ?? "tweet_image";
		if (!category) {
			if (mimeType.startsWith("video/")) mediaCategory = "tweet_video";
			else if (mimeType === "image/gif") mediaCategory = "tweet_gif";
		}
		const mediaId = (await this.oauthRequest("POST", `${UPLOAD_BASE}/media/upload.json?command=INIT&total_bytes=${totalBytes}&media_type=${encodeURIComponent(mimeType)}&media_category=${mediaCategory}`)).media_id_string;
		const CHUNK_SIZE = 5 * 1024 * 1024;
		const rawBuffer = Buffer.from(base64Data, "base64");
		const totalChunks = Math.ceil(rawBuffer.length / CHUNK_SIZE);
		for (let segment = 0; segment < totalChunks; segment++) {
			const start = segment * CHUNK_SIZE;
			const end = Math.min(start + CHUNK_SIZE, rawBuffer.length);
			const chunkBase64 = rawBuffer.subarray(start, end).toString("base64");
			const boundary = `----ClawnX${Date.now()}${segment}`;
			const authHeader = generateOAuthHeader("POST", `${UPLOAD_BASE}/media/upload.json`, this.creds);
			const bodyParts = [
				`--${boundary}\r\nContent-Disposition: form-data; name="command"\r\n\r\nAPPEND`,
				`--${boundary}\r\nContent-Disposition: form-data; name="media_id"\r\n\r\n${mediaId}`,
				`--${boundary}\r\nContent-Disposition: form-data; name="segment_index"\r\n\r\n${segment}`,
				`--${boundary}\r\nContent-Disposition: form-data; name="media_data"\r\n\r\n${chunkBase64}`,
				`--${boundary}--`
			].join("\r\n");
			const ctrl = new AbortController();
			const timer = setTimeout(() => ctrl.abort(), 12e4);
			try {
				const appendResp = await fetch(`${UPLOAD_BASE}/media/upload.json`, {
					method: "POST",
					headers: {
						Authorization: authHeader,
						"Content-Type": `multipart/form-data; boundary=${boundary}`
					},
					body: bodyParts,
					signal: ctrl.signal
				});
				if (!appendResp.ok && appendResp.status !== 204) {
					const errText = await appendResp.text();
					throw new ClawnXError(`Media APPEND failed (chunk ${segment + 1}/${totalChunks}): ${errText}`, appendResp.status);
				}
			} finally {
				clearTimeout(timer);
			}
		}
		const finalResult = await this.oauthRequest("POST", `${UPLOAD_BASE}/media/upload.json?command=FINALIZE&media_id=${mediaId}`);
		if (finalResult.processing_info) {
			let processing = finalResult.processing_info;
			while (processing.state === "pending" || processing.state === "in_progress") {
				const waitSecs = processing.check_after_secs ?? 5;
				await new Promise((r) => setTimeout(r, waitSecs * 1e3));
				processing = (await this.oauthRequest("GET", `${UPLOAD_BASE}/media/upload.json?command=STATUS&media_id=${mediaId}`)).processing_info ?? { state: "succeeded" };
			}
			if (processing.state === "failed") throw new ClawnXError(`Media processing failed: ${processing.error?.message ?? "unknown"}`, 0);
		}
		return finalResult;
	}
	/**
	* Fetch media from a URL, upload it, and wait for processing.
	*
	* Combines three steps into one call:
	* 1. Fetch media bytes from the URL
	* 2. Upload via uploadMedia()
	* 3. Poll for processing completion (videos/GIFs)
	*
	* @example
	* ```typescript
	* const media = await x.uploadMediaFromUrl({ url: 'https://example.com/chart.png' });
	* await x.postTweet({ text: 'Daily chart', mediaIds: [media.media_id_string] });
	* ```
	*/
	async uploadMediaFromUrl(opts) {
		const response = await fetch(opts.url);
		if (!response.ok) throw new ClawnXError(`Failed to fetch media from ${opts.url}: ${response.status}`, response.status);
		const buffer = Buffer.from(await response.arrayBuffer());
		const mimeType = opts.mimeType ?? response.headers.get("content-type") ?? "image/png";
		return this.uploadMedia(buffer, mimeType, opts.mediaCategory);
	}
	/**
	* Connect to the X API v2 filtered stream and collect matching tweets.
	*
	* You must set stream rules first via `setStreamRules()`.
	* The stream runs for `durationMs` or until `maxTweets` are collected.
	*
	* @example
	* ```typescript
	* await x.setStreamRules([{ value: '$MYTKN', tag: 'token' }]);
	* const tweets = await x.streamFiltered({ durationMs: 30_000, maxTweets: 50 });
	* console.log(`Collected ${tweets.length} matching tweets`);
	* ```
	*/
	async streamFiltered(opts = {}) {
		const durationMs = opts.durationMs ?? 3e4;
		const maxTweets = opts.maxTweets ?? 50;
		const url = `${X_API_BASE}/tweets/search/stream?tweet.fields=created_at,public_metrics,author_id&expansions=author_id&user.fields=username`;
		const controller = new AbortController();
		this._streamController = controller;
		const timeout = setTimeout(() => controller.abort(), durationMs);
		const tweets = [];
		try {
			const response = await fetch(url, {
				headers: { Authorization: `Bearer ${this.creds.bearerToken}` },
				signal: controller.signal
			});
			if (!response.ok) {
				const errText = await response.text();
				throw new ClawnXError(`Stream connection failed (${response.status}): ${errText}`, response.status);
			}
			if (!response.body) throw new ClawnXError("No response body for stream", 0);
			const reader = response.body.getReader();
			const decoder = new TextDecoder();
			let buffer = "";
			while (tweets.length < maxTweets) {
				const { done, value } = await reader.read();
				if (done) break;
				buffer += decoder.decode(value, { stream: true });
				const lines = buffer.split("\r\n");
				buffer = lines.pop() ?? "";
				for (const line of lines) {
					if (!line.trim()) continue;
					try {
						const parsed = JSON.parse(line);
						if (parsed.data) tweets.push(parsed.data);
					} catch {}
				}
			}
		} catch (err) {
			if (err.name !== "AbortError") throw err;
		} finally {
			clearTimeout(timeout);
			this._streamController = null;
		}
		return tweets;
	}
	/**
	* Stop the active filtered stream (if any).
	* @returns true if a stream was stopped, false if none was active
	*/
	stopStream() {
		if (this._streamController) {
			this._streamController.abort();
			this._streamController = null;
			return true;
		}
		return false;
	}
	/**
	* Replace all stream filter rules with new ones.
	* Deletes any existing rules first, then adds the new set.
	*
	* @example
	* ```typescript
	* await x.setStreamRules([
	*   { value: '$MYTKN', tag: 'token' },
	*   { value: 'from:clawnch', tag: 'official' },
	* ]);
	* ```
	*/
	async setStreamRules(rules) {
		const rulesUrl = `${X_API_BASE}/tweets/search/stream/rules`;
		const authHeader = `Bearer ${this.creds.bearerToken}`;
		const timeout = 3e4;
		const getCtrl = new AbortController();
		const getTimer = setTimeout(() => getCtrl.abort(), timeout);
		try {
			const getResp = await fetch(rulesUrl, {
				headers: { Authorization: authHeader },
				signal: getCtrl.signal
			});
			if (!getResp.ok) throw new ClawnXError(`Failed to get stream rules: ${getResp.status}`, getResp.status);
			const existing = await getResp.json();
			if (existing.data?.length > 0) {
				const ids = existing.data.map((r) => r.id);
				const delCtrl = new AbortController();
				const delTimer = setTimeout(() => delCtrl.abort(), timeout);
				try {
					const delResp = await fetch(rulesUrl, {
						method: "POST",
						headers: {
							Authorization: authHeader,
							"Content-Type": "application/json"
						},
						body: JSON.stringify({ delete: { ids } }),
						signal: delCtrl.signal
					});
					if (!delResp.ok) throw new ClawnXError(`Failed to delete stream rules: ${delResp.status}`, delResp.status);
				} finally {
					clearTimeout(delTimer);
				}
			}
		} finally {
			clearTimeout(getTimer);
		}
		const addCtrl = new AbortController();
		const addTimer = setTimeout(() => addCtrl.abort(), timeout);
		try {
			const addResp = await fetch(rulesUrl, {
				method: "POST",
				headers: {
					Authorization: authHeader,
					"Content-Type": "application/json"
				},
				body: JSON.stringify({ add: rules }),
				signal: addCtrl.signal
			});
			if (!addResp.ok) {
				const body = await addResp.text().catch(() => "");
				throw new ClawnXError(`Failed to add stream rules: ${addResp.status} ${body}`, addResp.status);
			}
			return (await addResp.json()).data ?? [];
		} finally {
			clearTimeout(addTimer);
		}
	}
	/**
	* Get the current stream filter rules.
	*/
	async getStreamRules() {
		const ctrl = new AbortController();
		const timer = setTimeout(() => ctrl.abort(), 3e4);
		try {
			const resp = await fetch(`${X_API_BASE}/tweets/search/stream/rules`, {
				headers: { Authorization: `Bearer ${this.creds.bearerToken}` },
				signal: ctrl.signal
			});
			if (!resp.ok) throw new ClawnXError(`Failed to get stream rules: ${resp.status}`, resp.status);
			return (await resp.json()).data ?? [];
		} finally {
			clearTimeout(timer);
		}
	}
	/**
	* Execute a chain of actions sequentially with data flow between steps.
	*
	* Each step's parameters can use the special value `"PREV_TWEET_ID"` which
	* is replaced with the tweet ID produced by the previous step. This enables
	* post-then-like, post-then-retweet, and other compound workflows.
	*
	* @example
	* ```typescript
	* const result = await x.executeChain([
	*   { action: 'postTweet', text: 'Just launched!' },
	*   { action: 'likeTweet', idOrUrl: 'PREV_TWEET_ID' },
	*   { action: 'retweet', idOrUrl: 'PREV_TWEET_ID' },
	* ]);
	* ```
	*/
	async executeChain(steps) {
		if (steps.length === 0) throw new ClawnXError("Chain must have at least one step", 0);
		const results = [];
		let prevTweetId;
		let allSucceeded = true;
		for (let i = 0; i < steps.length; i++) {
			const { action, ...stepParams } = steps[i];
			const resolvedParams = { ...stepParams };
			for (const [key, val] of Object.entries(resolvedParams)) if (val === "PREV_TWEET_ID") {
				if (!prevTweetId) {
					const error = `Step ${i + 1} references PREV_TWEET_ID but no previous step produced a tweet ID`;
					results.push({
						step: i,
						action,
						success: false,
						result: null,
						error
					});
					return {
						success: false,
						steps: results,
						lastTweetId: prevTweetId
					};
				}
				resolvedParams[key] = prevTweetId;
			}
			try {
				const tweetId = await this._executeChainStep(action, resolvedParams);
				results.push({
					step: i,
					action,
					success: true,
					result: `OK`,
					tweetId: tweetId ?? void 0
				});
				if (tweetId) prevTweetId = tweetId;
			} catch (err) {
				allSucceeded = false;
				results.push({
					step: i,
					action,
					success: false,
					result: null,
					error: err instanceof Error ? err.message : String(err)
				});
				break;
			}
		}
		return {
			success: allSucceeded,
			steps: results,
			lastTweetId: prevTweetId
		};
	}
	/**
	* Execute a single chain step and return the tweet ID if one was produced.
	* Maps action names to ClawnX methods.
	*/
	async _executeChainStep(action, params) {
		switch (action) {
			case "postTweet": return (await this.postTweet(params)).data.id;
			case "postThread": return (await this.postThread(params.tweets)).tweetIds[0] ?? null;
			case "deleteTweet":
				await this.deleteTweet(params.idOrUrl);
				return null;
			case "likeTweet":
				await this.likeTweet(params.idOrUrl);
				return null;
			case "unlikeTweet":
				await this.unlikeTweet(params.idOrUrl);
				return null;
			case "retweet":
				await this.retweet(params.idOrUrl);
				return null;
			case "unretweet":
				await this.unretweet(params.idOrUrl);
				return null;
			case "bookmarkTweet":
				await this.bookmarkTweet(params.idOrUrl);
				return null;
			case "unbookmarkTweet":
				await this.unbookmarkTweet(params.idOrUrl);
				return null;
			case "followUser":
				await this.followUser(params.username);
				return null;
			case "unfollowUser":
				await this.unfollowUser(params.username);
				return null;
			case "blockUser":
				await this.blockUser(params.username);
				return null;
			case "unblockUser":
				await this.unblockUser(params.username);
				return null;
			case "muteUser":
				await this.muteUser(params.username);
				return null;
			case "unmuteUser":
				await this.unmuteUser(params.username);
				return null;
			case "sendDM":
				await this.sendDM(params.username, { text: params.text });
				return null;
			case "uploadMediaFromUrl": return (await this.uploadMediaFromUrl({
				url: params.url,
				mimeType: params.mimeType,
				mediaCategory: params.mediaCategory
			})).media_id_string;
			default: throw new ClawnXError(`Unknown chain action: ${action}`, 0);
		}
	}
};
var ClawnXError = class extends Error {
	status;
	constructor(message, status) {
		super(message);
		this.status = status;
		this.name = "ClawnXError";
	}
};
//#endregion
export { ClawnX };

//# sourceMappingURL=dist-DotC-n5F.mjs.map