import crypto from 'crypto';
export interface Env {
TWITTER_CONSUMER_KEY: string;
TWITTER_CONSUMER_SECRET: string;
TWITTER_ACCESS_TOKEN: string;
TWITTER_ACCESS_TOKEN_SECRET: string;
TWITTER_BEARER_TOKEN: string;
}
function generateOAuth1Header(env: Env, method: string, url: string, params: any = {}): string {
const timestamp = Math.floor(Date.now() / 1000).toString();
const nonce = crypto.randomBytes(16).toString('hex');
// OAuth 1.0a parameters
const oauthParams = {
oauth_consumer_key: env.TWITTER_CONSUMER_KEY,
oauth_nonce: nonce,
oauth_signature_method: 'HMAC-SHA1',
oauth_timestamp: timestamp,
oauth_token: env.TWITTER_ACCESS_TOKEN,
oauth_version: '1.0',
...params
};
// Create signature base string
const paramString = Object.keys(oauthParams)
.sort()
.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(oauthParams[key])}`)
.join('&');
const signatureBaseString = [
method.toUpperCase(),
encodeURIComponent(url),
encodeURIComponent(paramString)
].join('&');
// Generate signature
const signingKey = `${encodeURIComponent(env.TWITTER_CONSUMER_SECRET)}&${encodeURIComponent(env.TWITTER_ACCESS_TOKEN_SECRET)}`;
const signature = crypto
.createHmac('sha1', signingKey)
.update(signatureBaseString)
.digest('base64');
// Add signature to OAuth parameters
oauthParams.oauth_signature = signature;
// Create Authorization header
const authHeader = 'OAuth ' + Object.keys(oauthParams)
.map(key => `${encodeURIComponent(key)}="${encodeURIComponent(oauthParams[key])}"`)
.join(', ');
return authHeader;
}
export async function getUserProfile(env: Env, username: string): Promise<any> {
const url = `https://api.twitter.com/2/users/by/username/${username}?user.fields=description,profile_image_url,public_metrics,created_at`;
const method = 'GET';
const response = await fetch(url, {
method: method,
headers: {
'Authorization': `Bearer ${env.TWITTER_BEARER_TOKEN}`,
'Content-Type': 'application/json'
},
});
console.log("π | getUserProfile | response:", response)
if (!response.ok) {
const errorData = await response.json();
throw new Error(`Failed to post tweet: ${response.statusText} - ${JSON.stringify(errorData)}`);
}
return response.json();
}
export async function postTweet(env: Env, text: string): Promise<any> {
const url = 'https://api.twitter.com/2/tweets';
const method = 'POST';
const authHeader = generateOAuth1Header(env, method, url);
const response = await fetch(url, {
method: method,
headers: {
'Authorization': authHeader,
'Content-Type': 'application/json'
},
body: JSON.stringify({ text })
});
console.info("π | postTweet | response:", response)
if (!response.ok) {
const errorData = await response.json();
throw new Error(`Failed to post tweet: ${response.statusText} - ${JSON.stringify(errorData)}`);
}
return response.json();
}
export async function searchTweets(
env: Env,
query: string,
count: number
): Promise<any> {
try {
const url = `https://api.twitter.com/2/tweets/search/recent?query=${encodeURIComponent(query)}&max_results=${count}&expansions=author_id&tweet.fields=public_metrics,created_at&user.fields=username,name,verified`;
const method = 'GET';
const response = await fetch(url, {
method: method,
headers: {
'Authorization': `Bearer ${env.TWITTER_BEARER_TOKEN}`,
'Content-Type': 'application/json'
}
});
console.log("π | response:", response)
if (!response.ok) {
const errorData = await response.json();
throw new Error(`Failed to search tweets: ${response.statusText} - ${JSON.stringify(errorData)}`);
}
const data = await response.json();
// console.info(`Fetched ${data.data.length} tweets for query: "${query}"`);
// const tweets = data.data.map(tweet => ({
// id: tweet.id,
// text: tweet.text,
// authorId: tweet.authorId,
// metrics: tweet.metrics,
// createdAt: tweet.createdAt
// }));
// const users = data.includes?.users ?? [];
return `{ tweets, users }`;
} catch (error) {
console.error(error);
throw new Error(`Failed to search tweets: ${error}`);
}
}