From e981745d82d25dd3b5323922d5d5832ca750efb7 Mon Sep 17 00:00:00 2001 From: Wazbat Date: Sat, 15 Apr 2023 23:14:25 +0200 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Updated=20code=20as=20per?= =?UTF-8?q?=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(); });