kopia lustrzana https://github.com/transitive-bullshit/chatgpt-api
Merge pull request #698 from transitive-bullshit/feature/update-twitter-nango
Improve Twitter clientpull/700/head
commit
b36cd31f40
|
@ -3,7 +3,7 @@ import pThrottle from 'p-throttle'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
|
|
||||||
import type * as types from './types'
|
import type * as types from './types'
|
||||||
import { handleKnownTwitterErrors } from './utils'
|
import { handleTwitterError } from './utils'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This file contains rate-limited wrappers around all of the core Twitter API
|
* This file contains rate-limited wrappers around all of the core Twitter API
|
||||||
|
@ -14,17 +14,19 @@ import { handleKnownTwitterErrors } from './utils'
|
||||||
* denominator OR vary based on the twitter developer plan you're using. We
|
* denominator OR vary based on the twitter developer plan you're using. We
|
||||||
* chose to go with the latter.
|
* chose to go with the latter.
|
||||||
*
|
*
|
||||||
* @see https://developer.twitter.com/en/docs/twitter-api/rate-limits
|
* @see https://docs.x.com/x-api/fundamentals/rate-limits
|
||||||
*/
|
*/
|
||||||
|
|
||||||
type TwitterApiMethod =
|
type TwitterApiMethod =
|
||||||
| 'createTweet'
|
| 'createTweet'
|
||||||
| 'usersIdMentions'
|
| 'getTweetById'
|
||||||
| 'findTweetById'
|
| 'getTweetsById'
|
||||||
| 'findTweetsById'
|
|
||||||
| 'searchRecentTweets'
|
| 'searchRecentTweets'
|
||||||
| 'findUserById'
|
| 'listTweetMentionsByUserId'
|
||||||
| 'findUserByUsername'
|
| 'listTweetsLikedByUserId'
|
||||||
|
| 'listTweetsByUserId'
|
||||||
|
| 'getUserById'
|
||||||
|
| 'getUserByUsername'
|
||||||
|
|
||||||
const TWENTY_FOUR_HOURS_MS = 24 * 60 * 60 * 1000
|
const TWENTY_FOUR_HOURS_MS = 24 * 60 * 60 * 1000
|
||||||
const FIFTEEN_MINUTES_MS = 15 * 60 * 1000
|
const FIFTEEN_MINUTES_MS = 15 * 60 * 1000
|
||||||
|
@ -44,14 +46,14 @@ const twitterApiRateLimitsByPlan: Record<
|
||||||
// 50 per 24h per app
|
// 50 per 24h per app
|
||||||
createTweet: { limit: 50, interval: TWENTY_FOUR_HOURS_MS },
|
createTweet: { limit: 50, interval: TWENTY_FOUR_HOURS_MS },
|
||||||
|
|
||||||
// TODO: according to the twitter docs, this shouldn't be allowed on the
|
getTweetById: { limit: 1, interval: FIFTEEN_MINUTES_MS },
|
||||||
// free plan, but it seems to work...
|
getTweetsById: { limit: 1, interval: FIFTEEN_MINUTES_MS },
|
||||||
usersIdMentions: { limit: 1, interval: FIFTEEN_MINUTES_MS },
|
|
||||||
findTweetById: { limit: 1, interval: FIFTEEN_MINUTES_MS },
|
|
||||||
findTweetsById: { limit: 1, interval: FIFTEEN_MINUTES_MS },
|
|
||||||
searchRecentTweets: { limit: 1, interval: FIFTEEN_MINUTES_MS },
|
searchRecentTweets: { limit: 1, interval: FIFTEEN_MINUTES_MS },
|
||||||
findUserById: { limit: 1, interval: FIFTEEN_MINUTES_MS },
|
listTweetMentionsByUserId: { limit: 1, interval: FIFTEEN_MINUTES_MS },
|
||||||
findUserByUsername: { limit: 1, interval: FIFTEEN_MINUTES_MS }
|
listTweetsLikedByUserId: { limit: 1, interval: FIFTEEN_MINUTES_MS },
|
||||||
|
listTweetsByUserId: { limit: 1, interval: FIFTEEN_MINUTES_MS },
|
||||||
|
getUserById: { limit: 1, interval: FIFTEEN_MINUTES_MS },
|
||||||
|
getUserByUsername: { limit: 1, interval: FIFTEEN_MINUTES_MS }
|
||||||
},
|
},
|
||||||
|
|
||||||
basic: {
|
basic: {
|
||||||
|
@ -59,23 +61,31 @@ const twitterApiRateLimitsByPlan: Record<
|
||||||
// 1667 per 24h per app
|
// 1667 per 24h per app
|
||||||
createTweet: { limit: 100, interval: TWENTY_FOUR_HOURS_MS },
|
createTweet: { limit: 100, interval: TWENTY_FOUR_HOURS_MS },
|
||||||
|
|
||||||
// https://developer.twitter.com/en/docs/twitter-api/tweets/timelines/api-reference/get-users-id-mentions
|
|
||||||
// TODO: undocumented
|
|
||||||
// 180 per 15m per user
|
|
||||||
// 450 per 15m per app
|
|
||||||
usersIdMentions: { limit: 180, interval: FIFTEEN_MINUTES_MS },
|
|
||||||
|
|
||||||
// 15 per 15m per user
|
// 15 per 15m per user
|
||||||
// 15 per 15m per app
|
// 15 per 15m per app
|
||||||
findTweetById: { limit: 15, interval: FIFTEEN_MINUTES_MS },
|
getTweetById: { limit: 15, interval: FIFTEEN_MINUTES_MS },
|
||||||
findTweetsById: { limit: 15, interval: FIFTEEN_MINUTES_MS },
|
getTweetsById: { limit: 15, interval: FIFTEEN_MINUTES_MS },
|
||||||
|
|
||||||
// 60 per 15m per user
|
// 60 per 15m per user
|
||||||
// 60 per 15m per app
|
// 60 per 15m per app
|
||||||
searchRecentTweets: { limit: 60, interval: FIFTEEN_MINUTES_MS },
|
searchRecentTweets: { limit: 60, interval: FIFTEEN_MINUTES_MS },
|
||||||
|
|
||||||
findUserById: { limit: 100, interval: TWENTY_FOUR_HOURS_MS },
|
// 10 per 15m per user
|
||||||
findUserByUsername: { limit: 100, interval: TWENTY_FOUR_HOURS_MS }
|
// 10 per 15m per app
|
||||||
|
listTweetMentionsByUserId: { limit: 180, interval: FIFTEEN_MINUTES_MS },
|
||||||
|
|
||||||
|
// 5 per 15min per user
|
||||||
|
// 5 per 15min per app
|
||||||
|
listTweetsLikedByUserId: { limit: 5, interval: FIFTEEN_MINUTES_MS },
|
||||||
|
|
||||||
|
// 5 per 15min per user
|
||||||
|
// 10 per 15min per app
|
||||||
|
listTweetsByUserId: { limit: 5, interval: FIFTEEN_MINUTES_MS },
|
||||||
|
|
||||||
|
// 100 per 24h per user
|
||||||
|
// 500 per 24h per app
|
||||||
|
getUserById: { limit: 100, interval: TWENTY_FOUR_HOURS_MS },
|
||||||
|
getUserByUsername: { limit: 100, interval: TWENTY_FOUR_HOURS_MS }
|
||||||
},
|
},
|
||||||
|
|
||||||
pro: {
|
pro: {
|
||||||
|
@ -83,23 +93,32 @@ const twitterApiRateLimitsByPlan: Record<
|
||||||
// 10k per 24h per app
|
// 10k per 24h per app
|
||||||
createTweet: { limit: 100, interval: FIFTEEN_MINUTES_MS },
|
createTweet: { limit: 100, interval: FIFTEEN_MINUTES_MS },
|
||||||
|
|
||||||
// 180 per 15m per user
|
// TODO: why would the per-user rate-limit be more than the per-app one?!
|
||||||
// 450 per 15m per app
|
|
||||||
usersIdMentions: { limit: 180, interval: FIFTEEN_MINUTES_MS },
|
|
||||||
|
|
||||||
// TODO: why would the per-user rate-limit be less than the per-app one?!
|
|
||||||
// 900 per 15m per user
|
// 900 per 15m per user
|
||||||
// 450 per 15m per app
|
// 450 per 15m per app
|
||||||
findTweetById: { limit: 450, interval: FIFTEEN_MINUTES_MS },
|
getTweetById: { limit: 450, interval: FIFTEEN_MINUTES_MS },
|
||||||
findTweetsById: { limit: 450, interval: FIFTEEN_MINUTES_MS },
|
getTweetsById: { limit: 450, interval: FIFTEEN_MINUTES_MS },
|
||||||
|
|
||||||
// TODO: why would the per-user rate-limit be less than the per-app one?!
|
// 300 per 15m per user
|
||||||
// 456 per 15m per user
|
// 450 per 15m per app
|
||||||
// 300 per 15m per app
|
|
||||||
searchRecentTweets: { limit: 300, interval: FIFTEEN_MINUTES_MS },
|
searchRecentTweets: { limit: 300, interval: FIFTEEN_MINUTES_MS },
|
||||||
|
|
||||||
findUserById: { limit: 300, interval: FIFTEEN_MINUTES_MS },
|
// 300 per 15m per user
|
||||||
findUserByUsername: { limit: 300, interval: FIFTEEN_MINUTES_MS }
|
// 450 per 15m per app
|
||||||
|
listTweetMentionsByUserId: { limit: 300, interval: FIFTEEN_MINUTES_MS },
|
||||||
|
|
||||||
|
// 75 per 15min per user
|
||||||
|
// 75 per 15min per app
|
||||||
|
listTweetsLikedByUserId: { limit: 75, interval: FIFTEEN_MINUTES_MS },
|
||||||
|
|
||||||
|
// 900 per 15min per user
|
||||||
|
// 1500 per 15min per app
|
||||||
|
listTweetsByUserId: { limit: 900, interval: FIFTEEN_MINUTES_MS },
|
||||||
|
|
||||||
|
// 900 per 15m per user
|
||||||
|
// 300 per 15m per app
|
||||||
|
getUserById: { limit: 300, interval: FIFTEEN_MINUTES_MS },
|
||||||
|
getUserByUsername: { limit: 300, interval: FIFTEEN_MINUTES_MS }
|
||||||
},
|
},
|
||||||
|
|
||||||
enterprise: {
|
enterprise: {
|
||||||
|
@ -107,15 +126,33 @@ const twitterApiRateLimitsByPlan: Record<
|
||||||
// completely customizable, but it's still useful to define rate limits
|
// completely customizable, but it's still useful to define rate limits
|
||||||
// for robustness. These values just 10x those of the pro plan.
|
// for robustness. These values just 10x those of the pro plan.
|
||||||
createTweet: { limit: 1000, interval: FIFTEEN_MINUTES_MS },
|
createTweet: { limit: 1000, interval: FIFTEEN_MINUTES_MS },
|
||||||
usersIdMentions: { limit: 1800, interval: FIFTEEN_MINUTES_MS },
|
|
||||||
findTweetById: { limit: 4500, interval: FIFTEEN_MINUTES_MS },
|
getTweetById: { limit: 4500, interval: FIFTEEN_MINUTES_MS },
|
||||||
findTweetsById: { limit: 4500, interval: FIFTEEN_MINUTES_MS },
|
getTweetsById: { limit: 4500, interval: FIFTEEN_MINUTES_MS },
|
||||||
|
|
||||||
searchRecentTweets: { limit: 3000, interval: FIFTEEN_MINUTES_MS },
|
searchRecentTweets: { limit: 3000, interval: FIFTEEN_MINUTES_MS },
|
||||||
findUserById: { limit: 3000, interval: FIFTEEN_MINUTES_MS },
|
listTweetMentionsByUserId: { limit: 3000, interval: FIFTEEN_MINUTES_MS },
|
||||||
findUserByUsername: { limit: 3000, interval: FIFTEEN_MINUTES_MS }
|
listTweetsLikedByUserId: { limit: 750, interval: FIFTEEN_MINUTES_MS },
|
||||||
|
listTweetsByUserId: { limit: 9000, interval: FIFTEEN_MINUTES_MS },
|
||||||
|
|
||||||
|
getUserById: { limit: 3000, interval: FIFTEEN_MINUTES_MS },
|
||||||
|
getUserByUsername: { limit: 3000, interval: FIFTEEN_MINUTES_MS }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Twitter API v2 client wrapper with rate-limited methods and `@aiFunction`
|
||||||
|
* compatibility.
|
||||||
|
*
|
||||||
|
* Rate limits differ by plan, so make sure theh `twitterApiPlan` parameter is
|
||||||
|
* properly set to maximize your rate-limit usage.
|
||||||
|
*
|
||||||
|
* @note This class does not handle distributed rate-limits. It assumes a
|
||||||
|
* single, local client is accessing the API at a time, which is a better fit
|
||||||
|
* for serverful environments.
|
||||||
|
*
|
||||||
|
* @see https://docs.x.com/x-api/fundamentals/rate-limits
|
||||||
|
*/
|
||||||
export class TwitterClient extends AIFunctionsProvider {
|
export class TwitterClient extends AIFunctionsProvider {
|
||||||
readonly client: types.TwitterV2Client
|
readonly client: types.TwitterV2Client
|
||||||
readonly twitterApiPlan: types.TwitterApiPlan
|
readonly twitterApiPlan: types.TwitterApiPlan
|
||||||
|
@ -143,38 +180,59 @@ export class TwitterClient extends AIFunctionsProvider {
|
||||||
assert(twitterApiRateLimits, `Invalid twitter api plan: ${twitterApiPlan}`)
|
assert(twitterApiRateLimits, `Invalid twitter api plan: ${twitterApiPlan}`)
|
||||||
|
|
||||||
const createTweetThrottle = pThrottle(twitterApiRateLimits.createTweet)
|
const createTweetThrottle = pThrottle(twitterApiRateLimits.createTweet)
|
||||||
const findTweetByIdThrottle = pThrottle(twitterApiRateLimits.findTweetById)
|
const getTweetByIdThrottle = pThrottle(twitterApiRateLimits.getTweetById)
|
||||||
const findTweetsByIdThrottle = pThrottle(
|
const getTweetsByIdThrottle = pThrottle(twitterApiRateLimits.getTweetsById)
|
||||||
twitterApiRateLimits.findTweetsById
|
|
||||||
)
|
|
||||||
const searchRecentTweetsThrottle = pThrottle(
|
const searchRecentTweetsThrottle = pThrottle(
|
||||||
twitterApiRateLimits.searchRecentTweets
|
twitterApiRateLimits.searchRecentTweets
|
||||||
)
|
)
|
||||||
const findUserByIdThrottle = pThrottle(twitterApiRateLimits.findUserById)
|
const listTweetMentionsByUserIdThrottle = pThrottle(
|
||||||
const findUserByUsernameThrottle = pThrottle(
|
twitterApiRateLimits.listTweetMentionsByUserId
|
||||||
twitterApiRateLimits.findUserByUsername
|
)
|
||||||
|
const listTweetsLikedByUserIdThrottle = pThrottle(
|
||||||
|
twitterApiRateLimits.listTweetsLikedByUserId
|
||||||
|
)
|
||||||
|
const listTweetsByUserIdThrottle = pThrottle(
|
||||||
|
twitterApiRateLimits.listTweetsByUserId
|
||||||
|
)
|
||||||
|
const getUserByIdThrottle = pThrottle(twitterApiRateLimits.getUserById)
|
||||||
|
const getUserByUsernameThrottle = pThrottle(
|
||||||
|
twitterApiRateLimits.getUserByUsername
|
||||||
)
|
)
|
||||||
|
|
||||||
this._createTweet = createTweetThrottle(createTweetImpl(this.client))
|
this._createTweet = createTweetThrottle(createTweetImpl(this.client))
|
||||||
this._findTweetById = findTweetByIdThrottle(findTweetByIdImpl(this.client))
|
this._getTweetById = getTweetByIdThrottle(getTweetByIdImpl(this.client))
|
||||||
this._findTweetsById = findTweetsByIdThrottle(
|
this._getTweetsById = getTweetsByIdThrottle(getTweetsByIdImpl(this.client))
|
||||||
findTweetsByIdImpl(this.client)
|
|
||||||
)
|
|
||||||
this._searchRecentTweets = searchRecentTweetsThrottle(
|
this._searchRecentTweets = searchRecentTweetsThrottle(
|
||||||
searchRecentTweetsImpl(this.client)
|
searchRecentTweetsImpl(this.client)
|
||||||
)
|
)
|
||||||
this._findUserById = findUserByIdThrottle(findUserByIdImpl(this.client))
|
this._listTweetMentionsByUserId = listTweetMentionsByUserIdThrottle(
|
||||||
this._findUserByUsername = findUserByUsernameThrottle(
|
listTweetMentionsByUserIdImpl(this.client)
|
||||||
findUserByUsernameImpl(this.client)
|
)
|
||||||
|
this._listTweetsLikedByUserId = listTweetsLikedByUserIdThrottle(
|
||||||
|
listTweetsLikedByUserIdImpl(this.client)
|
||||||
|
)
|
||||||
|
this._listTweetsByUserId = listTweetsByUserIdThrottle(
|
||||||
|
listTweetsByUserIdImpl(this.client)
|
||||||
|
)
|
||||||
|
this._getUserById = getUserByIdThrottle(getUserByIdImpl(this.client))
|
||||||
|
this._getUserByUsername = getUserByUsernameThrottle(
|
||||||
|
getUserByUsernameImpl(this.client)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected _createTweet: ReturnType<typeof createTweetImpl>
|
protected _createTweet: ReturnType<typeof createTweetImpl>
|
||||||
protected _findTweetById: ReturnType<typeof findTweetByIdImpl>
|
protected _getTweetById: ReturnType<typeof getTweetByIdImpl>
|
||||||
protected _findTweetsById: ReturnType<typeof findTweetsByIdImpl>
|
protected _getTweetsById: ReturnType<typeof getTweetsByIdImpl>
|
||||||
protected _searchRecentTweets: ReturnType<typeof searchRecentTweetsImpl>
|
protected _searchRecentTweets: ReturnType<typeof searchRecentTweetsImpl>
|
||||||
protected _findUserById: ReturnType<typeof findUserByIdImpl>
|
protected _listTweetMentionsByUserId: ReturnType<
|
||||||
protected _findUserByUsername: ReturnType<typeof findUserByUsernameImpl>
|
typeof listTweetMentionsByUserIdImpl
|
||||||
|
>
|
||||||
|
protected _listTweetsLikedByUserId: ReturnType<
|
||||||
|
typeof listTweetsLikedByUserIdImpl
|
||||||
|
>
|
||||||
|
protected _listTweetsByUserId: ReturnType<typeof listTweetsByUserIdImpl>
|
||||||
|
protected _getUserById: ReturnType<typeof getUserByIdImpl>
|
||||||
|
protected _getUserByUsername: ReturnType<typeof getUserByUsernameImpl>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new tweet
|
* Creates a new tweet
|
||||||
|
@ -183,7 +241,7 @@ export class TwitterClient extends AIFunctionsProvider {
|
||||||
name: 'create_tweet',
|
name: 'create_tweet',
|
||||||
description: 'Creates a new tweet',
|
description: 'Creates a new tweet',
|
||||||
inputSchema: z.object({
|
inputSchema: z.object({
|
||||||
text: z.string().min(1)
|
text: z.string().nonempty()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
async createTweet(
|
async createTweet(
|
||||||
|
@ -199,16 +257,16 @@ export class TwitterClient extends AIFunctionsProvider {
|
||||||
name: 'get_tweet_by_id',
|
name: 'get_tweet_by_id',
|
||||||
description: 'Fetch a tweet by its ID',
|
description: 'Fetch a tweet by its ID',
|
||||||
inputSchema: z.object({
|
inputSchema: z.object({
|
||||||
id: z.string().min(1)
|
id: z.string().nonempty()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
async findTweetById(params: { id: string } & types.FindTweetByIdParams) {
|
async getTweetById(params: { id: string } & types.GetTweetByIdParams) {
|
||||||
assert(
|
assert(
|
||||||
this.twitterApiPlan !== 'free',
|
this.twitterApiPlan !== 'free',
|
||||||
'TwitterClient.findTweetById not supported on free plan'
|
'TwitterClient.getTweetById is not supported on free plan'
|
||||||
)
|
)
|
||||||
|
|
||||||
return this._findTweetById(params.id, params)
|
return this._getTweetById(params.id, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -218,16 +276,16 @@ export class TwitterClient extends AIFunctionsProvider {
|
||||||
name: 'get_tweets_by_id',
|
name: 'get_tweets_by_id',
|
||||||
description: 'Fetch an array of tweets by their IDs',
|
description: 'Fetch an array of tweets by their IDs',
|
||||||
inputSchema: z.object({
|
inputSchema: z.object({
|
||||||
ids: z.array(z.string().min(1))
|
ids: z.array(z.string().nonempty())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
async findTweetsById({ ids, ...params }: types.FindTweetsByIdParams) {
|
async getTweetsById({ ids, ...params }: types.GetTweetsByIdParams) {
|
||||||
assert(
|
assert(
|
||||||
this.twitterApiPlan !== 'free',
|
this.twitterApiPlan !== 'free',
|
||||||
'TwitterClient.findTweetsById not supported on free plan'
|
'TwitterClient.getTweetsById is not supported on free plan'
|
||||||
)
|
)
|
||||||
|
|
||||||
return this._findTweetsById(ids, params)
|
return this._getTweetsById(ids, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -237,22 +295,102 @@ export class TwitterClient extends AIFunctionsProvider {
|
||||||
name: 'search_recent_tweets',
|
name: 'search_recent_tweets',
|
||||||
description: 'Searches for recent tweets',
|
description: 'Searches for recent tweets',
|
||||||
inputSchema: z.object({
|
inputSchema: z.object({
|
||||||
query: z.string().min(1),
|
query: z.string().nonempty(),
|
||||||
sort_order: z
|
sort_order: z
|
||||||
.enum(['recency', 'relevancy'])
|
.enum(['recency', 'relevancy'])
|
||||||
.default('relevancy')
|
.default('relevancy')
|
||||||
.optional()
|
.optional(),
|
||||||
|
max_results: z.number().min(10).max(100).optional(),
|
||||||
|
pagination_token: z.string().optional()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
async searchRecentTweets(params: types.SearchRecentTweetsParams) {
|
async searchRecentTweets(params: types.SearchRecentTweetsParams) {
|
||||||
assert(
|
assert(
|
||||||
this.twitterApiPlan !== 'free',
|
this.twitterApiPlan !== 'free',
|
||||||
'TwitterClient.searchRecentTweets not supported on free plan'
|
'TwitterClient.searchRecentTweets is not supported on free plan'
|
||||||
)
|
)
|
||||||
|
|
||||||
return this._searchRecentTweets(params)
|
return this._searchRecentTweets(params)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lists tweets which mention the given user.
|
||||||
|
*/
|
||||||
|
@aiFunction({
|
||||||
|
name: 'list_tweet_mentions_by_user_id',
|
||||||
|
description: 'Lists tweets which mention the given user.',
|
||||||
|
inputSchema: z.object({
|
||||||
|
userId: z.string().nonempty(),
|
||||||
|
max_results: z.number().min(5).max(100).optional(),
|
||||||
|
pagination_token: z.string().optional()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
async listTweetMentionsByUserId({
|
||||||
|
userId,
|
||||||
|
...params
|
||||||
|
}: { userId: string } & types.ListTweetMentionsByUserIdParams) {
|
||||||
|
assert(
|
||||||
|
this.twitterApiPlan !== 'free',
|
||||||
|
'TwitterClient.listTweetMentionsByUserId is not supported on free plan'
|
||||||
|
)
|
||||||
|
|
||||||
|
return this._listTweetMentionsByUserId(userId, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lists tweets liked by a user.
|
||||||
|
*/
|
||||||
|
@aiFunction({
|
||||||
|
name: 'list_tweets_liked_by_user_id',
|
||||||
|
description: 'Lists tweets liked by a user.',
|
||||||
|
inputSchema: z.object({
|
||||||
|
userId: z.string().nonempty(),
|
||||||
|
max_results: z.number().min(5).max(100).optional(),
|
||||||
|
pagination_token: z.string().optional()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
async listTweetsLikedByUserId({
|
||||||
|
userId,
|
||||||
|
...params
|
||||||
|
}: { userId: string } & types.ListTweetsLikedByUserIdParams) {
|
||||||
|
assert(
|
||||||
|
this.twitterApiPlan !== 'free',
|
||||||
|
'TwitterClient.listTweetsLikedByUserId is not supported on free plan'
|
||||||
|
)
|
||||||
|
|
||||||
|
return this._listTweetsLikedByUserId(userId, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lists tweets authored by a user.
|
||||||
|
*/
|
||||||
|
@aiFunction({
|
||||||
|
name: 'list_tweets_by_user_id',
|
||||||
|
description: 'Lists tweets authored by a user.',
|
||||||
|
inputSchema: z.object({
|
||||||
|
userId: z.string().nonempty(),
|
||||||
|
max_results: z.number().min(5).max(100).optional(),
|
||||||
|
pagination_token: z.string().optional(),
|
||||||
|
exclude: z
|
||||||
|
.array(z.union([z.literal('replies'), z.literal('retweets')]))
|
||||||
|
.optional()
|
||||||
|
.describe(
|
||||||
|
'By default, replies and retweets are included. Use this parameter if you want to exclude either or both of them.'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
async listTweetsByUserId({
|
||||||
|
userId,
|
||||||
|
...params
|
||||||
|
}: { userId: string } & types.ListTweetsByUserIdParams) {
|
||||||
|
assert(
|
||||||
|
this.twitterApiPlan !== 'free',
|
||||||
|
'TwitterClient.listTweetsByUserId is not supported on free plan'
|
||||||
|
)
|
||||||
|
|
||||||
|
return this._listTweetsByUserId(userId, params)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch a twitter user by ID
|
* Fetch a twitter user by ID
|
||||||
*/
|
*/
|
||||||
|
@ -263,16 +401,16 @@ export class TwitterClient extends AIFunctionsProvider {
|
||||||
id: z.string().min(1)
|
id: z.string().min(1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
async findUserById({
|
async getUserById({
|
||||||
id,
|
id,
|
||||||
...params
|
...params
|
||||||
}: { id: string } & types.FindUserByIdParams) {
|
}: { id: string } & types.GetUserByIdParams) {
|
||||||
assert(
|
assert(
|
||||||
this.twitterApiPlan !== 'free',
|
this.twitterApiPlan !== 'free',
|
||||||
'TwitterClient.findUserById not supported on free plan'
|
'TwitterClient.getUserById not supported on free plan'
|
||||||
)
|
)
|
||||||
|
|
||||||
return this._findUserById(id, params)
|
return this._getUserById(id, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -285,16 +423,16 @@ export class TwitterClient extends AIFunctionsProvider {
|
||||||
username: z.string().min(1)
|
username: z.string().min(1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
async findUserByUsername({
|
async getUserByUsername({
|
||||||
username,
|
username,
|
||||||
...params
|
...params
|
||||||
}: { username: string } & types.FindUserByUsernameParams) {
|
}: { username: string } & types.GetUserByUsernameParams) {
|
||||||
assert(
|
assert(
|
||||||
this.twitterApiPlan !== 'free',
|
this.twitterApiPlan !== 'free',
|
||||||
'TwitterClient.findUserByUsername not supported on free plan'
|
'TwitterClient.getUserByUsername not supported on free plan'
|
||||||
)
|
)
|
||||||
|
|
||||||
return this._findUserByUsername(username, params)
|
return this._getUserByUsername(username, params)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -380,30 +518,28 @@ function createTweetImpl(client: types.TwitterV2Client) {
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error('error creating tweet', JSON.stringify(err, null, 2))
|
console.error('error creating tweet', JSON.stringify(err, null, 2))
|
||||||
|
|
||||||
handleKnownTwitterErrors(err, { label: 'creating tweet' })
|
handleTwitterError(err, { label: 'error creating tweet' })
|
||||||
throw err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function findTweetByIdImpl(client: types.TwitterV2Client) {
|
function getTweetByIdImpl(client: types.TwitterV2Client) {
|
||||||
return async (tweetId: string, params?: types.FindTweetByIdParams) => {
|
return async (tweetId: string, params?: types.GetTweetByIdParams) => {
|
||||||
try {
|
try {
|
||||||
return await client.tweets.findTweetById(tweetId, {
|
return await client.tweets.findTweetById(tweetId, {
|
||||||
...defaultTweetQueryParams,
|
...defaultTweetQueryParams,
|
||||||
...params
|
...params
|
||||||
})
|
})
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
handleKnownTwitterErrors(err, { label: `fetching tweet ${tweetId}` })
|
handleTwitterError(err, { label: `error fetching tweet ${tweetId}` })
|
||||||
throw err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function findTweetsByIdImpl(client: types.TwitterV2Client) {
|
function getTweetsByIdImpl(client: types.TwitterV2Client) {
|
||||||
return async (
|
return async (
|
||||||
ids: string[],
|
ids: string[],
|
||||||
params?: Omit<types.FindTweetsByIdParams, 'ids'>
|
params?: Omit<types.GetTweetsByIdParams, 'ids'>
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
return await client.tweets.findTweetsById({
|
return await client.tweets.findTweetsById({
|
||||||
|
@ -412,8 +548,7 @@ function findTweetsByIdImpl(client: types.TwitterV2Client) {
|
||||||
ids
|
ids
|
||||||
})
|
})
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
handleKnownTwitterErrors(err, { label: `fetching ${ids.length} tweets` })
|
handleTwitterError(err, { label: `error fetching ${ids.length} tweets` })
|
||||||
throw err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -426,42 +561,90 @@ function searchRecentTweetsImpl(client: types.TwitterV2Client) {
|
||||||
...params
|
...params
|
||||||
})
|
})
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
handleKnownTwitterErrors(err, {
|
handleTwitterError(err, {
|
||||||
label: `searching tweets query "${params.query}"`
|
label: `error searching tweets query "${params.query}"`
|
||||||
})
|
})
|
||||||
throw err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function findUserByIdImpl(client: types.TwitterV2Client) {
|
function getUserByIdImpl(client: types.TwitterV2Client) {
|
||||||
return async (userId: string, params?: types.FindUserByIdParams) => {
|
return async (userId: string, params?: types.GetUserByIdParams) => {
|
||||||
try {
|
try {
|
||||||
return await client.users.findUserById(userId, {
|
return await client.users.findUserById(userId, {
|
||||||
...defaultUserQueryParams,
|
...defaultUserQueryParams,
|
||||||
...params
|
...params
|
||||||
})
|
})
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
handleKnownTwitterErrors(err, {
|
handleTwitterError(err, {
|
||||||
label: `fetching user with id ${userId}`
|
label: `error fetching user ${userId}`
|
||||||
})
|
})
|
||||||
throw err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function findUserByUsernameImpl(client: types.TwitterV2Client) {
|
function getUserByUsernameImpl(client: types.TwitterV2Client) {
|
||||||
return async (username: string, params?: types.FindUserByUsernameParams) => {
|
return async (username: string, params?: types.GetUserByUsernameParams) => {
|
||||||
try {
|
try {
|
||||||
return await client.users.findUserByUsername(username, {
|
return await client.users.findUserByUsername(username, {
|
||||||
...defaultUserQueryParams,
|
...defaultUserQueryParams,
|
||||||
...params
|
...params
|
||||||
})
|
})
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
handleKnownTwitterErrors(err, {
|
handleTwitterError(err, {
|
||||||
label: `fetching user with username ${username}`
|
label: `error fetching user with username ${username}`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function listTweetMentionsByUserIdImpl(client: types.TwitterV2Client) {
|
||||||
|
return async (
|
||||||
|
userId: string,
|
||||||
|
params?: types.ListTweetMentionsByUserIdParams
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
return await client.tweets.usersIdMentions(userId, {
|
||||||
|
...defaultTweetQueryParams,
|
||||||
|
...params
|
||||||
|
})
|
||||||
|
} catch (err: any) {
|
||||||
|
handleTwitterError(err, {
|
||||||
|
label: `error fetching tweets mentions for user ${userId}`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function listTweetsLikedByUserIdImpl(client: types.TwitterV2Client) {
|
||||||
|
return async (
|
||||||
|
userId: string,
|
||||||
|
params?: types.ListTweetsLikedByUserIdParams
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
return await client.tweets.usersIdLikedTweets(userId, {
|
||||||
|
...defaultTweetQueryParams,
|
||||||
|
...params
|
||||||
|
})
|
||||||
|
} catch (err: any) {
|
||||||
|
handleTwitterError(err, {
|
||||||
|
label: `error fetching tweets liked by user ${userId}`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function listTweetsByUserIdImpl(client: types.TwitterV2Client) {
|
||||||
|
return async (userId: string, params?: types.ListTweetsByUserIdParams) => {
|
||||||
|
try {
|
||||||
|
return await client.tweets.usersIdTweets(userId, {
|
||||||
|
...defaultTweetQueryParams,
|
||||||
|
...params
|
||||||
|
})
|
||||||
|
} catch (err: any) {
|
||||||
|
handleTwitterError(err, {
|
||||||
|
label: `error fetching tweets by user ${userId}`
|
||||||
})
|
})
|
||||||
throw err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,15 +30,15 @@ export type CreateTweetParams = Simplify<
|
||||||
Parameters<TwitterV2Client['tweets']['createTweet']>[0]
|
Parameters<TwitterV2Client['tweets']['createTweet']>[0]
|
||||||
>
|
>
|
||||||
|
|
||||||
export type UsersIdMentionsParams = Simplify<
|
export type ListTweetMentionsByUserIdParams = Simplify<
|
||||||
Parameters<TwitterV2Client['tweets']['usersIdMentions']>[1]
|
Parameters<TwitterV2Client['tweets']['usersIdMentions']>[1]
|
||||||
>
|
>
|
||||||
|
|
||||||
export type FindTweetByIdParams = Simplify<
|
export type GetTweetByIdParams = Simplify<
|
||||||
Parameters<TwitterV2Client['tweets']['findTweetById']>[1]
|
Parameters<TwitterV2Client['tweets']['findTweetById']>[1]
|
||||||
>
|
>
|
||||||
|
|
||||||
export type FindTweetsByIdParams = Simplify<
|
export type GetTweetsByIdParams = Simplify<
|
||||||
Parameters<TwitterV2Client['tweets']['findTweetsById']>[0]
|
Parameters<TwitterV2Client['tweets']['findTweetsById']>[0]
|
||||||
>
|
>
|
||||||
|
|
||||||
|
@ -46,14 +46,22 @@ export type SearchRecentTweetsParams = Simplify<
|
||||||
Parameters<TwitterV2Client['tweets']['tweetsRecentSearch']>[0]
|
Parameters<TwitterV2Client['tweets']['tweetsRecentSearch']>[0]
|
||||||
>
|
>
|
||||||
|
|
||||||
export type FindUserByIdParams = Simplify<
|
export type GetUserByIdParams = Simplify<
|
||||||
Parameters<TwitterV2Client['users']['findUserById']>[1]
|
Parameters<TwitterV2Client['users']['findUserById']>[1]
|
||||||
>
|
>
|
||||||
|
|
||||||
export type FindUserByUsernameParams = Simplify<
|
export type GetUserByUsernameParams = Simplify<
|
||||||
Parameters<TwitterV2Client['users']['findUserByUsername']>[1]
|
Parameters<TwitterV2Client['users']['findUserByUsername']>[1]
|
||||||
>
|
>
|
||||||
|
|
||||||
|
export type ListTweetsLikedByUserIdParams = Simplify<
|
||||||
|
Parameters<TwitterV2Client['tweets']['usersIdLikedTweets']>[1]
|
||||||
|
>
|
||||||
|
|
||||||
|
export type ListTweetsByUserIdParams = Simplify<
|
||||||
|
Parameters<TwitterV2Client['tweets']['usersIdTweets']>[1]
|
||||||
|
>
|
||||||
|
|
||||||
type Unpacked<T> = T extends (infer U)[] ? U : T
|
type Unpacked<T> = T extends (infer U)[] ? U : T
|
||||||
|
|
||||||
export type Tweet = Simplify<
|
export type Tweet = Simplify<
|
||||||
|
|
|
@ -7,24 +7,21 @@ import { TwitterError } from './error'
|
||||||
* Error handler which takes in an unknown Error object and converts it to a
|
* Error handler which takes in an unknown Error object and converts it to a
|
||||||
* structured TwitterError object for a set of common Twitter API errors.
|
* structured TwitterError object for a set of common Twitter API errors.
|
||||||
*
|
*
|
||||||
* Re-throws the error and will never return.
|
* Re-throws the error if not recognized and will never return.
|
||||||
*/
|
*/
|
||||||
export function handleKnownTwitterErrors(
|
export function handleTwitterError(
|
||||||
err: any,
|
err: any,
|
||||||
{ label = '' }: { label?: string } = {}
|
{ label = '' }: { label?: string } = {}
|
||||||
) {
|
): never {
|
||||||
if (err.status === 403) {
|
if (err.status === 403) {
|
||||||
// user may have deleted the tweet we're trying to respond to
|
// user may have deleted the tweet we're trying to respond to
|
||||||
throw new TwitterError(
|
throw new TwitterError(err.error?.detail || `${label}: 403 forbidden`, {
|
||||||
err.error?.detail || `error ${label}: 403 forbidden`,
|
|
||||||
{
|
|
||||||
type: 'twitter:forbidden',
|
type: 'twitter:forbidden',
|
||||||
isFinal: true,
|
isFinal: true,
|
||||||
cause: err
|
cause: err
|
||||||
}
|
})
|
||||||
)
|
|
||||||
} else if (err.status === 401) {
|
} else if (err.status === 401) {
|
||||||
throw new TwitterError(`error ${label}: unauthorized`, {
|
throw new TwitterError(`${label}: unauthorized`, {
|
||||||
type: 'twitter:auth',
|
type: 'twitter:auth',
|
||||||
cause: err
|
cause: err
|
||||||
})
|
})
|
||||||
|
@ -34,13 +31,13 @@ export function handleKnownTwitterErrors(
|
||||||
err.error?.error_description
|
err.error?.error_description
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
throw new TwitterError(`error ${label}: invalid auth token`, {
|
throw new TwitterError(`${label}: invalid auth token`, {
|
||||||
type: 'twitter:auth',
|
type: 'twitter:auth',
|
||||||
cause: err
|
cause: err
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else if (err.status === 429) {
|
} else if (err.status === 429) {
|
||||||
throw new TwitterError(`error ${label}: too many requests`, {
|
throw new TwitterError(`${label}: too many requests`, {
|
||||||
type: 'twitter:rate-limit',
|
type: 'twitter:rate-limit',
|
||||||
cause: err
|
cause: err
|
||||||
})
|
})
|
||||||
|
@ -54,9 +51,7 @@ export function handleKnownTwitterErrors(
|
||||||
|
|
||||||
if (err.status >= 400 && err.status < 500) {
|
if (err.status >= 400 && err.status < 500) {
|
||||||
throw new TwitterError(
|
throw new TwitterError(
|
||||||
`error ${label}: ${err.status} ${
|
`${label}: ${err.status} ${err.error?.description || err.toString()}`,
|
||||||
err.error?.description || err.toString()
|
|
||||||
}`,
|
|
||||||
{
|
{
|
||||||
type: 'twitter:unknown',
|
type: 'twitter:unknown',
|
||||||
isFinal: true,
|
isFinal: true,
|
||||||
|
@ -65,9 +60,7 @@ export function handleKnownTwitterErrors(
|
||||||
)
|
)
|
||||||
} else if (err.status >= 500) {
|
} else if (err.status >= 500) {
|
||||||
throw new TwitterError(
|
throw new TwitterError(
|
||||||
`error ${label}: ${err.status} ${
|
`${label}: ${err.status} ${err.error?.description || err.toString()}`,
|
||||||
err.error?.description || err.toString()
|
|
||||||
}`,
|
|
||||||
{
|
{
|
||||||
type: 'twitter:unknown',
|
type: 'twitter:unknown',
|
||||||
isFinal: false,
|
isFinal: false,
|
||||||
|
|
Ładowanie…
Reference in New Issue