From e7d8933056802bc95252a86ce482e941cd2b10d5 Mon Sep 17 00:00:00 2001 From: Wazbat Date: Sat, 15 Apr 2023 17:12:41 +0200 Subject: [PATCH 01/13] =?UTF-8?q?=E2=9C=A8=20Added=20support=20for=20fetch?= =?UTF-8?q?ing=20user=20profiles?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/status.ts | 4 +- src/api/user.ts | 91 ++++++++++++++++++++++++++++++++++ src/fetch.ts | 37 ++++++++++++++ src/server.ts | 95 +++++++++++++++++++++++++++++++++--- src/strings.ts | 1 + src/types/twitterTypes.d.ts | 97 +++++++++++++++++++++++++++++++++++++ src/types/types.d.ts | 27 ++++++++++- src/user.ts | 53 ++++++++++++++++++++ test/index.test.ts | 49 +++++++++++++++++++ 9 files changed, 445 insertions(+), 9 deletions(-) create mode 100644 src/api/user.ts create mode 100644 src/user.ts diff --git a/src/api/status.ts b/src/api/status.ts index e4578e1..cc6d648 100644 --- a/src/api/status.ts +++ b/src/api/status.ts @@ -187,7 +187,7 @@ export const statusAPI = async ( language: string | undefined, event: FetchEvent, flags?: InputFlags -): Promise => { +): Promise => { let conversation = await fetchConversation(status, event); let tweet = conversation?.globalObjects?.tweets?.[status] || {}; @@ -242,7 +242,7 @@ export const statusAPI = async ( } /* Creating the response objects */ - const response: APIResponse = { code: 200, message: 'OK' } as APIResponse; + const response: TweetAPIResponse = { code: 200, message: 'OK' } as TweetAPIResponse; const apiTweet: APITweet = (await populateTweetProperties( tweet, conversation, diff --git a/src/api/user.ts b/src/api/user.ts new file mode 100644 index 0000000..bd98a8b --- /dev/null +++ b/src/api/user.ts @@ -0,0 +1,91 @@ +import { renderCard } from '../helpers/card'; +import { Constants } from '../constants'; +import { fetchConversation, fetchUser } from '../fetch'; +import { linkFixer } from '../helpers/linkFixer'; +import { handleMosaic } from '../helpers/mosaic'; +import { colorFromPalette } from '../helpers/palette'; +import { translateTweet } from '../helpers/translate'; +import { unescapeText } from '../helpers/utils'; +import { processMedia } from '../helpers/media'; + +/* This function does the heavy lifting of processing data from Twitter API + and using it to create FixTweet's streamlined API responses */ +const populateUserProperties = async ( + response: GraphQLUserResponse, + language: string | undefined + // eslint-disable-next-line sonarjs/cognitive-complexity +): Promise => { + const apiUser = {} as APIUser; + + const user = response.data.user.result; + /* Populating a lot of the basics */ + apiUser.url = `${Constants.TWITTER_ROOT}/${user.legacy.screen_name}`; + apiUser.id = user.id; + apiUser.followers = user.legacy.followers_count; + apiUser.following = user.legacy.friends_count; + apiUser.likes = user.legacy.favourites_count; + apiUser.tweets = user.legacy.statuses_count; + apiUser.name = user.legacy.name; + apiUser.screen_name = user.legacy.screen_name; + apiUser.description = user.legacy.description; + apiUser.location = user.legacy.location; + apiUser.verified = user.legacy.verified; + apiUser.avatar_url = user.legacy.profile_image_url_https; + + return apiUser; +}; + +const writeDataPoint = ( + event: FetchEvent, + language: string | undefined, + returnCode: string, + flags?: InputFlags +) => { + console.log('Writing data point...'); + if (typeof AnalyticsEngine !== 'undefined') { + const flagString = + Object.keys(flags || {}) + // @ts-expect-error - TypeScript doesn't like iterating over the keys, but that's OK + .filter(flag => flags?.[flag])[0] || 'standard'; + + AnalyticsEngine.writeDataPoint({ + blobs: [ + event.request.cf?.colo as string /* Datacenter location */, + event.request.cf?.country as string /* Country code */, + event.request.headers.get('user-agent') ?? + '' /* User agent (for aggregating bots calling) */, + returnCode /* Return code */, + flagString /* Type of request */, + language ?? '' /* For translate feature */ + ], + doubles: [0 /* NSFW media = 1, No NSFW Media = 0 */], + indexes: [event.request.headers.get('cf-ray') ?? '' /* CF Ray */] + }); + } +}; + +/* API for Twitter profiles (Users) + Used internally by FixTweet's embed service, or + available for free using api.fxtwitter.com. */ +export const userAPI = async ( + username: string, + language: string | undefined, + event: FetchEvent, + flags?: InputFlags +): Promise => { + const userResponse = await fetchUser(username, event); + + /* Creating the response objects */ + const response: UserAPIResponse = { code: 200, message: 'OK' } as UserAPIResponse; + const apiTweet: APIUser = (await populateUserProperties( + userResponse, + language + )) as APIUser; + + /* Finally, staple the User to the response and return it */ + response.user = apiTweet; + + writeDataPoint(event, language, 'OK', flags); + + return response; +}; diff --git a/src/fetch.ts b/src/fetch.ts index 51c1554..e520e5f 100644 --- a/src/fetch.ts +++ b/src/fetch.ts @@ -203,3 +203,40 @@ export const fetchConversation = async ( } )) as TimelineBlobPartial; }; + +export const fetchUser = async ( + username: string, + event: FetchEvent, + useElongator = false +): Promise => { + return (await twitterFetch( + `${Constants.TWITTER_ROOT}/i/api/graphql/sLVLhk0bGj3MVFEKTdax1w/UserByScreenName?variables=${ + encodeURIComponent( + JSON.stringify({ + screen_name: username, + withSafetyModeUserFields: true + }) + ) + }&features=${encodeURIComponent( + JSON.stringify({ + blue_business_profile_image_shape_enabled: true, + responsive_web_graphql_exclude_directive_enabled: true, + verified_phone_label_enabled: true + }) + )}`, + event, + useElongator, + // Validator function + (_res: unknown) => { + const response = _res as GraphQLUserResponse; + return !(response?.data?.user?.result?.__typename !== 'User' || typeof response.data.user.result.legacy === 'undefined'); + /* + return !( + typeof conversation.globalObjects === 'undefined' && + (typeof conversation.errors === 'undefined' || + conversation.errors?.[0]?.code === 239) + ); + */ + } + )) as GraphQLUserResponse; +}; diff --git a/src/server.ts b/src/server.ts index 2a287a6..6b8899c 100644 --- a/src/server.ts +++ b/src/server.ts @@ -7,6 +7,7 @@ import { Strings } from './strings'; import motd from '../motd.json'; import { sanitizeText } from './helpers/utils'; +import { handleProfile } from './user'; const router = Router(); @@ -140,18 +141,100 @@ const statusRequest = async ( } }; -/* Redirects to user profile when linked. - We don't do any fancy special embeds yet, just Twitter default embeds. */ -const profileRequest = async (request: IRequest) => { - const { handle } = request.params; +/* Handler for User Profiles */ +const profileRequest = async (request: IRequest, event: FetchEvent, + flags: InputFlags = {}) => { + const { handle, language } = request.params; const url = new URL(request.url); + const userAgent = request.headers.get('User-Agent') || ''; + + /* User Agent matching for embed generators, bots, crawlers, and other automated + tools. It's pretty all-encompassing. Note that Firefox/92 is in here because + Discord sometimes uses the following UA: + + Mozilla/5.0 (Macintosh; Intel Mac OS X 11.6; rv:92.0) Gecko/20100101 Firefox/92.0 + + I'm not sure why that specific one, it's pretty weird, but this edge case ensures + stuff keeps working. + + On the very rare off chance someone happens to be using specifically Firefox 92, + the http-equiv="refresh" meta tag will ensure an actual human is sent to the destination. */ + const isBotUA = userAgent.match(Constants.BOT_UA_REGEX) !== null; /* If not a valid screen name, we redirect to project GitHub */ if (handle.match(/\w{1,15}/gi)?.[0] !== handle) { return Response.redirect(Constants.REDIRECT_URL, 302); - } else { - return Response.redirect(`${Constants.TWITTER_ROOT}${url.pathname}`, 302); } + const username = handle.match(/\w{1,15}/gi)?.[0]; + /* Check if request is to api.fxtwitter.com, or the tweet is appended with .json + Note that unlike TwitFix, FixTweet will never generate embeds for .json, and + in fact we only support .json because it's what people using TwitFix API would + be used to. */ + if ( + url.pathname.match(/\/status(es)?\/\d{2,20}\.(json)/g) !== null || + Constants.API_HOST_LIST.includes(url.hostname) + ) { + console.log('JSON API request'); + flags.api = true; + } + + /* Direct media or API access bypasses bot check, returning same response regardless of UA */ + if (isBotUA || flags.direct || flags.api) { + if (isBotUA) { + console.log(`Matched bot UA ${userAgent}`); + } else { + console.log('Bypass bot check'); + } + + /* This throws the necessary data to handleStatus (in status.ts) */ + const profileResponse = await handleProfile( + handle.match(/\w{1,15}/gi)?.[0] || '', + userAgent, + flags, + language, + event + ); + + /* Complete responses are normally sent just by errors. Normal embeds send a `text` value. */ + if (profileResponse.response) { + console.log('handleProfile sent response'); + return profileResponse.response; + } else if (profileResponse.text) { + console.log('handleProfile sent embed'); + /* TODO This check has purpose in the original handleStatus handler, but I'm not sure if this edge case can happen here */ + if (!isBotUA) { + return Response.redirect(`${Constants.TWITTER_ROOT}/${handle}`, 302); + } + + let headers = Constants.RESPONSE_HEADERS; + + if (profileResponse.cacheControl) { + headers = { ...headers, 'cache-control': profileResponse.cacheControl }; + } + + /* Return the response containing embed information */ + return new Response(profileResponse.text, { + headers: headers, + status: 200 + }); + } else { + /* Somehow handleStatus sent us nothing. This should *never* happen, but we have a case for it. */ + return new Response(Strings.ERROR_UNKNOWN, { + headers: Constants.RESPONSE_HEADERS, + status: 500 + }); + } + } else { + /* A human has clicked a fxtwitter.com/:screen_name link! + Obviously we just need to redirect to the user directly.*/ + console.log('Matched human UA', userAgent); + return Response.redirect( + `${Constants.TWITTER_ROOT}/${handle}`, + 302 + ); + } + + }; const genericTwitterRedirect = async (request: IRequest) => { diff --git a/src/strings.ts b/src/strings.ts index 037b8d7..b6e8b4f 100644 --- a/src/strings.ts +++ b/src/strings.ts @@ -148,6 +148,7 @@ This is caused by Twitter API downtime or a new bug. Try again in a little while ERROR_API_FAIL: 'Tweet failed to load due to an API error :(', ERROR_PRIVATE: `Due to Twitter API changes, some NSFW Tweets are currently being blocked. We are currently looking into a workaround. ๐Ÿ™`, ERROR_TWEET_NOT_FOUND: `Sorry, that Tweet doesn't exist :(`, + ERROR_USER_NOT_FOUND: `Sorry, that Tweet doesn't exist :(`, ERROR_UNKNOWN: `Unknown error occurred, sorry about that :(`, TWITFIX_API_SUNSET: `The original TwitFix API has been sunset. To learn more about the FixTweet API, check out ; // {}, + "is_blue_verified": boolean; // false, + "profile_image_shape": 'Circle' | 'Square'; // "Circle", + "legacy": { + "created_at": string; // "Sat Sep 26 17:20:55 +0000 2015", + "default_profile": boolean // false, + "default_profile_image": boolean // false, + "description": string; // "dangered wolf#3621 https://t.co/eBTS4kksMw", + "entities": { + "description": { + "urls": { + "display_url": string; // "t.me/dangeredwolf", + "expanded_url": string; // "http://t.me/dangeredwolf", + "url": string; // "https://t.co/eBTS4kksMw", + "indices": [ + 19, + 42 + ] + }[] + } + }, + "fast_followers_count": 0, + "favourites_count": number; // 126708, + "followers_count": number; // 4996, + "friends_count": number; // 2125, + "has_custom_timelines": boolean; // true, + "is_translator": boolean; // false, + "listed_count": number; // 69, + "location": string; // "they/them", + "media_count": number; // 20839, + "name": string; // "dangered wolf", + "normal_followers_count": number; // 4996, + "pinned_tweet_ids_str": string[]; // Array of tweet ids + "possibly_sensitive": boolean; // false, + "profile_banner_url": string; // "https://pbs.twimg.com/profile_banners/3784131322/1658599775", + "profile_image_url_https": string; // "https://pbs.twimg.com/profile_images/1555638673705783299/3gaaetxC_normal.jpg", + "profile_interstitial_type": string; // "", + "screen_name": string; // "dangeredwolf", + "statuses_count": number; // 108222, + "translator_type": string; // "regular", + "verified": boolean; // false, + "withheld_in_countries": [] + }, + "professional": { + "rest_id": string; // "1508134739420536845", + "professional_type": string; // "Creator", + "category": [ + { + "id": number; // 354, + "name": string // "Fish & Chips Restaurant", + "icon_name": string; // "IconBriefcaseStroke" + } + ] + }, + "legacy_extended_profile": { + birthdate?: { + day: number; // 7, + month: number; // 1, + visibility: string; // "Public" + year: number; // 2000 + year_visibility: string; // "Public" + }; + profile_image_shape: string; // "Circle", + rest_id: string; // "3784131322", + }, + "is_profile_translatable": false, + "verification_info": { + reason: { + description: { + entities: { + from_index: number; // 98, + ref: { + url: string; // "https://help.twitter.com/managing-your-account/about-twitter-verified-accounts", + url_type: string; // "ExternalUrl" + }; + to_index: number; // 108 + }[]; + text: string; // "This account is verified because itโ€™s subscribed to Twitter Blue or is a legacy verified account. Learn more" + } + } + }, + "business_account": {} + +} \ No newline at end of file diff --git a/src/types/types.d.ts b/src/types/types.d.ts index 21c3a60..84070f2 100644 --- a/src/types/types.d.ts +++ b/src/types/types.d.ts @@ -40,12 +40,18 @@ interface VerticalSize { secondHeight: number; } -interface APIResponse { +interface TweetAPIResponse { code: number; message: string; tweet?: APITweet; } +interface UserAPIResponse { + code: number; + message: string; + user?: APIUser; +} + interface APITranslate { text: string; source_lang: string; @@ -141,3 +147,22 @@ interface APITweet { twitter_card: 'tweet' | 'summary' | 'summary_large_image' | 'player'; } + +interface APIUser { + id: string; + name: string; + screen_name: string; + avatar_url: string; + banner_url: string; + avatar_color: string; + description: string; + location: string; + url: string; + protected: boolean; + verified: boolean; + followers: number; + following: number; + tweets: number; + likes: number; + joined: string; +} \ No newline at end of file diff --git a/src/user.ts b/src/user.ts new file mode 100644 index 0000000..ac4037d --- /dev/null +++ b/src/user.ts @@ -0,0 +1,53 @@ +import { Constants } from './constants'; +import { handleQuote } from './helpers/quote'; +import { formatNumber, sanitizeText } from './helpers/utils'; +import { Strings } from './strings'; +import { getAuthorText } from './helpers/author'; +import { userAPI } from './api/user'; + +export const returnError = (error: string): StatusResponse => { + return { + text: Strings.BASE_HTML.format({ + lang: '', + headers: [ + ``, + `` + ].join('') + }) + }; +}; + +/* Handler for Twitter users */ +export const handleProfile = async ( + username: string, + userAgent?: string, + flags?: InputFlags, + language?: string, + event?: FetchEvent +): Promise => { + console.log('Direct?', flags?.direct); + + const api = await userAPI(username, language, event as FetchEvent); + const user = api?.user as APIUser; + + /* Catch this request if it's an API response */ + // For now we just always return the API response while testing + if (flags?.api || true) { + return { + response: new Response(JSON.stringify(api), { + headers: { ...Constants.RESPONSE_HEADERS, ...Constants.API_RESPONSE_HEADERS }, + status: api.code + }) + }; + } + + /* If there was any errors fetching the User, we'll return it */ + switch (api.code || true) { + case 401: + return returnError(Strings.ERROR_PRIVATE); + case 404: + return returnError(Strings.ERROR_USER_NOT_FOUND); + case 500: + return returnError(Strings.ERROR_API_FAIL); + } +}; diff --git a/test/index.test.ts b/test/index.test.ts index 95d4062..ffbba6b 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -312,3 +312,52 @@ test('API fetch poll Tweet', async () => { expect(choices[3].count).toEqual(31706); expect(choices[3].percentage).toEqual(58); }); + +test('API fetch user', async () => { + const result = await cacheWrapper( + new Request('https://api.fxtwitter.com/wazbat', { + method: 'GET', + headers: botHeaders + }) + ); + expect(result.status).toEqual(200); + const response = (await result.json()) as APIResponse; + expect(response).toBeTruthy(); + expect(response.code).toEqual(200); + expect(response.message).toEqual('OK'); + + const tweet = response.tweet as APITweet; + expect(tweet).toBeTruthy(); + expect(tweet.url).toEqual('https://twitter.com/Twitter/status/1055475950543167488'); + expect(tweet.id).toEqual('1055475950543167488'); + expect(tweet.text).toEqual('A poll:'); + expect(tweet.author.screen_name?.toLowerCase()).toEqual('twitter'); + expect(tweet.author.name).toBeTruthy(); + expect(tweet.author.avatar_url).toBeTruthy(); + expect(tweet.author.banner_url).toBeTruthy(); + expect(tweet.author.avatar_color).toBeTruthy(); + expect(tweet.twitter_card).toEqual('tweet'); + expect(tweet.created_at).toEqual('Thu Oct 25 15:07:31 +0000 2018'); + expect(tweet.created_timestamp).toEqual(1540480051); + expect(tweet.lang).toEqual('en'); + expect(tweet.replying_to).toBeNull(); + expect(tweet.poll).toBeTruthy(); + const poll = tweet.poll as APIPoll; + expect(poll.ends_at).toEqual('2018-10-26T03:07:30Z'); + expect(poll.time_left_en).toEqual('Final results'); + expect(poll.total_votes).toEqual(54703); + + const choices = poll.choices as APIPollChoice[]; + expect(choices[0].label).toEqual('Yesssss'); + expect(choices[0].count).toEqual(14773); + expect(choices[0].percentage).toEqual(27); + expect(choices[1].label).toEqual('No'); + expect(choices[1].count).toEqual(3618); + expect(choices[1].percentage).toEqual(6.6); + expect(choices[2].label).toEqual('Maybe?'); + expect(choices[2].count).toEqual(4606); + expect(choices[2].percentage).toEqual(8.4); + expect(choices[3].label).toEqual('Just show me the results'); + expect(choices[3].count).toEqual(31706); + expect(choices[3].percentage).toEqual(58); +}); From 959726be4604f324139fb7af8933a7eb36ab3366 Mon Sep 17 00:00:00 2001 From: Wazbat Date: Sat, 15 Apr 2023 17:16:31 +0200 Subject: [PATCH 02/13] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Removed=20quotes=20f?= =?UTF-8?q?rom=20types?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/types/twitterTypes.d.ts | 96 ++++++++++++++++++------------------- 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/src/types/twitterTypes.d.ts b/src/types/twitterTypes.d.ts index ec679a4..08d38b2 100644 --- a/src/types/twitterTypes.d.ts +++ b/src/types/twitterTypes.d.ts @@ -204,62 +204,62 @@ type GraphQLUserResponse = { type GraphQLUser = { __typename: "User", id: string; // "VXNlcjozNzg0MTMxMzIy", - "rest_id": string; // "3784131322", - "affiliates_highlighted_label": Record; // {}, - "is_blue_verified": boolean; // false, - "profile_image_shape": 'Circle' | 'Square'; // "Circle", - "legacy": { - "created_at": string; // "Sat Sep 26 17:20:55 +0000 2015", - "default_profile": boolean // false, - "default_profile_image": boolean // false, - "description": string; // "dangered wolf#3621 https://t.co/eBTS4kksMw", - "entities": { - "description": { - "urls": { - "display_url": string; // "t.me/dangeredwolf", - "expanded_url": string; // "http://t.me/dangeredwolf", - "url": string; // "https://t.co/eBTS4kksMw", - "indices": [ + rest_id: string; // "3784131322", + affiliates_highlighted_label: Record; // {}, + is_blue_verified: boolean; // false, + profile_image_shape: 'Circle' | 'Square'; // "Circle", + legacy: { + created_at: string; // "Sat Sep 26 17:20:55 +0000 2015", + default_profile: boolean // false, + default_profile_image: boolean // false, + description: string; // "dangered wolf#3621 https://t.co/eBTS4kksMw", + entities: { + description: { + urls: { + display_url: string; // "t.me/dangeredwolf", + expanded_url: string; // "http://t.me/dangeredwolf", + url: string; // "https://t.co/eBTS4kksMw", + indices: [ 19, 42 ] }[] } }, - "fast_followers_count": 0, - "favourites_count": number; // 126708, - "followers_count": number; // 4996, - "friends_count": number; // 2125, - "has_custom_timelines": boolean; // true, - "is_translator": boolean; // false, - "listed_count": number; // 69, - "location": string; // "they/them", - "media_count": number; // 20839, - "name": string; // "dangered wolf", - "normal_followers_count": number; // 4996, - "pinned_tweet_ids_str": string[]; // Array of tweet ids - "possibly_sensitive": boolean; // false, - "profile_banner_url": string; // "https://pbs.twimg.com/profile_banners/3784131322/1658599775", - "profile_image_url_https": string; // "https://pbs.twimg.com/profile_images/1555638673705783299/3gaaetxC_normal.jpg", - "profile_interstitial_type": string; // "", - "screen_name": string; // "dangeredwolf", - "statuses_count": number; // 108222, - "translator_type": string; // "regular", - "verified": boolean; // false, - "withheld_in_countries": [] + fast_followers_count: 0, + favourites_count: number; // 126708, + followers_count: number; // 4996, + friends_count: number; // 2125, + has_custom_timelines: boolean; // true, + is_translator: boolean; // false, + listed_count: number; // 69, + location: string; // "they/them", + media_count: number; // 20839, + name: string; // "dangered wolf", + normal_followers_count: number; // 4996, + pinned_tweet_ids_str: string[]; // Array of tweet ids + possibly_sensitive: boolean; // false, + profile_banner_url: string; // "https://pbs.twimg.com/profile_banners/3784131322/1658599775", + profile_image_url_https: string; // "https://pbs.twimg.com/profile_images/1555638673705783299/3gaaetxC_normal.jpg", + profile_interstitial_type: string; // "", + screen_name: string; // "dangeredwolf", + statuses_count: number; // 108222, + translator_type: string; // "regular", + verified: boolean; // false, + withheld_in_countries: [] }, - "professional": { - "rest_id": string; // "1508134739420536845", - "professional_type": string; // "Creator", - "category": [ + professional: { + rest_id: string; // "1508134739420536845", + professional_type: string; // "Creator", + category: [ { - "id": number; // 354, - "name": string // "Fish & Chips Restaurant", - "icon_name": string; // "IconBriefcaseStroke" + id: number; // 354, + name: string // "Fish & Chips Restaurant", + icon_name: string; // "IconBriefcaseStroke" } ] }, - "legacy_extended_profile": { + legacy_extended_profile: { birthdate?: { day: number; // 7, month: number; // 1, @@ -270,8 +270,8 @@ type GraphQLUser = { profile_image_shape: string; // "Circle", rest_id: string; // "3784131322", }, - "is_profile_translatable": false, - "verification_info": { + is_profile_translatable: false, + verification_info: { reason: { description: { entities: { @@ -286,6 +286,6 @@ type GraphQLUser = { } } }, - "business_account": {} + business_account: Record; // {}, } \ No newline at end of file From d7ce35c413f2bf0084c1feec4af421fdc9f7acde Mon Sep 17 00:00:00 2001 From: Wazbat Date: Sat, 15 Apr 2023 19:26:58 +0200 Subject: [PATCH 03/13] =?UTF-8?q?=E2=9C=A8=20Added=20birthday=20support=20?= =?UTF-8?q?and=20fixed=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/user.ts | 14 +++++++--- src/types/types.d.ts | 5 ++++ test/index.test.ts | 63 ++++++++++++++++---------------------------- 3 files changed, 39 insertions(+), 43 deletions(-) diff --git a/src/api/user.ts b/src/api/user.ts index bd98a8b..79e4c8d 100644 --- a/src/api/user.ts +++ b/src/api/user.ts @@ -20,7 +20,7 @@ const populateUserProperties = async ( const user = response.data.user.result; /* Populating a lot of the basics */ apiUser.url = `${Constants.TWITTER_ROOT}/${user.legacy.screen_name}`; - apiUser.id = user.id; + apiUser.id = user.rest_id; apiUser.followers = user.legacy.followers_count; apiUser.following = user.legacy.friends_count; apiUser.likes = user.legacy.favourites_count; @@ -31,6 +31,14 @@ const populateUserProperties = async ( apiUser.location = user.legacy.location; apiUser.verified = user.legacy.verified; apiUser.avatar_url = user.legacy.profile_image_url_https; + apiUser.joined = user.legacy.created_at; + if (user.legacy_extended_profile?.birthdate) { + const { birthdate } = user.legacy_extended_profile; + apiUser.birthday = {}; + if (typeof birthdate.day === 'number') apiUser.birthday.day = birthdate.day; + if (typeof birthdate.month === 'number') apiUser.birthday.month = birthdate.month; + if (typeof birthdate.year === 'number') apiUser.birthday.year = birthdate.year; + } return apiUser; }; @@ -77,13 +85,13 @@ export const userAPI = async ( /* Creating the response objects */ const response: UserAPIResponse = { code: 200, message: 'OK' } as UserAPIResponse; - const apiTweet: APIUser = (await populateUserProperties( + const apiUser: APIUser = (await populateUserProperties( userResponse, language )) as APIUser; /* Finally, staple the User to the response and return it */ - response.user = apiTweet; + response.user = apiUser; writeDataPoint(event, language, 'OK', flags); diff --git a/src/types/types.d.ts b/src/types/types.d.ts index 84070f2..906164d 100644 --- a/src/types/types.d.ts +++ b/src/types/types.d.ts @@ -165,4 +165,9 @@ interface APIUser { tweets: number; likes: number; joined: string; + birthday: { + day?: number; + month?: number; + year?: number + } } \ No newline at end of file diff --git a/test/index.test.ts b/test/index.test.ts index ffbba6b..81b9bad 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -71,7 +71,7 @@ test('API fetch basic Tweet', async () => { }) ); expect(result.status).toEqual(200); - const response = (await result.json()) as APIResponse; + const response = (await result.json()) as TweetAPIResponse; expect(response).toBeTruthy(); expect(response.code).toEqual(200); expect(response.message).toEqual('OK'); @@ -104,7 +104,7 @@ test('API fetch video Tweet', async () => { }) ); expect(result.status).toEqual(200); - const response = (await result.json()) as APIResponse; + const response = (await result.json()) as TweetAPIResponse; expect(response).toBeTruthy(); expect(response.code).toEqual(200); expect(response.message).toEqual('OK'); @@ -150,7 +150,7 @@ test('API fetch multi-photo Tweet', async () => { }) ); expect(result.status).toEqual(200); - const response = (await result.json()) as APIResponse; + const response = (await result.json()) as TweetAPIResponse; expect(response).toBeTruthy(); expect(response.code).toEqual(200); expect(response.message).toEqual('OK'); @@ -195,7 +195,7 @@ test('API fetch multi-photo Tweet', async () => { // }) // ); // expect(result.status).toEqual(200); -// const response = (await result.json()) as APIResponse; +// const response = (await result.json()) as TweetAPIResponse; // expect(response).toBeTruthy(); // expect(response.code).toEqual(200); // expect(response.message).toEqual('OK'); @@ -272,7 +272,7 @@ test('API fetch poll Tweet', async () => { }) ); expect(result.status).toEqual(200); - const response = (await result.json()) as APIResponse; + const response = (await result.json()) as TweetAPIResponse; expect(response).toBeTruthy(); expect(response.code).toEqual(200); expect(response.message).toEqual('OK'); @@ -321,43 +321,26 @@ test('API fetch user', async () => { }) ); expect(result.status).toEqual(200); - const response = (await result.json()) as APIResponse; + const response = (await result.json()) as UserAPIResponse; expect(response).toBeTruthy(); expect(response.code).toEqual(200); expect(response.message).toEqual('OK'); - const tweet = response.tweet as APITweet; - expect(tweet).toBeTruthy(); - expect(tweet.url).toEqual('https://twitter.com/Twitter/status/1055475950543167488'); - expect(tweet.id).toEqual('1055475950543167488'); - expect(tweet.text).toEqual('A poll:'); - expect(tweet.author.screen_name?.toLowerCase()).toEqual('twitter'); - expect(tweet.author.name).toBeTruthy(); - expect(tweet.author.avatar_url).toBeTruthy(); - expect(tweet.author.banner_url).toBeTruthy(); - expect(tweet.author.avatar_color).toBeTruthy(); - expect(tweet.twitter_card).toEqual('tweet'); - expect(tweet.created_at).toEqual('Thu Oct 25 15:07:31 +0000 2018'); - expect(tweet.created_timestamp).toEqual(1540480051); - expect(tweet.lang).toEqual('en'); - expect(tweet.replying_to).toBeNull(); - expect(tweet.poll).toBeTruthy(); - const poll = tweet.poll as APIPoll; - expect(poll.ends_at).toEqual('2018-10-26T03:07:30Z'); - expect(poll.time_left_en).toEqual('Final results'); - expect(poll.total_votes).toEqual(54703); - - const choices = poll.choices as APIPollChoice[]; - expect(choices[0].label).toEqual('Yesssss'); - expect(choices[0].count).toEqual(14773); - expect(choices[0].percentage).toEqual(27); - expect(choices[1].label).toEqual('No'); - expect(choices[1].count).toEqual(3618); - expect(choices[1].percentage).toEqual(6.6); - expect(choices[2].label).toEqual('Maybe?'); - expect(choices[2].count).toEqual(4606); - expect(choices[2].percentage).toEqual(8.4); - expect(choices[3].label).toEqual('Just show me the results'); - expect(choices[3].count).toEqual(31706); - expect(choices[3].percentage).toEqual(58); + const user = response.user as APIUser; + expect(user).toBeTruthy(); + expect(user.url).toEqual('https://twitter.com/wazbat'); + expect(user.id).toEqual('157658332'); + expect(user.name).toEqual('Wazbat'); + expect(user.screen_name).toEqual('wazbat'); + expect(user.location).toEqual('Behind you'); + expect(user.followers).toBeGreaterThanOrEqual(1_000); + expect(user.followers).toBeLessThanOrEqual(100_000); + expect(user.following).toBeGreaterThanOrEqual(10); + expect(user.following).toBeLessThanOrEqual(1_000); + expect(user.likes).toBeGreaterThanOrEqual(10_000); + expect(user.verified).toEqual(false); + expect(user.joined).toEqual('Sun Jun 20 13:29:36 +0000 2010'); + expect(user.birthday.day).toEqual(14); + expect(user.birthday.month).toEqual(7); + expect(user.birthday.year).toBeUndefined(); }); From 747b446c7f0d027eaf0093beacdc44ad1e294b93 Mon Sep 17 00:00:00 2001 From: Wazbat Date: Sat, 15 Apr 2023 19:37:22 +0200 Subject: [PATCH 04/13] =?UTF-8?q?=F0=9F=92=AC=20Updated=20string=20constan?= =?UTF-8?q?t?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/strings.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strings.ts b/src/strings.ts index b6e8b4f..26b87d7 100644 --- a/src/strings.ts +++ b/src/strings.ts @@ -148,7 +148,7 @@ This is caused by Twitter API downtime or a new bug. Try again in a little while ERROR_API_FAIL: 'Tweet failed to load due to an API error :(', ERROR_PRIVATE: `Due to Twitter API changes, some NSFW Tweets are currently being blocked. We are currently looking into a workaround. ๐Ÿ™`, ERROR_TWEET_NOT_FOUND: `Sorry, that Tweet doesn't exist :(`, - ERROR_USER_NOT_FOUND: `Sorry, that Tweet doesn't exist :(`, + ERROR_USER_NOT_FOUND: `Sorry, that User doesn't exist :(`, ERROR_UNKNOWN: `Unknown error occurred, sorry about that :(`, TWITFIX_API_SUNSET: `The original TwitFix API has been sunset. To learn more about the FixTweet API, check out `, + ]; + + // TODO Add card creation logic here + + /* Finally, after all that work we return the response HTML! */ + return { + text: Strings.BASE_HTML.format({ + lang: `lang="en"`, + headers: headers.join('') + }), + cacheControl: null + }; }; From 14e0ab7a6173656d57d4dcb0d1c26cece2946ac6 Mon Sep 17 00:00:00 2001 From: Wazbat Date: Sat, 15 Apr 2023 20:18:57 +0200 Subject: [PATCH 07/13] =?UTF-8?q?=E2=9C=A8=20Added=20language=20path=20par?= =?UTF-8?q?am=20and=20fixed=20lint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Disabled test.only --- src/server.ts | 129 ++++++++++++++++++++++----------------------- test/index.test.ts | 2 +- 2 files changed, 65 insertions(+), 66 deletions(-) diff --git a/src/server.ts b/src/server.ts index 6b8899c..57a757c 100644 --- a/src/server.ts +++ b/src/server.ts @@ -165,76 +165,75 @@ const profileRequest = async (request: IRequest, event: FetchEvent, if (handle.match(/\w{1,15}/gi)?.[0] !== handle) { return Response.redirect(Constants.REDIRECT_URL, 302); } - const username = handle.match(/\w{1,15}/gi)?.[0]; + const username = handle.match(/\w{1,15}/gi)?.[0] as string; /* Check if request is to api.fxtwitter.com, or the tweet is appended with .json Note that unlike TwitFix, FixTweet will never generate embeds for .json, and in fact we only support .json because it's what people using TwitFix API would - be used to. */ - if ( - url.pathname.match(/\/status(es)?\/\d{2,20}\.(json)/g) !== null || - Constants.API_HOST_LIST.includes(url.hostname) - ) { - console.log('JSON API request'); - flags.api = true; - } + be used to. + */ + if ( + url.pathname.match(/\/status(es)?\/\d{2,20}\.(json)/g) !== null || + Constants.API_HOST_LIST.includes(url.hostname) + ) { + console.log('JSON API request'); + flags.api = true; + } - /* Direct media or API access bypasses bot check, returning same response regardless of UA */ - if (isBotUA || flags.direct || flags.api) { - if (isBotUA) { - console.log(`Matched bot UA ${userAgent}`); - } else { - console.log('Bypass bot check'); - } - - /* This throws the necessary data to handleStatus (in status.ts) */ - const profileResponse = await handleProfile( - handle.match(/\w{1,15}/gi)?.[0] || '', - userAgent, - flags, - language, - event - ); - - /* Complete responses are normally sent just by errors. Normal embeds send a `text` value. */ - if (profileResponse.response) { - console.log('handleProfile sent response'); - return profileResponse.response; - } else if (profileResponse.text) { - console.log('handleProfile sent embed'); - /* TODO This check has purpose in the original handleStatus handler, but I'm not sure if this edge case can happen here */ - if (!isBotUA) { - return Response.redirect(`${Constants.TWITTER_ROOT}/${handle}`, 302); - } - - let headers = Constants.RESPONSE_HEADERS; - - if (profileResponse.cacheControl) { - headers = { ...headers, 'cache-control': profileResponse.cacheControl }; - } - - /* Return the response containing embed information */ - return new Response(profileResponse.text, { - headers: headers, - status: 200 - }); - } else { - /* Somehow handleStatus sent us nothing. This should *never* happen, but we have a case for it. */ - return new Response(Strings.ERROR_UNKNOWN, { - headers: Constants.RESPONSE_HEADERS, - status: 500 - }); - } + /* Direct media or API access bypasses bot check, returning same response regardless of UA */ + if (isBotUA || flags.direct || flags.api) { + if (isBotUA) { + console.log(`Matched bot UA ${userAgent}`); } else { - /* A human has clicked a fxtwitter.com/:screen_name link! - Obviously we just need to redirect to the user directly.*/ - console.log('Matched human UA', userAgent); - return Response.redirect( - `${Constants.TWITTER_ROOT}/${handle}`, - 302 - ); + console.log('Bypass bot check'); } - - + + /* This throws the necessary data to handleStatus (in status.ts) */ + const profileResponse = await handleProfile( + username, + userAgent, + flags, + language, + event + ); + + /* Complete responses are normally sent just by errors. Normal embeds send a `text` value. */ + if (profileResponse.response) { + console.log('handleProfile sent response'); + return profileResponse.response; + } else if (profileResponse.text) { + console.log('handleProfile sent embed'); + /* TODO This check has purpose in the original handleStatus handler, but I'm not sure if this edge case can happen here */ + if (!isBotUA) { + return Response.redirect(`${Constants.TWITTER_ROOT}/${handle}`, 302); + } + + let headers = Constants.RESPONSE_HEADERS; + + if (profileResponse.cacheControl) { + headers = { ...headers, 'cache-control': profileResponse.cacheControl }; + } + + /* Return the response containing embed information */ + return new Response(profileResponse.text, { + headers: headers, + status: 200 + }); + } else { + /* Somehow handleStatus sent us nothing. This should *never* happen, but we have a case for it. */ + return new Response(Strings.ERROR_UNKNOWN, { + headers: Constants.RESPONSE_HEADERS, + status: 500 + }); + } + } else { + /* A human has clicked a fxtwitter.com/:screen_name link! + Obviously we just need to redirect to the user directly.*/ + console.log('Matched human UA', userAgent); + return Response.redirect( + `${Constants.TWITTER_ROOT}/${handle}`, + 302 + ); + } }; const genericTwitterRedirect = async (request: IRequest) => { @@ -337,7 +336,7 @@ router.get('/owoembed', async (request: IRequest) => { We don't currently have custom profile cards yet, but it's something we might do. Maybe. */ router.get('/:handle', profileRequest); -router.get('/:handle/', profileRequest); +router.get('/:handle/:language', profileRequest); router.get('/i/events/:id', genericTwitterRedirect); router.get('/hashtag/:hashtag', genericTwitterRedirect); diff --git a/test/index.test.ts b/test/index.test.ts index 810836d..81b9bad 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -313,7 +313,7 @@ test('API fetch poll Tweet', async () => { expect(choices[3].percentage).toEqual(58); }); -test.only('API fetch user', async () => { +test('API fetch user', async () => { const result = await cacheWrapper( new Request('https://api.fxtwitter.com/wazbat', { method: 'GET', From e981745d82d25dd3b5323922d5d5832ca750efb7 Mon Sep 17 00:00:00 2001 From: Wazbat Date: Sat, 15 Apr 2023 23:14:25 +0200 Subject: [PATCH 08/13] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Updated=20code=20as?= =?UTF-8?q?=20per=20code=20review?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/user.ts | 51 ++++++++---------------- src/server.ts | 18 +++------ src/strings.ts | 2 +- src/types/twitterTypes.d.ts | 77 ++++++++++++++++++++++--------------- src/types/types.d.ts | 3 +- src/user.ts | 3 +- test/index.test.ts | 28 +++++++------- 7 files changed, 83 insertions(+), 99 deletions(-) diff --git a/src/api/user.ts b/src/api/user.ts index 12a968e..10bf0d4 100644 --- a/src/api/user.ts +++ b/src/api/user.ts @@ -4,8 +4,7 @@ import { fetchUser } from '../fetch'; /* This function does the heavy lifting of processing data from Twitter API and using it to create FixTweet's streamlined API responses */ const populateUserProperties = async ( - response: GraphQLUserResponse, - language: string | undefined + response: GraphQLUserResponse // eslint-disable-next-line sonarjs/cognitive-complexity ): Promise => { const apiUser = {} as APIUser; @@ -22,7 +21,20 @@ const populateUserProperties = async ( apiUser.screen_name = user.legacy.screen_name; apiUser.description = user.legacy.description; apiUser.location = user.legacy.location; - apiUser.verified = user.legacy.verified; + if (user.is_blue_verified) { + apiUser.verified = 'blue'; + } else if (user.legacy.verified) { + if (user.legacy.verified_type === 'Business') { + apiUser.verified = 'business'; + } else if (user.legacy.verified_type === 'Government') { + apiUser.verified = 'government'; + } else { + apiUser.verified = 'legacy'; + } + } + if (apiUser.verified === 'government') { + apiUser.verified_lable = user.affiliates_highlighted_label?.label?.description || ''; + } apiUser.avatar_url = user.legacy.profile_image_url_https; apiUser.joined = user.legacy.created_at; if (user.legacy_extended_profile?.birthdate) { @@ -36,41 +48,11 @@ const populateUserProperties = async ( return apiUser; }; -const writeDataPoint = ( - event: FetchEvent, - language: string | undefined, - returnCode: string, - flags?: InputFlags -) => { - console.log('Writing data point...'); - if (typeof AnalyticsEngine !== 'undefined') { - const flagString = - Object.keys(flags || {}) - // @ts-expect-error - TypeScript doesn't like iterating over the keys, but that's OK - .filter(flag => flags?.[flag])[0] || 'standard'; - - AnalyticsEngine.writeDataPoint({ - blobs: [ - event.request.cf?.colo as string /* Datacenter location */, - event.request.cf?.country as string /* Country code */, - event.request.headers.get('user-agent') ?? - '' /* User agent (for aggregating bots calling) */, - returnCode /* Return code */, - flagString /* Type of request */, - language ?? '' /* For translate feature */ - ], - doubles: [0 /* NSFW media = 1, No NSFW Media = 0 */], - indexes: [event.request.headers.get('cf-ray') ?? '' /* CF Ray */] - }); - } -}; - /* API for Twitter profiles (Users) Used internally by FixTweet's embed service, or available for free using api.fxtwitter.com. */ export const userAPI = async ( username: string, - language: string | undefined, event: FetchEvent, flags?: InputFlags ): Promise => { @@ -80,13 +62,10 @@ export const userAPI = async ( const response: UserAPIResponse = { code: 200, message: 'OK' } as UserAPIResponse; const apiUser: APIUser = (await populateUserProperties( userResponse, - language )) as APIUser; /* Finally, staple the User to the response and return it */ response.user = apiUser; - writeDataPoint(event, language, 'OK', flags); - return response; }; diff --git a/src/server.ts b/src/server.ts index 57a757c..89b54d7 100644 --- a/src/server.ts +++ b/src/server.ts @@ -144,7 +144,7 @@ const statusRequest = async ( /* Handler for User Profiles */ const profileRequest = async (request: IRequest, event: FetchEvent, flags: InputFlags = {}) => { - const { handle, language } = request.params; + const { handle } = request.params; const url = new URL(request.url); const userAgent = request.headers.get('User-Agent') || ''; @@ -166,21 +166,14 @@ const profileRequest = async (request: IRequest, event: FetchEvent, return Response.redirect(Constants.REDIRECT_URL, 302); } const username = handle.match(/\w{1,15}/gi)?.[0] as string; - /* Check if request is to api.fxtwitter.com, or the tweet is appended with .json - Note that unlike TwitFix, FixTweet will never generate embeds for .json, and - in fact we only support .json because it's what people using TwitFix API would - be used to. - */ - if ( - url.pathname.match(/\/status(es)?\/\d{2,20}\.(json)/g) !== null || - Constants.API_HOST_LIST.includes(url.hostname) - ) { + /* Check if request is to api.fxtwitter.com */ + if (Constants.API_HOST_LIST.includes(url.hostname)) { console.log('JSON API request'); flags.api = true; } /* Direct media or API access bypasses bot check, returning same response regardless of UA */ - if (isBotUA || flags.direct || flags.api) { + if (isBotUA || flags.api) { if (isBotUA) { console.log(`Matched bot UA ${userAgent}`); } else { @@ -192,7 +185,6 @@ const profileRequest = async (request: IRequest, event: FetchEvent, username, userAgent, flags, - language, event ); @@ -336,7 +328,7 @@ router.get('/owoembed', async (request: IRequest) => { We don't currently have custom profile cards yet, but it's something we might do. Maybe. */ router.get('/:handle', profileRequest); -router.get('/:handle/:language', profileRequest); +router.get('/:handle/', profileRequest); router.get('/i/events/:id', genericTwitterRedirect); router.get('/hashtag/:hashtag', genericTwitterRedirect); diff --git a/src/strings.ts b/src/strings.ts index 26b87d7..540c368 100644 --- a/src/strings.ts +++ b/src/strings.ts @@ -148,7 +148,7 @@ This is caused by Twitter API downtime or a new bug. Try again in a little while ERROR_API_FAIL: 'Tweet failed to load due to an API error :(', ERROR_PRIVATE: `Due to Twitter API changes, some NSFW Tweets are currently being blocked. We are currently looking into a workaround. ๐Ÿ™`, ERROR_TWEET_NOT_FOUND: `Sorry, that Tweet doesn't exist :(`, - ERROR_USER_NOT_FOUND: `Sorry, that User doesn't exist :(`, + ERROR_USER_NOT_FOUND: `Sorry, that user doesn't exist :(`, ERROR_UNKNOWN: `Unknown error occurred, sorry about that :(`, TWITFIX_API_SUNSET: `The original TwitFix API has been sunset. To learn more about the FixTweet API, check out ; // {}, + __typename: "User"; + id: string; // "VXNlcjo3ODMyMTQ=" + rest_id: string; // "783214", + affiliates_highlighted_label: { + label?: { + badge?: { + url?: string; // "https://pbs.twimg.com/semantic_core_img/1290392753013002240/mWq1iE5L?format=png&name=orig" + } + description?: string; // "United States government organization" + url?: { + url?: string; // "https://help.twitter.com/rules-and-policies/state-affiliated" + urlType: string; // "DeepLink" + } + } + } + business_account: { + affiliates_count?: 20 + } is_blue_verified: boolean; // false, - profile_image_shape: 'Circle' | 'Square'; // "Circle", + profile_image_shape: 'Circle' | 'Square'|'Hexagon'; // "Circle", + has_nft_avatar: boolean; // false, legacy: { - created_at: string; // "Sat Sep 26 17:20:55 +0000 2015", + created_at: string; // "Tue Feb 20 14:35:54 +0000 2007", default_profile: boolean // false, default_profile_image: boolean // false, - description: string; // "dangered wolf#3621 https://t.co/eBTS4kksMw", + description: string; // "What's happening?!", entities: { - description: { - urls: { - display_url: string; // "t.me/dangeredwolf", - expanded_url: string; // "http://t.me/dangeredwolf", - url: string; // "https://t.co/eBTS4kksMw", + description?: { + urls?: { + display_url: string; // "about.twitter.com", + expanded_url: string; // "https://about.twitter.com/", + url: string; // "https://t.co/DAtOo6uuHk", indices: [ - 19, - 42 + 0, + 23 ] }[] } @@ -232,29 +247,30 @@ type GraphQLUser = { friends_count: number; // 2125, has_custom_timelines: boolean; // true, is_translator: boolean; // false, - listed_count: number; // 69, - location: string; // "they/them", + listed_count: number; // 88165, + location: string; // "everywhere", media_count: number; // 20839, - name: string; // "dangered wolf", - normal_followers_count: number; // 4996, - pinned_tweet_ids_str: string[]; // Array of tweet ids + name: string; // "Twitter", + normal_followers_count: number; // 65669107, + pinned_tweet_ids_str: string[]; // Array of tweet ids, usually one. Empty if no pinned tweet possibly_sensitive: boolean; // false, - profile_banner_url: string; // "https://pbs.twimg.com/profile_banners/3784131322/1658599775", - profile_image_url_https: string; // "https://pbs.twimg.com/profile_images/1555638673705783299/3gaaetxC_normal.jpg", + profile_banner_url: string; // "https://pbs.twimg.com/profile_banners/783214/1646075315", + profile_image_url_https: string; // "https://pbs.twimg.com/profile_images/1488548719062654976/u6qfBBkF_normal.jpg", profile_interstitial_type: string; // "", - screen_name: string; // "dangeredwolf", - statuses_count: number; // 108222, - translator_type: string; // "regular", - verified: boolean; // false, + screen_name: string; // "Twitter", + statuses_count: number; // 15047 + translator_type: string; // "regular" + verified: boolean; // false + verified_type: 'Business'|'Government'; withheld_in_countries: [] }, professional: { - rest_id: string; // "1508134739420536845", + rest_id: string; // "1503055759638159366", professional_type: string; // "Creator", category: [ { id: number; // 354, - name: string // "Fish & Chips Restaurant", + name: string // "Community", icon_name: string; // "IconBriefcaseStroke" } ] @@ -268,7 +284,7 @@ type GraphQLUser = { year_visibility: string; // "Public" }; profile_image_shape: string; // "Circle", - rest_id: string; // "3784131322", + rest_id: string; // "783214", }, is_profile_translatable: false, verification_info: { @@ -282,10 +298,9 @@ type GraphQLUser = { }; to_index: number; // 108 }[]; - text: string; // "This account is verified because itโ€™s subscribed to Twitter Blue or is a legacy verified account. Learn more" + text?: 'This account is verified because itโ€™s subscribed to Twitter Blue or is a legacy verified account. Learn more'|'This account is verified because it\'s an official organisation on Twitter. Learn more'; } } - }, - business_account: Record; // {}, + } } \ No newline at end of file diff --git a/src/types/types.d.ts b/src/types/types.d.ts index 906164d..6faa4cf 100644 --- a/src/types/types.d.ts +++ b/src/types/types.d.ts @@ -159,7 +159,8 @@ interface APIUser { location: string; url: string; protected: boolean; - verified: boolean; + verified: 'legacy' | 'blue'| 'business' | 'government'; + verified_lable: string; followers: number; following: number; tweets: number; diff --git a/src/user.ts b/src/user.ts index a41fc8a..d92d163 100644 --- a/src/user.ts +++ b/src/user.ts @@ -19,12 +19,11 @@ export const handleProfile = async ( username: string, userAgent?: string, flags?: InputFlags, - language?: string, event?: FetchEvent ): Promise => { console.log('Direct?', flags?.direct); - const api = await userAPI(username, language, event as FetchEvent); + const api = await userAPI(username, event as FetchEvent); const user = api?.user as APIUser; /* Catch this request if it's an API response */ diff --git a/test/index.test.ts b/test/index.test.ts index 81b9bad..8045e61 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -315,7 +315,7 @@ test('API fetch poll Tweet', async () => { test('API fetch user', async () => { const result = await cacheWrapper( - new Request('https://api.fxtwitter.com/wazbat', { + new Request('https://api.fxtwitter.com/twitter', { method: 'GET', headers: botHeaders }) @@ -328,19 +328,17 @@ test('API fetch user', async () => { const user = response.user as APIUser; expect(user).toBeTruthy(); - expect(user.url).toEqual('https://twitter.com/wazbat'); - expect(user.id).toEqual('157658332'); - expect(user.name).toEqual('Wazbat'); - expect(user.screen_name).toEqual('wazbat'); - expect(user.location).toEqual('Behind you'); - expect(user.followers).toBeGreaterThanOrEqual(1_000); - expect(user.followers).toBeLessThanOrEqual(100_000); - expect(user.following).toBeGreaterThanOrEqual(10); - expect(user.following).toBeLessThanOrEqual(1_000); - expect(user.likes).toBeGreaterThanOrEqual(10_000); - expect(user.verified).toEqual(false); - expect(user.joined).toEqual('Sun Jun 20 13:29:36 +0000 2010'); - expect(user.birthday.day).toEqual(14); - expect(user.birthday.month).toEqual(7); + expect(user.url).toEqual('https://twitter.com/Twitter'); + expect(user.id).toEqual('783214'); + expect(user.screen_name).toEqual('Twitter'); + expect(user.followers).toEqual(expect.any(Number)); + expect(user.following).toEqual(expect.any(Number)); + // The official twitter account will never be following as many people as it has followers + expect(user.following).not.toEqual(user.followers); + expect(user.likes).toEqual(expect.any(Number)); + expect(user.verified).toEqual('business'); + expect(user.joined).toEqual('Tue Feb 20 14:35:54 +0000 2007'); + expect(user.birthday.day).toEqual(21); + expect(user.birthday.month).toEqual(3); expect(user.birthday.year).toBeUndefined(); }); From 4983e1a58e01a1d862838905d0cb0b35ce405b34 Mon Sep 17 00:00:00 2001 From: Wazbat Date: Sun, 16 Apr 2023 21:03:16 +0200 Subject: [PATCH 09/13] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Fixed=20spelling=20e?= =?UTF-8?q?rror?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/user.ts | 2 +- src/types/types.d.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/api/user.ts b/src/api/user.ts index 10bf0d4..6d03c36 100644 --- a/src/api/user.ts +++ b/src/api/user.ts @@ -33,7 +33,7 @@ const populateUserProperties = async ( } } if (apiUser.verified === 'government') { - apiUser.verified_lable = user.affiliates_highlighted_label?.label?.description || ''; + apiUser.verified_label = user.affiliates_highlighted_label?.label?.description || ''; } apiUser.avatar_url = user.legacy.profile_image_url_https; apiUser.joined = user.legacy.created_at; diff --git a/src/types/types.d.ts b/src/types/types.d.ts index 6faa4cf..45666de 100644 --- a/src/types/types.d.ts +++ b/src/types/types.d.ts @@ -160,7 +160,7 @@ interface APIUser { url: string; protected: boolean; verified: 'legacy' | 'blue'| 'business' | 'government'; - verified_lable: string; + verified_label: string; followers: number; following: number; tweets: number; From fcc47649f609e3ff6395e7045035249c76c9bcb3 Mon Sep 17 00:00:00 2001 From: Wazbat Date: Wed, 19 Apr 2023 15:05:58 +0200 Subject: [PATCH 10/13] =?UTF-8?q?=F0=9F=8F=B7=EF=B8=8F=20Split=20user=20in?= =?UTF-8?q?to=20common=20shared=20type?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/types/types.d.ts | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/types/types.d.ts b/src/types/types.d.ts index 45666de..f4fc0a2 100644 --- a/src/types/types.d.ts +++ b/src/types/types.d.ts @@ -58,14 +58,15 @@ interface APITranslate { source_lang_en: string; target_lang: string; } - -interface APIAuthor { +interface BaseUser { name?: string; screen_name?: string; avatar_url?: string; - avatar_color: string; banner_url?: string; } +interface APITweetAuthor extends BaseUser { + avatar_color: string; +} interface APIExternalMedia { type: 'video'; @@ -129,7 +130,7 @@ interface APITweet { quote?: APITweet; poll?: APIPoll; translation?: APITranslate; - author: APIAuthor; + author: APITweetAuthor; media?: { external?: APIExternalMedia; @@ -148,13 +149,8 @@ interface APITweet { twitter_card: 'tweet' | 'summary' | 'summary_large_image' | 'player'; } -interface APIUser { +interface APIUser extends BaseUser { id: string; - name: string; - screen_name: string; - avatar_url: string; - banner_url: string; - avatar_color: string; description: string; location: string; url: string; From db9b3f651599b2c294700411df071c8a8bc199e5 Mon Sep 17 00:00:00 2001 From: Wazbat Date: Wed, 19 Apr 2023 15:15:45 +0200 Subject: [PATCH 11/13] =?UTF-8?q?=E2=9C=A8=20Added=20id=20field=20to=20sta?= =?UTF-8?q?tus=20responses?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/status.ts | 1 + src/types/types.d.ts | 2 +- test/index.test.ts | 154 ++++++++++++++++++++++--------------------- 3 files changed, 81 insertions(+), 76 deletions(-) diff --git a/src/api/status.ts b/src/api/status.ts index cc6d648..80d45a9 100644 --- a/src/api/status.ts +++ b/src/api/status.ts @@ -32,6 +32,7 @@ const populateTweetProperties = async ( apiTweet.id = tweet.id_str; apiTweet.text = unescapeText(linkFixer(tweet, tweet.full_text || '')); apiTweet.author = { + id: tweet.user_id_str, name: name, screen_name: screenName, avatar_url: diff --git a/src/types/types.d.ts b/src/types/types.d.ts index f4fc0a2..cb995bf 100644 --- a/src/types/types.d.ts +++ b/src/types/types.d.ts @@ -59,6 +59,7 @@ interface APITranslate { target_lang: string; } interface BaseUser { + id?: string; name?: string; screen_name?: string; avatar_url?: string; @@ -150,7 +151,6 @@ interface APITweet { } interface APIUser extends BaseUser { - id: string; description: string; location: string; url: string; diff --git a/test/index.test.ts b/test/index.test.ts index 8045e61..6778650 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -82,6 +82,7 @@ test('API fetch basic Tweet', async () => { expect(tweet.id).toEqual('20'); expect(tweet.text).toEqual('just setting up my twttr'); expect(tweet.author.screen_name?.toLowerCase()).toEqual('jack'); + expect(tweet.author.id).toEqual('12'); expect(tweet.author.name).toBeTruthy(); expect(tweet.author.avatar_url).toBeTruthy(); expect(tweet.author.banner_url).toBeTruthy(); @@ -117,6 +118,7 @@ test('API fetch video Tweet', async () => { 'Get the sauces ready, #NuggsForCarter has 3 million+ Retweets.' ); expect(tweet.author.screen_name?.toLowerCase()).toEqual('twitter'); + expect(tweet.author.id).toEqual('783214'); expect(tweet.author.name).toBeTruthy(); expect(tweet.author.avatar_url).toBeTruthy(); expect(tweet.author.banner_url).toBeTruthy(); @@ -161,6 +163,7 @@ test('API fetch multi-photo Tweet', async () => { expect(tweet.id).toEqual('1445094085593866246'); expect(tweet.text).toEqual('@netflix'); expect(tweet.author.screen_name?.toLowerCase()).toEqual('twitter'); + expect(tweet.author.id).toEqual('783214'); expect(tweet.author.name).toBeTruthy(); expect(tweet.author.avatar_url).toBeTruthy(); expect(tweet.author.banner_url).toBeTruthy(); @@ -187,82 +190,82 @@ test('API fetch multi-photo Tweet', async () => { ); }); -// test('API fetch multi-video Tweet', async () => { -// const result = await cacheWrapper( -// new Request('https://api.fxtwitter.com/dangeredwolf/status/1557914172763127808', { -// method: 'GET', -// headers: botHeaders -// }) -// ); -// expect(result.status).toEqual(200); -// const response = (await result.json()) as TweetAPIResponse; -// expect(response).toBeTruthy(); -// expect(response.code).toEqual(200); -// expect(response.message).toEqual('OK'); +test.skip('API fetch multi-video Tweet', async () => { + const result = await cacheWrapper( + new Request('https://api.fxtwitter.com/dangeredwolf/status/1557914172763127808', { + method: 'GET', + headers: botHeaders + }) + ); + expect(result.status).toEqual(200); + const response = (await result.json()) as TweetAPIResponse; + expect(response).toBeTruthy(); + expect(response.code).toEqual(200); + expect(response.message).toEqual('OK'); -// const tweet = response.tweet as APITweet; -// expect(tweet).toBeTruthy(); -// expect(tweet.url).toEqual( -// 'https://twitter.com/dangeredwolf/status/1557914172763127808' -// ); -// expect(tweet.id).toEqual('1557914172763127808'); -// expect(tweet.text).toEqual(''); -// expect(tweet.author.screen_name?.toLowerCase()).toEqual('dangeredwolf'); -// expect(tweet.author.name).toBeTruthy(); -// expect(tweet.author.avatar_url).toBeTruthy(); -// expect(tweet.author.banner_url).toBeTruthy(); -// expect(tweet.author.avatar_color).toBeTruthy(); -// expect(tweet.twitter_card).toEqual('player'); -// expect(tweet.created_at).toEqual('Fri Aug 12 02:17:38 +0000 2022'); -// expect(tweet.created_timestamp).toEqual(1660270658); -// expect(tweet.replying_to).toBeNull(); -// expect(tweet.media?.videos).toBeTruthy(); -// const videos = tweet.media?.videos as APIVideo[]; -// expect(videos[0].url).toEqual( -// 'https://video.twimg.com/ext_tw_video/1539029945124528130/pu/vid/1662x1080/ZQP4eoQhnGnKcLEb.mp4?tag=14' -// ); -// expect(videos[0].thumbnail_url).toEqual( -// 'https://pbs.twimg.com/ext_tw_video_thumb/1539029945124528130/pu/img/6Z1MXMliums60j03.jpg' -// ); -// expect(videos[0].width).toEqual(3548); -// expect(videos[0].height).toEqual(2304); -// expect(videos[0].duration).toEqual(37.75); -// expect(videos[0].format).toEqual('video/mp4'); -// expect(videos[0].type).toEqual('video'); -// expect(videos[1].url).toEqual( -// 'https://video.twimg.com/ext_tw_video/1543316856697769984/pu/vid/1920x1080/3fo7b4EnWv2WO8Z1.mp4?tag=14' -// ); -// expect(videos[1].thumbnail_url).toEqual( -// 'https://pbs.twimg.com/ext_tw_video_thumb/1543316856697769984/pu/img/eCl67JRWO8r4r8A4.jpg' -// ); -// expect(videos[1].width).toEqual(1920); -// expect(videos[1].height).toEqual(1080); -// expect(videos[1].duration).toEqual(71.855); -// expect(videos[1].format).toEqual('video/mp4'); -// expect(videos[1].type).toEqual('video'); -// expect(videos[2].url).toEqual( -// 'https://video.twimg.com/ext_tw_video/1543797953105625088/pu/vid/1920x1080/GHSLxzBrwiDLhLYD.mp4?tag=14' -// ); -// expect(videos[2].thumbnail_url).toEqual( -// 'https://pbs.twimg.com/ext_tw_video_thumb/1543797953105625088/pu/img/2eX2QQkd7b2S1YDl.jpg' -// ); -// expect(videos[2].width).toEqual(1920); -// expect(videos[2].height).toEqual(1080); -// expect(videos[2].duration).toEqual(22.018); -// expect(videos[2].format).toEqual('video/mp4'); -// expect(videos[2].type).toEqual('video'); -// expect(videos[3].url).toEqual( -// 'https://video.twimg.com/ext_tw_video/1548602342488129536/pu/vid/720x1280/I_D3svYfjBl7_xGS.mp4?tag=14' -// ); -// expect(videos[3].thumbnail_url).toEqual( -// 'https://pbs.twimg.com/ext_tw_video_thumb/1548602342488129536/pu/img/V_1u5Nv5BwKBynwv.jpg' -// ); -// expect(videos[3].width).toEqual(720); -// expect(videos[3].height).toEqual(1280); -// expect(videos[3].duration).toEqual(25.133); -// expect(videos[3].format).toEqual('video/mp4'); -// expect(videos[3].type).toEqual('video'); -// }); + const tweet = response.tweet as APITweet; + expect(tweet).toBeTruthy(); + expect(tweet.url).toEqual( + 'https://twitter.com/dangeredwolf/status/1557914172763127808' + ); + expect(tweet.id).toEqual('1557914172763127808'); + expect(tweet.text).toEqual(''); + expect(tweet.author.screen_name?.toLowerCase()).toEqual('dangeredwolf'); + expect(tweet.author.name).toBeTruthy(); + expect(tweet.author.avatar_url).toBeTruthy(); + expect(tweet.author.banner_url).toBeTruthy(); + expect(tweet.author.avatar_color).toBeTruthy(); + expect(tweet.twitter_card).toEqual('player'); + expect(tweet.created_at).toEqual('Fri Aug 12 02:17:38 +0000 2022'); + expect(tweet.created_timestamp).toEqual(1660270658); + expect(tweet.replying_to).toBeNull(); + expect(tweet.media?.videos).toBeTruthy(); + const videos = tweet.media?.videos as APIVideo[]; + expect(videos[0].url).toEqual( + 'https://video.twimg.com/ext_tw_video/1539029945124528130/pu/vid/1662x1080/ZQP4eoQhnGnKcLEb.mp4?tag=14' + ); + expect(videos[0].thumbnail_url).toEqual( + 'https://pbs.twimg.com/ext_tw_video_thumb/1539029945124528130/pu/img/6Z1MXMliums60j03.jpg' + ); + expect(videos[0].width).toEqual(3548); + expect(videos[0].height).toEqual(2304); + expect(videos[0].duration).toEqual(37.75); + expect(videos[0].format).toEqual('video/mp4'); + expect(videos[0].type).toEqual('video'); + expect(videos[1].url).toEqual( + 'https://video.twimg.com/ext_tw_video/1543316856697769984/pu/vid/1920x1080/3fo7b4EnWv2WO8Z1.mp4?tag=14' + ); + expect(videos[1].thumbnail_url).toEqual( + 'https://pbs.twimg.com/ext_tw_video_thumb/1543316856697769984/pu/img/eCl67JRWO8r4r8A4.jpg' + ); + expect(videos[1].width).toEqual(1920); + expect(videos[1].height).toEqual(1080); + expect(videos[1].duration).toEqual(71.855); + expect(videos[1].format).toEqual('video/mp4'); + expect(videos[1].type).toEqual('video'); + expect(videos[2].url).toEqual( + 'https://video.twimg.com/ext_tw_video/1543797953105625088/pu/vid/1920x1080/GHSLxzBrwiDLhLYD.mp4?tag=14' + ); + expect(videos[2].thumbnail_url).toEqual( + 'https://pbs.twimg.com/ext_tw_video_thumb/1543797953105625088/pu/img/2eX2QQkd7b2S1YDl.jpg' + ); + expect(videos[2].width).toEqual(1920); + expect(videos[2].height).toEqual(1080); + expect(videos[2].duration).toEqual(22.018); + expect(videos[2].format).toEqual('video/mp4'); + expect(videos[2].type).toEqual('video'); + expect(videos[3].url).toEqual( + 'https://video.twimg.com/ext_tw_video/1548602342488129536/pu/vid/720x1280/I_D3svYfjBl7_xGS.mp4?tag=14' + ); + expect(videos[3].thumbnail_url).toEqual( + 'https://pbs.twimg.com/ext_tw_video_thumb/1548602342488129536/pu/img/V_1u5Nv5BwKBynwv.jpg' + ); + expect(videos[3].width).toEqual(720); + expect(videos[3].height).toEqual(1280); + expect(videos[3].duration).toEqual(25.133); + expect(videos[3].format).toEqual('video/mp4'); + expect(videos[3].type).toEqual('video'); +}); test('API fetch poll Tweet', async () => { const result = await cacheWrapper( @@ -283,6 +286,7 @@ test('API fetch poll Tweet', async () => { expect(tweet.id).toEqual('1055475950543167488'); expect(tweet.text).toEqual('A poll:'); expect(tweet.author.screen_name?.toLowerCase()).toEqual('twitter'); + expect(tweet.author.id).toEqual('783214'); expect(tweet.author.name).toBeTruthy(); expect(tweet.author.avatar_url).toBeTruthy(); expect(tweet.author.banner_url).toBeTruthy(); From d5d29f1502474f170740a4cc5fd4b08ff7d18c75 Mon Sep 17 00:00:00 2001 From: Wazbat Date: Wed, 19 Apr 2023 15:20:12 +0200 Subject: [PATCH 12/13] =?UTF-8?q?=E2=9A=B0=EF=B8=8F=20Removed=20test=20as?= =?UTF-8?q?=20per=20request?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/index.test.ts | 76 ---------------------------------------------- 1 file changed, 76 deletions(-) diff --git a/test/index.test.ts b/test/index.test.ts index 6778650..a05da47 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -190,82 +190,6 @@ test('API fetch multi-photo Tweet', async () => { ); }); -test.skip('API fetch multi-video Tweet', async () => { - const result = await cacheWrapper( - new Request('https://api.fxtwitter.com/dangeredwolf/status/1557914172763127808', { - method: 'GET', - headers: botHeaders - }) - ); - expect(result.status).toEqual(200); - const response = (await result.json()) as TweetAPIResponse; - expect(response).toBeTruthy(); - expect(response.code).toEqual(200); - expect(response.message).toEqual('OK'); - - const tweet = response.tweet as APITweet; - expect(tweet).toBeTruthy(); - expect(tweet.url).toEqual( - 'https://twitter.com/dangeredwolf/status/1557914172763127808' - ); - expect(tweet.id).toEqual('1557914172763127808'); - expect(tweet.text).toEqual(''); - expect(tweet.author.screen_name?.toLowerCase()).toEqual('dangeredwolf'); - expect(tweet.author.name).toBeTruthy(); - expect(tweet.author.avatar_url).toBeTruthy(); - expect(tweet.author.banner_url).toBeTruthy(); - expect(tweet.author.avatar_color).toBeTruthy(); - expect(tweet.twitter_card).toEqual('player'); - expect(tweet.created_at).toEqual('Fri Aug 12 02:17:38 +0000 2022'); - expect(tweet.created_timestamp).toEqual(1660270658); - expect(tweet.replying_to).toBeNull(); - expect(tweet.media?.videos).toBeTruthy(); - const videos = tweet.media?.videos as APIVideo[]; - expect(videos[0].url).toEqual( - 'https://video.twimg.com/ext_tw_video/1539029945124528130/pu/vid/1662x1080/ZQP4eoQhnGnKcLEb.mp4?tag=14' - ); - expect(videos[0].thumbnail_url).toEqual( - 'https://pbs.twimg.com/ext_tw_video_thumb/1539029945124528130/pu/img/6Z1MXMliums60j03.jpg' - ); - expect(videos[0].width).toEqual(3548); - expect(videos[0].height).toEqual(2304); - expect(videos[0].duration).toEqual(37.75); - expect(videos[0].format).toEqual('video/mp4'); - expect(videos[0].type).toEqual('video'); - expect(videos[1].url).toEqual( - 'https://video.twimg.com/ext_tw_video/1543316856697769984/pu/vid/1920x1080/3fo7b4EnWv2WO8Z1.mp4?tag=14' - ); - expect(videos[1].thumbnail_url).toEqual( - 'https://pbs.twimg.com/ext_tw_video_thumb/1543316856697769984/pu/img/eCl67JRWO8r4r8A4.jpg' - ); - expect(videos[1].width).toEqual(1920); - expect(videos[1].height).toEqual(1080); - expect(videos[1].duration).toEqual(71.855); - expect(videos[1].format).toEqual('video/mp4'); - expect(videos[1].type).toEqual('video'); - expect(videos[2].url).toEqual( - 'https://video.twimg.com/ext_tw_video/1543797953105625088/pu/vid/1920x1080/GHSLxzBrwiDLhLYD.mp4?tag=14' - ); - expect(videos[2].thumbnail_url).toEqual( - 'https://pbs.twimg.com/ext_tw_video_thumb/1543797953105625088/pu/img/2eX2QQkd7b2S1YDl.jpg' - ); - expect(videos[2].width).toEqual(1920); - expect(videos[2].height).toEqual(1080); - expect(videos[2].duration).toEqual(22.018); - expect(videos[2].format).toEqual('video/mp4'); - expect(videos[2].type).toEqual('video'); - expect(videos[3].url).toEqual( - 'https://video.twimg.com/ext_tw_video/1548602342488129536/pu/vid/720x1280/I_D3svYfjBl7_xGS.mp4?tag=14' - ); - expect(videos[3].thumbnail_url).toEqual( - 'https://pbs.twimg.com/ext_tw_video_thumb/1548602342488129536/pu/img/V_1u5Nv5BwKBynwv.jpg' - ); - expect(videos[3].width).toEqual(720); - expect(videos[3].height).toEqual(1280); - expect(videos[3].duration).toEqual(25.133); - expect(videos[3].format).toEqual('video/mp4'); - expect(videos[3].type).toEqual('video'); -}); test('API fetch poll Tweet', async () => { const result = await cacheWrapper( From d664a3cfde20db91856db22e414df536dc05b4d6 Mon Sep 17 00:00:00 2001 From: Wazbat Date: Tue, 25 Apr 2023 00:14:35 +0200 Subject: [PATCH 13/13] =?UTF-8?q?=F0=9F=94=A5=20Commented=20out=20verified?= =?UTF-8?q?=20functionality?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/user.ts | 3 +++ src/types/types.d.ts | 4 ++-- test/index.test.ts | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/api/user.ts b/src/api/user.ts index 6d03c36..7cbc488 100644 --- a/src/api/user.ts +++ b/src/api/user.ts @@ -21,6 +21,7 @@ const populateUserProperties = async ( apiUser.screen_name = user.legacy.screen_name; apiUser.description = user.legacy.description; apiUser.location = user.legacy.location; + /* if (user.is_blue_verified) { apiUser.verified = 'blue'; } else if (user.legacy.verified) { @@ -32,9 +33,11 @@ const populateUserProperties = async ( apiUser.verified = 'legacy'; } } + if (apiUser.verified === 'government') { apiUser.verified_label = user.affiliates_highlighted_label?.label?.description || ''; } + */ apiUser.avatar_url = user.legacy.profile_image_url_https; apiUser.joined = user.legacy.created_at; if (user.legacy_extended_profile?.birthdate) { diff --git a/src/types/types.d.ts b/src/types/types.d.ts index cb995bf..6582f02 100644 --- a/src/types/types.d.ts +++ b/src/types/types.d.ts @@ -155,8 +155,8 @@ interface APIUser extends BaseUser { location: string; url: string; protected: boolean; - verified: 'legacy' | 'blue'| 'business' | 'government'; - verified_label: string; + // verified: 'legacy' | 'blue'| 'business' | 'government'; + // verified_label: string; followers: number; following: number; tweets: number; diff --git a/test/index.test.ts b/test/index.test.ts index a05da47..44ab781 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -264,7 +264,7 @@ test('API fetch user', async () => { // The official twitter account will never be following as many people as it has followers expect(user.following).not.toEqual(user.followers); expect(user.likes).toEqual(expect.any(Number)); - expect(user.verified).toEqual('business'); + // expect(user.verified).toEqual('business'); expect(user.joined).toEqual('Tue Feb 20 14:35:54 +0000 2007'); expect(user.birthday.day).toEqual(21); expect(user.birthday.month).toEqual(3);