mirror of
https://github.com/CompeyDev/fxtwitter-docker.git
synced 2025-04-04 10:00:55 +01:00
♻️ Updated code as per code review
This commit is contained in:
parent
14e0ab7a61
commit
e981745d82
7 changed files with 83 additions and 99 deletions
|
@ -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<APIUser> => {
|
||||
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<UserAPIResponse> => {
|
||||
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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 <a href="https://${
|
||||
|
|
77
src/types/twitterTypes.d.ts
vendored
77
src/types/twitterTypes.d.ts
vendored
|
@ -202,26 +202,41 @@ type GraphQLUserResponse = {
|
|||
}
|
||||
|
||||
type GraphQLUser = {
|
||||
__typename: "User",
|
||||
id: string; // "VXNlcjozNzg0MTMxMzIy",
|
||||
rest_id: string; // "3784131322",
|
||||
affiliates_highlighted_label: Record<string, unknown>; // {},
|
||||
__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<string, unknown>; // {},
|
||||
}
|
||||
|
||||
}
|
3
src/types/types.d.ts
vendored
3
src/types/types.d.ts
vendored
|
@ -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;
|
||||
|
|
|
@ -19,12 +19,11 @@ export const handleProfile = async (
|
|||
username: string,
|
||||
userAgent?: string,
|
||||
flags?: InputFlags,
|
||||
language?: string,
|
||||
event?: FetchEvent
|
||||
): Promise<StatusResponse> => {
|
||||
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 */
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
|
Loading…
Add table
Reference in a new issue