mirror of
https://github.com/CompeyDev/fxtwitter-docker.git
synced 2025-04-10 21:10:54 +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
|
/* This function does the heavy lifting of processing data from Twitter API
|
||||||
and using it to create FixTweet's streamlined API responses */
|
and using it to create FixTweet's streamlined API responses */
|
||||||
const populateUserProperties = async (
|
const populateUserProperties = async (
|
||||||
response: GraphQLUserResponse,
|
response: GraphQLUserResponse
|
||||||
language: string | undefined
|
|
||||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||||
): Promise<APIUser> => {
|
): Promise<APIUser> => {
|
||||||
const apiUser = {} as APIUser;
|
const apiUser = {} as APIUser;
|
||||||
|
@ -22,7 +21,20 @@ const populateUserProperties = async (
|
||||||
apiUser.screen_name = user.legacy.screen_name;
|
apiUser.screen_name = user.legacy.screen_name;
|
||||||
apiUser.description = user.legacy.description;
|
apiUser.description = user.legacy.description;
|
||||||
apiUser.location = user.legacy.location;
|
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.avatar_url = user.legacy.profile_image_url_https;
|
||||||
apiUser.joined = user.legacy.created_at;
|
apiUser.joined = user.legacy.created_at;
|
||||||
if (user.legacy_extended_profile?.birthdate) {
|
if (user.legacy_extended_profile?.birthdate) {
|
||||||
|
@ -36,41 +48,11 @@ const populateUserProperties = async (
|
||||||
return apiUser;
|
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)
|
/* API for Twitter profiles (Users)
|
||||||
Used internally by FixTweet's embed service, or
|
Used internally by FixTweet's embed service, or
|
||||||
available for free using api.fxtwitter.com. */
|
available for free using api.fxtwitter.com. */
|
||||||
export const userAPI = async (
|
export const userAPI = async (
|
||||||
username: string,
|
username: string,
|
||||||
language: string | undefined,
|
|
||||||
event: FetchEvent,
|
event: FetchEvent,
|
||||||
flags?: InputFlags
|
flags?: InputFlags
|
||||||
): Promise<UserAPIResponse> => {
|
): Promise<UserAPIResponse> => {
|
||||||
|
@ -80,13 +62,10 @@ export const userAPI = async (
|
||||||
const response: UserAPIResponse = { code: 200, message: 'OK' } as UserAPIResponse;
|
const response: UserAPIResponse = { code: 200, message: 'OK' } as UserAPIResponse;
|
||||||
const apiUser: APIUser = (await populateUserProperties(
|
const apiUser: APIUser = (await populateUserProperties(
|
||||||
userResponse,
|
userResponse,
|
||||||
language
|
|
||||||
)) as APIUser;
|
)) as APIUser;
|
||||||
|
|
||||||
/* Finally, staple the User to the response and return it */
|
/* Finally, staple the User to the response and return it */
|
||||||
response.user = apiUser;
|
response.user = apiUser;
|
||||||
|
|
||||||
writeDataPoint(event, language, 'OK', flags);
|
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
};
|
};
|
||||||
|
|
|
@ -144,7 +144,7 @@ const statusRequest = async (
|
||||||
/* Handler for User Profiles */
|
/* Handler for User Profiles */
|
||||||
const profileRequest = async (request: IRequest, event: FetchEvent,
|
const profileRequest = async (request: IRequest, event: FetchEvent,
|
||||||
flags: InputFlags = {}) => {
|
flags: InputFlags = {}) => {
|
||||||
const { handle, language } = request.params;
|
const { handle } = request.params;
|
||||||
const url = new URL(request.url);
|
const url = new URL(request.url);
|
||||||
const userAgent = request.headers.get('User-Agent') || '';
|
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);
|
return Response.redirect(Constants.REDIRECT_URL, 302);
|
||||||
}
|
}
|
||||||
const username = handle.match(/\w{1,15}/gi)?.[0] as string;
|
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
|
/* Check if request is to api.fxtwitter.com */
|
||||||
Note that unlike TwitFix, FixTweet will never generate embeds for .json, and
|
if (Constants.API_HOST_LIST.includes(url.hostname)) {
|
||||||
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');
|
console.log('JSON API request');
|
||||||
flags.api = true;
|
flags.api = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Direct media or API access bypasses bot check, returning same response regardless of UA */
|
/* 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) {
|
if (isBotUA) {
|
||||||
console.log(`Matched bot UA ${userAgent}`);
|
console.log(`Matched bot UA ${userAgent}`);
|
||||||
} else {
|
} else {
|
||||||
|
@ -192,7 +185,6 @@ const profileRequest = async (request: IRequest, event: FetchEvent,
|
||||||
username,
|
username,
|
||||||
userAgent,
|
userAgent,
|
||||||
flags,
|
flags,
|
||||||
language,
|
|
||||||
event
|
event
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -336,7 +328,7 @@ router.get('/owoembed', async (request: IRequest) => {
|
||||||
We don't currently have custom profile cards yet,
|
We don't currently have custom profile cards yet,
|
||||||
but it's something we might do. Maybe. */
|
but it's something we might do. Maybe. */
|
||||||
router.get('/:handle', profileRequest);
|
router.get('/:handle', profileRequest);
|
||||||
router.get('/:handle/:language', profileRequest);
|
router.get('/:handle/', profileRequest);
|
||||||
router.get('/i/events/:id', genericTwitterRedirect);
|
router.get('/i/events/:id', genericTwitterRedirect);
|
||||||
router.get('/hashtag/:hashtag', 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_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_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_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 :(`,
|
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://${
|
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 = {
|
type GraphQLUser = {
|
||||||
__typename: "User",
|
__typename: "User";
|
||||||
id: string; // "VXNlcjozNzg0MTMxMzIy",
|
id: string; // "VXNlcjo3ODMyMTQ="
|
||||||
rest_id: string; // "3784131322",
|
rest_id: string; // "783214",
|
||||||
affiliates_highlighted_label: Record<string, unknown>; // {},
|
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,
|
is_blue_verified: boolean; // false,
|
||||||
profile_image_shape: 'Circle' | 'Square'; // "Circle",
|
profile_image_shape: 'Circle' | 'Square'|'Hexagon'; // "Circle",
|
||||||
|
has_nft_avatar: boolean; // false,
|
||||||
legacy: {
|
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: boolean // false,
|
||||||
default_profile_image: boolean // false,
|
default_profile_image: boolean // false,
|
||||||
description: string; // "dangered wolf#3621 https://t.co/eBTS4kksMw",
|
description: string; // "What's happening?!",
|
||||||
entities: {
|
entities: {
|
||||||
description: {
|
description?: {
|
||||||
urls: {
|
urls?: {
|
||||||
display_url: string; // "t.me/dangeredwolf",
|
display_url: string; // "about.twitter.com",
|
||||||
expanded_url: string; // "http://t.me/dangeredwolf",
|
expanded_url: string; // "https://about.twitter.com/",
|
||||||
url: string; // "https://t.co/eBTS4kksMw",
|
url: string; // "https://t.co/DAtOo6uuHk",
|
||||||
indices: [
|
indices: [
|
||||||
19,
|
0,
|
||||||
42
|
23
|
||||||
]
|
]
|
||||||
}[]
|
}[]
|
||||||
}
|
}
|
||||||
|
@ -232,29 +247,30 @@ type GraphQLUser = {
|
||||||
friends_count: number; // 2125,
|
friends_count: number; // 2125,
|
||||||
has_custom_timelines: boolean; // true,
|
has_custom_timelines: boolean; // true,
|
||||||
is_translator: boolean; // false,
|
is_translator: boolean; // false,
|
||||||
listed_count: number; // 69,
|
listed_count: number; // 88165,
|
||||||
location: string; // "they/them",
|
location: string; // "everywhere",
|
||||||
media_count: number; // 20839,
|
media_count: number; // 20839,
|
||||||
name: string; // "dangered wolf",
|
name: string; // "Twitter",
|
||||||
normal_followers_count: number; // 4996,
|
normal_followers_count: number; // 65669107,
|
||||||
pinned_tweet_ids_str: string[]; // Array of tweet ids
|
pinned_tweet_ids_str: string[]; // Array of tweet ids, usually one. Empty if no pinned tweet
|
||||||
possibly_sensitive: boolean; // false,
|
possibly_sensitive: boolean; // false,
|
||||||
profile_banner_url: string; // "https://pbs.twimg.com/profile_banners/3784131322/1658599775",
|
profile_banner_url: string; // "https://pbs.twimg.com/profile_banners/783214/1646075315",
|
||||||
profile_image_url_https: string; // "https://pbs.twimg.com/profile_images/1555638673705783299/3gaaetxC_normal.jpg",
|
profile_image_url_https: string; // "https://pbs.twimg.com/profile_images/1488548719062654976/u6qfBBkF_normal.jpg",
|
||||||
profile_interstitial_type: string; // "",
|
profile_interstitial_type: string; // "",
|
||||||
screen_name: string; // "dangeredwolf",
|
screen_name: string; // "Twitter",
|
||||||
statuses_count: number; // 108222,
|
statuses_count: number; // 15047
|
||||||
translator_type: string; // "regular",
|
translator_type: string; // "regular"
|
||||||
verified: boolean; // false,
|
verified: boolean; // false
|
||||||
|
verified_type: 'Business'|'Government';
|
||||||
withheld_in_countries: []
|
withheld_in_countries: []
|
||||||
},
|
},
|
||||||
professional: {
|
professional: {
|
||||||
rest_id: string; // "1508134739420536845",
|
rest_id: string; // "1503055759638159366",
|
||||||
professional_type: string; // "Creator",
|
professional_type: string; // "Creator",
|
||||||
category: [
|
category: [
|
||||||
{
|
{
|
||||||
id: number; // 354,
|
id: number; // 354,
|
||||||
name: string // "Fish & Chips Restaurant",
|
name: string // "Community",
|
||||||
icon_name: string; // "IconBriefcaseStroke"
|
icon_name: string; // "IconBriefcaseStroke"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -268,7 +284,7 @@ type GraphQLUser = {
|
||||||
year_visibility: string; // "Public"
|
year_visibility: string; // "Public"
|
||||||
};
|
};
|
||||||
profile_image_shape: string; // "Circle",
|
profile_image_shape: string; // "Circle",
|
||||||
rest_id: string; // "3784131322",
|
rest_id: string; // "783214",
|
||||||
},
|
},
|
||||||
is_profile_translatable: false,
|
is_profile_translatable: false,
|
||||||
verification_info: {
|
verification_info: {
|
||||||
|
@ -282,10 +298,9 @@ type GraphQLUser = {
|
||||||
};
|
};
|
||||||
to_index: number; // 108
|
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;
|
location: string;
|
||||||
url: string;
|
url: string;
|
||||||
protected: boolean;
|
protected: boolean;
|
||||||
verified: boolean;
|
verified: 'legacy' | 'blue'| 'business' | 'government';
|
||||||
|
verified_lable: string;
|
||||||
followers: number;
|
followers: number;
|
||||||
following: number;
|
following: number;
|
||||||
tweets: number;
|
tweets: number;
|
||||||
|
|
|
@ -19,12 +19,11 @@ export const handleProfile = async (
|
||||||
username: string,
|
username: string,
|
||||||
userAgent?: string,
|
userAgent?: string,
|
||||||
flags?: InputFlags,
|
flags?: InputFlags,
|
||||||
language?: string,
|
|
||||||
event?: FetchEvent
|
event?: FetchEvent
|
||||||
): Promise<StatusResponse> => {
|
): Promise<StatusResponse> => {
|
||||||
console.log('Direct?', flags?.direct);
|
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;
|
const user = api?.user as APIUser;
|
||||||
|
|
||||||
/* Catch this request if it's an API response */
|
/* Catch this request if it's an API response */
|
||||||
|
|
|
@ -315,7 +315,7 @@ test('API fetch poll Tweet', async () => {
|
||||||
|
|
||||||
test('API fetch user', async () => {
|
test('API fetch user', async () => {
|
||||||
const result = await cacheWrapper(
|
const result = await cacheWrapper(
|
||||||
new Request('https://api.fxtwitter.com/wazbat', {
|
new Request('https://api.fxtwitter.com/twitter', {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: botHeaders
|
headers: botHeaders
|
||||||
})
|
})
|
||||||
|
@ -328,19 +328,17 @@ test('API fetch user', async () => {
|
||||||
|
|
||||||
const user = response.user as APIUser;
|
const user = response.user as APIUser;
|
||||||
expect(user).toBeTruthy();
|
expect(user).toBeTruthy();
|
||||||
expect(user.url).toEqual('https://twitter.com/wazbat');
|
expect(user.url).toEqual('https://twitter.com/Twitter');
|
||||||
expect(user.id).toEqual('157658332');
|
expect(user.id).toEqual('783214');
|
||||||
expect(user.name).toEqual('Wazbat');
|
expect(user.screen_name).toEqual('Twitter');
|
||||||
expect(user.screen_name).toEqual('wazbat');
|
expect(user.followers).toEqual(expect.any(Number));
|
||||||
expect(user.location).toEqual('Behind you');
|
expect(user.following).toEqual(expect.any(Number));
|
||||||
expect(user.followers).toBeGreaterThanOrEqual(1_000);
|
// The official twitter account will never be following as many people as it has followers
|
||||||
expect(user.followers).toBeLessThanOrEqual(100_000);
|
expect(user.following).not.toEqual(user.followers);
|
||||||
expect(user.following).toBeGreaterThanOrEqual(10);
|
expect(user.likes).toEqual(expect.any(Number));
|
||||||
expect(user.following).toBeLessThanOrEqual(1_000);
|
expect(user.verified).toEqual('business');
|
||||||
expect(user.likes).toBeGreaterThanOrEqual(10_000);
|
expect(user.joined).toEqual('Tue Feb 20 14:35:54 +0000 2007');
|
||||||
expect(user.verified).toEqual(false);
|
expect(user.birthday.day).toEqual(21);
|
||||||
expect(user.joined).toEqual('Sun Jun 20 13:29:36 +0000 2010');
|
expect(user.birthday.month).toEqual(3);
|
||||||
expect(user.birthday.day).toEqual(14);
|
|
||||||
expect(user.birthday.month).toEqual(7);
|
|
||||||
expect(user.birthday.year).toBeUndefined();
|
expect(user.birthday.year).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Reference in a new issue