mirror of
https://github.com/CompeyDev/fxtwitter-docker.git
synced 2025-04-10 21:10:54 +01:00
Fix NSFW, added i.fxtwitter.com
This commit is contained in:
parent
d4a6582bd6
commit
775d244c7b
12 changed files with 50 additions and 24 deletions
|
@ -1,6 +1,7 @@
|
||||||
BRANDING_NAME = "FixTweet"
|
BRANDING_NAME = "FixTweet"
|
||||||
DIRECT_MEDIA_DOMAINS = "d.fxtwitter.com,dl.fxtwitter.com,d.pxtwitter.com,d.twittpr.com,dl.pxtwitter.com,dl.twittpr.com"
|
DIRECT_MEDIA_DOMAINS = "d.fxtwitter.com,dl.fxtwitter.com,d.pxtwitter.com,d.twittpr.com,dl.pxtwitter.com,dl.twittpr.com,d.fixupx.com,d.xfixup.com,dl.fixupx.com,dl.xfixup.com"
|
||||||
TEXT_ONLY_DOMAINS = "t.fxtwitter.com,t.twittpr.com"
|
TEXT_ONLY_DOMAINS = "t.fxtwitter.com,t.twittpr.com,t.fixupx.com,t.xfixup.com"
|
||||||
|
INSTANT_VIEW_DOMAINS = "i.fxtwitter.com,i.twittpr.com,i.fixupx.com,i.xfixup.com"
|
||||||
DEPRECATED_DOMAIN_LIST = "pxtwitter.com,www.pxtwitter.com"
|
DEPRECATED_DOMAIN_LIST = "pxtwitter.com,www.pxtwitter.com"
|
||||||
DEPRECATED_DOMAIN_EPOCH = "1559320000000000000"
|
DEPRECATED_DOMAIN_EPOCH = "1559320000000000000"
|
||||||
MOSAIC_DOMAIN_LIST = "mosaic.fxtwitter.com"
|
MOSAIC_DOMAIN_LIST = "mosaic.fxtwitter.com"
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
"globals": {
|
"globals": {
|
||||||
"BRANDING_NAME": "FixTweet",
|
"BRANDING_NAME": "FixTweet",
|
||||||
"TEXT_ONLY_DOMAINS": "t.fxtwitter.com,t.twittpr.com",
|
"TEXT_ONLY_DOMAINS": "t.fxtwitter.com,t.twittpr.com",
|
||||||
|
"INSTANT_VIEW_DOMAINS": "i.fxtwitter.com,i.twittpr.com",
|
||||||
"DIRECT_MEDIA_DOMAINS": "d.fxtwitter.com,dl.fxtwitter.com",
|
"DIRECT_MEDIA_DOMAINS": "d.fxtwitter.com,dl.fxtwitter.com",
|
||||||
"MOSAIC_DOMAIN_LIST": "mosaic.fxtwitter.com",
|
"MOSAIC_DOMAIN_LIST": "mosaic.fxtwitter.com",
|
||||||
"DEPRECATED_DOMAIN_LIST": "pxtwitter.com,www.pxtwitter.com",
|
"DEPRECATED_DOMAIN_LIST": "pxtwitter.com,www.pxtwitter.com",
|
||||||
|
|
|
@ -210,22 +210,23 @@ export const statusAPI = async (
|
||||||
event: FetchEvent,
|
event: FetchEvent,
|
||||||
flags?: InputFlags
|
flags?: InputFlags
|
||||||
): Promise<TweetAPIResponse> => {
|
): Promise<TweetAPIResponse> => {
|
||||||
let wasMediaBlockedNSFW = false;
|
const res = await fetchConversation(status, event);
|
||||||
let res = await fetchConversation(status, event);
|
|
||||||
const tweet = res.data?.tweetResult?.result;
|
const tweet = res.data?.tweetResult?.result;
|
||||||
if (!tweet) {
|
if (!tweet) {
|
||||||
return { code: 404, message: 'NOT_FOUND' };
|
return { code: 404, message: 'NOT_FOUND' };
|
||||||
}
|
}
|
||||||
if (tweet.__typename === 'TweetUnavailable' && tweet.reason === 'NsfwLoggedOut') {
|
/* We're handling this in the actual fetch code now */
|
||||||
wasMediaBlockedNSFW = true;
|
|
||||||
res = await fetchConversation(status, event, true);
|
// if (tweet.__typename === 'TweetUnavailable' && tweet.reason === 'NsfwLoggedOut') {
|
||||||
}
|
// wasMediaBlockedNSFW = true;
|
||||||
|
// res = await fetchConversation(status, event, true);
|
||||||
|
// }
|
||||||
|
|
||||||
// console.log(JSON.stringify(tweet))
|
// console.log(JSON.stringify(tweet))
|
||||||
|
|
||||||
if (tweet.__typename === 'TweetUnavailable') {
|
if (tweet.__typename === 'TweetUnavailable') {
|
||||||
if (tweet.reason === 'Protected') {
|
if ((tweet as {reason: string})?.reason === 'Protected') {
|
||||||
writeDataPoint(event, language, wasMediaBlockedNSFW, 'PRIVATE_TWEET', flags);
|
writeDataPoint(event, language, false, 'PRIVATE_TWEET', flags);
|
||||||
return { code: 401, message: 'PRIVATE_TWEET' };
|
return { code: 401, message: 'PRIVATE_TWEET' };
|
||||||
// } else if (tweet.reason === 'NsfwLoggedOut') {
|
// } else if (tweet.reason === 'NsfwLoggedOut') {
|
||||||
// // API failure as elongator should have handled this
|
// // API failure as elongator should have handled this
|
||||||
|
@ -233,14 +234,14 @@ export const statusAPI = async (
|
||||||
// return { code: 500, message: 'API_FAIL' };
|
// return { code: 500, message: 'API_FAIL' };
|
||||||
} else {
|
} else {
|
||||||
// Api failure at parsing status
|
// Api failure at parsing status
|
||||||
writeDataPoint(event, language, wasMediaBlockedNSFW, 'API_FAIL', flags);
|
writeDataPoint(event, language, false, 'API_FAIL', flags);
|
||||||
return { code: 500, message: 'API_FAIL' };
|
return { code: 500, message: 'API_FAIL' };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If the tweet is not a graphQL tweet something went wrong
|
// If the tweet is not a graphQL tweet something went wrong
|
||||||
if (!isGraphQLTweet(tweet)) {
|
if (!isGraphQLTweet(tweet)) {
|
||||||
console.log('Tweet was not a valid tweet', tweet);
|
console.log('Tweet was not a valid tweet', tweet);
|
||||||
writeDataPoint(event, language, wasMediaBlockedNSFW, 'API_FAIL', flags);
|
writeDataPoint(event, language, false, 'API_FAIL', flags);
|
||||||
return { code: 500, message: 'API_FAIL' };
|
return { code: 500, message: 'API_FAIL' };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -274,7 +275,7 @@ export const statusAPI = async (
|
||||||
/* Finally, staple the Tweet to the response and return it */
|
/* Finally, staple the Tweet to the response and return it */
|
||||||
response.tweet = apiTweet;
|
response.tweet = apiTweet;
|
||||||
|
|
||||||
writeDataPoint(event, language, wasMediaBlockedNSFW, 'OK', flags);
|
writeDataPoint(event, language, false, 'OK', flags);
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,6 +3,7 @@ export const Constants = {
|
||||||
BRANDING_NAME: BRANDING_NAME,
|
BRANDING_NAME: BRANDING_NAME,
|
||||||
DIRECT_MEDIA_DOMAINS: DIRECT_MEDIA_DOMAINS.split(','),
|
DIRECT_MEDIA_DOMAINS: DIRECT_MEDIA_DOMAINS.split(','),
|
||||||
TEXT_ONLY_DOMAINS: TEXT_ONLY_DOMAINS.split(','),
|
TEXT_ONLY_DOMAINS: TEXT_ONLY_DOMAINS.split(','),
|
||||||
|
INSTANT_VIEW_DOMAINS: INSTANT_VIEW_DOMAINS.split(','),
|
||||||
DEPRECATED_DOMAIN_LIST: DEPRECATED_DOMAIN_LIST.split(','),
|
DEPRECATED_DOMAIN_LIST: DEPRECATED_DOMAIN_LIST.split(','),
|
||||||
DEPRECATED_DOMAIN_EPOCH: BigInt(DEPRECATED_DOMAIN_EPOCH),
|
DEPRECATED_DOMAIN_EPOCH: BigInt(DEPRECATED_DOMAIN_EPOCH),
|
||||||
MOSAIC_DOMAIN_LIST: MOSAIC_DOMAIN_LIST.split(','),
|
MOSAIC_DOMAIN_LIST: MOSAIC_DOMAIN_LIST.split(','),
|
||||||
|
|
|
@ -42,7 +42,7 @@ export const handleStatus = async (
|
||||||
isTelegram /*&& !tweet.possibly_sensitive*/ &&
|
isTelegram /*&& !tweet.possibly_sensitive*/ &&
|
||||||
!flags?.direct &&
|
!flags?.direct &&
|
||||||
!flags?.api &&
|
!flags?.api &&
|
||||||
(tweet.media?.mosaic || tweet.is_note_tweet || tweet.quote);
|
(tweet.media?.mosaic || tweet.is_note_tweet || tweet.quote || flags?.forceInstantView);
|
||||||
|
|
||||||
let ivbody = '';
|
let ivbody = '';
|
||||||
|
|
||||||
|
|
21
src/fetch.ts
21
src/fetch.ts
|
@ -128,11 +128,13 @@ export const twitterFetch = async (
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: headers
|
headers: headers
|
||||||
});
|
});
|
||||||
|
console.log('Elongator request successful');
|
||||||
} else {
|
} else {
|
||||||
apiRequest = await fetch(url, {
|
apiRequest = await fetch(url, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: headers
|
headers: headers
|
||||||
});
|
});
|
||||||
|
console.log('Guest API request successful');
|
||||||
}
|
}
|
||||||
|
|
||||||
response = await apiRequest?.json();
|
response = await apiRequest?.json();
|
||||||
|
@ -148,12 +150,19 @@ export const twitterFetch = async (
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @ts-expect-error This is safe due to optional chaining
|
||||||
|
if ((response as TweetResultsByRestIdResult)?.data?.tweetResult?.result?.reason === 'NsfwLoggedOut') {
|
||||||
|
console.log(`nsfw tweet detected, it's elongator time`);
|
||||||
|
useElongator = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const remainingRateLimit = parseInt(
|
const remainingRateLimit = parseInt(
|
||||||
apiRequest.headers.get('x-rate-limit-remaining') || '0'
|
apiRequest.headers.get('x-rate-limit-remaining') || '0'
|
||||||
);
|
);
|
||||||
console.log(`Remaining rate limit: ${remainingRateLimit} requests`);
|
console.log(`Remaining rate limit: ${remainingRateLimit} requests`);
|
||||||
/* Running out of requests within our rate limit, let's purge the cache */
|
/* Running out of requests within our rate limit, let's purge the cache */
|
||||||
if (remainingRateLimit < 10) {
|
if (remainingRateLimit < 10 && !useElongator) {
|
||||||
console.log(`Purging token on this edge due to low rate limit remaining`);
|
console.log(`Purging token on this edge due to low rate limit remaining`);
|
||||||
event &&
|
event &&
|
||||||
event.waitUntil(
|
event.waitUntil(
|
||||||
|
@ -180,6 +189,7 @@ export const twitterFetch = async (
|
||||||
|
|
||||||
// @ts-expect-error - We'll pin the guest token to whatever response we have
|
// @ts-expect-error - We'll pin the guest token to whatever response we have
|
||||||
response.guestToken = guestToken;
|
response.guestToken = guestToken;
|
||||||
|
console.log('twitterFetch is all done here, see you soon!');
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -240,13 +250,17 @@ export const fetchConversation = async (
|
||||||
if (isGraphQLTweet(tweet)) {
|
if (isGraphQLTweet(tweet)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (tweet?.__typename === 'TweetUnavailable' && tweet.reason === 'Protected') {
|
console.log('invalid graphql tweet');
|
||||||
|
if (tweet?.__typename === 'TweetUnavailable' && tweet.reason === 'NsfwLoggedOut') {
|
||||||
|
console.log('tweet is nsfw');
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (tweet?.__typename === 'TweetUnavailable' && tweet.reason === 'NsfwLoggedOut') {
|
if (tweet?.__typename === 'TweetUnavailable' && tweet.reason === 'Protected') {
|
||||||
|
console.log('tweet is protected');
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (tweet?.__typename === 'TweetUnavailable') {
|
if (tweet?.__typename === 'TweetUnavailable') {
|
||||||
|
console.log('generic tweet unavailable error')
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// Final clause for checking if it's valid is if there's errors
|
// Final clause for checking if it's valid is if there's errors
|
||||||
|
@ -284,6 +298,7 @@ export const fetchUser = async (
|
||||||
const response = _res as GraphQLUserResponse;
|
const response = _res as GraphQLUserResponse;
|
||||||
// If _res.data is an empty object, we have no user
|
// If _res.data is an empty object, we have no user
|
||||||
if (!Object.keys(response?.data).length) {
|
if (!Object.keys(response?.data).length) {
|
||||||
|
console.log(`response.data is empty, can't continue`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return !(
|
return !(
|
||||||
|
|
|
@ -53,6 +53,9 @@ const statusRequest = async (
|
||||||
} else if (Constants.TEXT_ONLY_DOMAINS.includes(url.hostname)) {
|
} else if (Constants.TEXT_ONLY_DOMAINS.includes(url.hostname)) {
|
||||||
console.log('Text-only embed request');
|
console.log('Text-only embed request');
|
||||||
flags.textOnly = true;
|
flags.textOnly = true;
|
||||||
|
} else if (Constants.INSTANT_VIEW_DOMAINS.includes(url.hostname)) {
|
||||||
|
console.log('Forced instant view request');
|
||||||
|
flags.forceInstantView = true;
|
||||||
} else if (prefix === 'dl' || prefix === 'dir') {
|
} else if (prefix === 'dl' || prefix === 'dir') {
|
||||||
console.log('Direct media request by path prefix');
|
console.log('Direct media request by path prefix');
|
||||||
flags.direct = true;
|
flags.direct = true;
|
||||||
|
|
1
src/types/env.d.ts
vendored
1
src/types/env.d.ts
vendored
|
@ -1,6 +1,7 @@
|
||||||
declare const BRANDING_NAME: string;
|
declare const BRANDING_NAME: string;
|
||||||
declare const DIRECT_MEDIA_DOMAINS: string;
|
declare const DIRECT_MEDIA_DOMAINS: string;
|
||||||
declare const TEXT_ONLY_DOMAINS: string;
|
declare const TEXT_ONLY_DOMAINS: string;
|
||||||
|
declare const INSTANT_VIEW_DOMAINS: string;
|
||||||
declare const DEPRECATED_DOMAIN_LIST: string;
|
declare const DEPRECATED_DOMAIN_LIST: string;
|
||||||
declare const DEPRECATED_DOMAIN_EPOCH: string;
|
declare const DEPRECATED_DOMAIN_EPOCH: string;
|
||||||
declare const HOST_URL: string;
|
declare const HOST_URL: string;
|
||||||
|
|
2
src/types/twitterTypes.d.ts
vendored
2
src/types/twitterTypes.d.ts
vendored
|
@ -308,7 +308,7 @@ type GraphQLUser = {
|
||||||
type GraphQLTweet = {
|
type GraphQLTweet = {
|
||||||
// Workaround
|
// Workaround
|
||||||
result: GraphQLTweet;
|
result: GraphQLTweet;
|
||||||
__typename: 'Tweet';
|
__typename: 'Tweet' | 'TweetUnavailable';
|
||||||
rest_id: string; // "1674824189176590336",
|
rest_id: string; // "1674824189176590336",
|
||||||
has_birdwatch_notes: false;
|
has_birdwatch_notes: false;
|
||||||
core: {
|
core: {
|
||||||
|
|
1
src/types/types.d.ts
vendored
1
src/types/types.d.ts
vendored
|
@ -8,6 +8,7 @@ type InputFlags = {
|
||||||
deprecated?: boolean;
|
deprecated?: boolean;
|
||||||
textOnly?: boolean;
|
textOnly?: boolean;
|
||||||
isXDomain?: boolean;
|
isXDomain?: boolean;
|
||||||
|
forceInstantView?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface StatusResponse {
|
interface StatusResponse {
|
||||||
|
|
|
@ -6,6 +6,7 @@ const humanHeaders = {
|
||||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36'
|
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36'
|
||||||
};
|
};
|
||||||
const githubUrl = 'https://github.com/FixTweet/FixTweet';
|
const githubUrl = 'https://github.com/FixTweet/FixTweet';
|
||||||
|
const twitterBaseUrl = 'https://twitter.com';
|
||||||
|
|
||||||
test('Home page redirect', async () => {
|
test('Home page redirect', async () => {
|
||||||
const result = await cacheWrapper(
|
const result = await cacheWrapper(
|
||||||
|
@ -49,7 +50,7 @@ test('Twitter moment redirect', async () => {
|
||||||
);
|
);
|
||||||
expect(result.status).toEqual(302);
|
expect(result.status).toEqual(302);
|
||||||
expect(result.headers.get('location')).toEqual(
|
expect(result.headers.get('location')).toEqual(
|
||||||
'https://twitter.com/i/events/1572638642127966214'
|
`${twitterBaseUrl}/i/events/1572638642127966214`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -78,7 +79,7 @@ test('API fetch basic Tweet', async () => {
|
||||||
|
|
||||||
const tweet = response.tweet as APITweet;
|
const tweet = response.tweet as APITweet;
|
||||||
expect(tweet).toBeTruthy();
|
expect(tweet).toBeTruthy();
|
||||||
expect(tweet.url).toEqual('https://twitter.com/jack/status/20');
|
expect(tweet.url).toEqual(`${twitterBaseUrl}/jack/status/20`);
|
||||||
expect(tweet.id).toEqual('20');
|
expect(tweet.id).toEqual('20');
|
||||||
expect(tweet.text).toEqual('just setting up my twttr');
|
expect(tweet.text).toEqual('just setting up my twttr');
|
||||||
expect(tweet.author.screen_name?.toLowerCase()).toEqual('jack');
|
expect(tweet.author.screen_name?.toLowerCase()).toEqual('jack');
|
||||||
|
@ -112,7 +113,7 @@ test('API fetch video Tweet', async () => {
|
||||||
|
|
||||||
const tweet = response.tweet as APITweet;
|
const tweet = response.tweet as APITweet;
|
||||||
expect(tweet).toBeTruthy();
|
expect(tweet).toBeTruthy();
|
||||||
expect(tweet.url).toEqual('https://twitter.com/X/status/854416760933556224');
|
expect(tweet.url).toEqual(`${twitterBaseUrl}/X/status/854416760933556224`);
|
||||||
expect(tweet.id).toEqual('854416760933556224');
|
expect(tweet.id).toEqual('854416760933556224');
|
||||||
expect(tweet.text).toEqual(
|
expect(tweet.text).toEqual(
|
||||||
'Get the sauces ready, #NuggsForCarter has 3 million+ Retweets.'
|
'Get the sauces ready, #NuggsForCarter has 3 million+ Retweets.'
|
||||||
|
@ -158,7 +159,7 @@ test('API fetch multi-photo Tweet', async () => {
|
||||||
|
|
||||||
const tweet = response.tweet as APITweet;
|
const tweet = response.tweet as APITweet;
|
||||||
expect(tweet).toBeTruthy();
|
expect(tweet).toBeTruthy();
|
||||||
expect(tweet.url).toEqual('https://twitter.com/X/status/1445094085593866246');
|
expect(tweet.url).toEqual(`${twitterBaseUrl}/X/status/1445094085593866246`);
|
||||||
expect(tweet.id).toEqual('1445094085593866246');
|
expect(tweet.id).toEqual('1445094085593866246');
|
||||||
expect(tweet.text).toEqual('@netflix');
|
expect(tweet.text).toEqual('@netflix');
|
||||||
expect(tweet.author.screen_name?.toLowerCase()).toEqual('x');
|
expect(tweet.author.screen_name?.toLowerCase()).toEqual('x');
|
||||||
|
@ -206,7 +207,7 @@ test('API fetch poll Tweet', async () => {
|
||||||
|
|
||||||
const tweet = response.tweet as APITweet;
|
const tweet = response.tweet as APITweet;
|
||||||
expect(tweet).toBeTruthy();
|
expect(tweet).toBeTruthy();
|
||||||
expect(tweet.url).toEqual('https://twitter.com/X/status/1055475950543167488');
|
expect(tweet.url).toEqual(`${twitterBaseUrl}/X/status/1055475950543167488`);
|
||||||
expect(tweet.id).toEqual('1055475950543167488');
|
expect(tweet.id).toEqual('1055475950543167488');
|
||||||
expect(tweet.text).toEqual('A poll:');
|
expect(tweet.text).toEqual('A poll:');
|
||||||
expect(tweet.author.screen_name?.toLowerCase()).toEqual('x');
|
expect(tweet.author.screen_name?.toLowerCase()).toEqual('x');
|
||||||
|
@ -256,7 +257,7 @@ 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/X');
|
expect(user.url).toEqual(`${twitterBaseUrl}/X`);
|
||||||
expect(user.id).toEqual('783214');
|
expect(user.id).toEqual('783214');
|
||||||
expect(user.screen_name).toEqual('X');
|
expect(user.screen_name).toEqual('X');
|
||||||
expect(user.followers).toEqual(expect.any(Number));
|
expect(user.followers).toEqual(expect.any(Number));
|
||||||
|
|
|
@ -21,6 +21,7 @@ let envVariables = [
|
||||||
'BRANDING_NAME',
|
'BRANDING_NAME',
|
||||||
'DIRECT_MEDIA_DOMAINS',
|
'DIRECT_MEDIA_DOMAINS',
|
||||||
'TEXT_ONLY_DOMAINS',
|
'TEXT_ONLY_DOMAINS',
|
||||||
|
'INSTANT_VIEW_DOMAINS',
|
||||||
'HOST_URL',
|
'HOST_URL',
|
||||||
'REDIRECT_URL',
|
'REDIRECT_URL',
|
||||||
'EMBED_URL',
|
'EMBED_URL',
|
||||||
|
|
Loading…
Add table
Reference in a new issue