From 7c2de0b6a8b84224b81aef9da30eaafdd0fc18a5 Mon Sep 17 00:00:00 2001 From: dangered wolf Date: Wed, 12 Apr 2023 17:08:52 -0400 Subject: [PATCH 1/2] Improve types --- src/types/env.d.ts | 8 ++------ wrangler.example.toml | 3 +++ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/types/env.d.ts b/src/types/env.d.ts index 3eb8be6..4ee9b2b 100644 --- a/src/types/env.d.ts +++ b/src/types/env.d.ts @@ -15,9 +15,5 @@ declare const RELEASE_NAME: string; declare const TEST: boolean | undefined; -declare const TwitterProxy: { - fetch: ( - input: RequestInfo>, - init?: RequestInit | undefined - ) => Promise; -}; +declare const TwitterProxy: Fetcher; +declare const AnalyticsEngine: AnalyticsEngineDataset; \ No newline at end of file diff --git a/wrangler.example.toml b/wrangler.example.toml index f048818..8d8c11e 100644 --- a/wrangler.example.toml +++ b/wrangler.example.toml @@ -6,6 +6,9 @@ send_metrics = false services = [ { binding = "TwitterProxy", service = "elongator" } ] +analytics_engine_datasets = [ + { binding = "AnalyticsEngine" } +] [build] command = "npm run build" From 4832983506098ef469ac8caa6f2d651750b60bc1 Mon Sep 17 00:00:00 2001 From: dangered wolf Date: Thu, 13 Apr 2023 12:29:39 -0400 Subject: [PATCH 2/2] Implement Cloudflare Analytics Engine --- src/api/status.ts | 37 ++++++++++++++++++++++++++++++++++++- src/status.ts | 2 +- src/types/env.d.ts | 2 +- 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/src/api/status.ts b/src/api/status.ts index eeaf868..1625eb0 100644 --- a/src/api/status.ts +++ b/src/api/status.ts @@ -143,17 +143,45 @@ const populateTweetProperties = async ( return apiTweet; }; +const writeDataPoint = (event: FetchEvent, language: string | undefined, nsfw: boolean, 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'; + + console.log(flagString) + + 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: [ + nsfw ? 1 : 0 /* NSFW media = 1, No NSFW Media = 0 */ + ] + }); + } +} + /* API for Twitter statuses (Tweets) Used internally by FixTweet's embed service, or available for free using api.fxtwitter.com. */ export const statusAPI = async ( status: string, language: string | undefined, - event: FetchEvent + event: FetchEvent, + flags?: InputFlags ): Promise => { let conversation = await fetchConversation(status, event); let tweet = conversation?.globalObjects?.tweets?.[status] || {}; + let wasMediaBlockedNSFW = false; + if (tweet.retweeted_status_id_str) { tweet = conversation?.globalObjects?.tweets?.[tweet.retweeted_status_id_str] || {}; } @@ -170,6 +198,7 @@ export const statusAPI = async ( if (typeof tweet.full_text !== 'undefined') { console.log('Successfully loaded Tweet using elongator'); + wasMediaBlockedNSFW = true; } else if ( typeof tweet.full_text === 'undefined' && conversation.timeline?.instructions?.length > 0 @@ -177,20 +206,24 @@ export const statusAPI = async ( console.log( 'Tweet could not be accessed with elongator, must be private/suspended' ); + writeDataPoint(event, language, wasMediaBlockedNSFW, 'PRIVATE_TWEET', flags); return { code: 401, message: 'PRIVATE_TWEET' }; } } else { /* {"errors":[{"code":34,"message":"Sorry, that page does not exist."}]} */ if (conversation.errors?.[0]?.code === 34) { + writeDataPoint(event, language, wasMediaBlockedNSFW, 'NOT_FOUND', flags); return { code: 404, message: 'NOT_FOUND' }; } /* Tweets object is completely missing, smells like API failure */ if (typeof conversation?.globalObjects?.tweets === 'undefined') { + writeDataPoint(event, language, wasMediaBlockedNSFW, 'API_FAIL', flags); return { code: 500, message: 'API_FAIL' }; } /* If we have no idea what happened then just return API error */ + writeDataPoint(event, language, wasMediaBlockedNSFW, 'API_FAIL', flags); return { code: 500, message: 'API_FAIL' }; } } @@ -217,5 +250,7 @@ export const statusAPI = async ( /* Finally, staple the Tweet to the response and return it */ response.tweet = apiTweet; + writeDataPoint(event, language, wasMediaBlockedNSFW, 'OK', flags); + return response; }; diff --git a/src/status.ts b/src/status.ts index e8b3d9f..e19e333 100644 --- a/src/status.ts +++ b/src/status.ts @@ -30,7 +30,7 @@ export const handleStatus = async ( ): Promise => { console.log('Direct?', flags?.direct); - const api = await statusAPI(status, language, event as FetchEvent); + const api = await statusAPI(status, language, event as FetchEvent, flags); const tweet = api?.tweet as APITweet; /* Catch this request if it's an API response */ diff --git a/src/types/env.d.ts b/src/types/env.d.ts index 4ee9b2b..3656c89 100644 --- a/src/types/env.d.ts +++ b/src/types/env.d.ts @@ -16,4 +16,4 @@ declare const RELEASE_NAME: string; declare const TEST: boolean | undefined; declare const TwitterProxy: Fetcher; -declare const AnalyticsEngine: AnalyticsEngineDataset; \ No newline at end of file +declare const AnalyticsEngine: AnalyticsEngineDataset;