From ad5ccfb68b34639076cd5dae34901aa6b8f41c68 Mon Sep 17 00:00:00 2001 From: dangered wolf Date: Tue, 30 Apr 2024 02:30:42 -0400 Subject: [PATCH 1/6] Add crowdin badge --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index aa11bb8..41a68a9 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ ![][icons] +[![Crowdin][crowdinbadge]][crowdin] [![esbuild][buildbadge]][build] [![Tests][testsbadge]][tests] [![Status][statusbadge]][status] @@ -20,6 +21,8 @@ [licensebadge]: https://img.shields.io/github/license/FixTweet/FxTwitter [status]: https://status.fxtwitter.com [statusbadge]: https://status.fxtwitter.com/api/badge/8/uptime/720?label=Uptime%2030d +[crowdinbadge]: https://badges.crowdin.net/fxtwitter/localized.svg +[crowdin]: https://crowdin.com/project/fxtwitter ## Written in TypeScript as a Cloudflare Worker to scale, packed with more features and [best-in-class user privacy 🔒](#built-with-privacy-in-mind). From beea81de75963259a1a96a773315763ec8616ca2 Mon Sep 17 00:00:00 2001 From: dangered wolf Date: Tue, 30 Apr 2024 15:45:14 -0400 Subject: [PATCH 2/6] Upgrade to hono v4 --- package-lock.json | 8 ++++---- package.json | 2 +- src/caches.ts | 4 ++-- src/embed/status.ts | 3 ++- src/experiments.ts | 2 +- src/providers/twitter/conversation.ts | 4 ++-- src/realms/api/hit.ts | 2 +- src/realms/api/router.ts | 3 +-- src/realms/twitter/routes/oembed.ts | 3 +-- src/realms/twitter/routes/redirects.ts | 10 +++------- src/realms/twitter/routes/status.ts | 3 +-- src/user.ts | 3 ++- src/worker.ts | 10 ++++------ 13 files changed, 25 insertions(+), 32 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4c09196..919101d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "@hono/sentry": "^1.0.1", - "hono": "^3.12.12" + "hono": "^4.2.9" }, "devDependencies": { "@cloudflare/workers-types": "^4.20240423.0", @@ -4991,9 +4991,9 @@ } }, "node_modules/hono": { - "version": "3.12.12", - "resolved": "https://registry.npmjs.org/hono/-/hono-3.12.12.tgz", - "integrity": "sha512-5IAMJOXfpA5nT+K0MNjClchzz0IhBHs2Szl7WFAhrFOsbtQsYmNynFyJRg/a3IPsmCfxcrf8txUGiNShXpK5Rg==", + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.2.9.tgz", + "integrity": "sha512-59FAv52UxDWUt/NlC0NzrRCjeVCThUnVlqlrKYm+k80XujBu6uJwBIa5gACKKZWobjA0MJ6Vds0I3URKf383Cw==", "engines": { "node": ">=16.0.0" } diff --git a/package.json b/package.json index 59ccb3b..3b0df53 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,6 @@ }, "dependencies": { "@hono/sentry": "^1.0.1", - "hono": "^3.12.12" + "hono": "^4.2.9" } } diff --git a/src/caches.ts b/src/caches.ts index a9c1229..2321e9a 100644 --- a/src/caches.ts +++ b/src/caches.ts @@ -90,11 +90,11 @@ export const cacheMiddleware = (): MiddlewareHandler => async (c, next) => { /* We properly state our OPTIONS when asked */ case 'OPTIONS': c.header('allow', Constants.RESPONSE_HEADERS.allow); + c.body(null); c.status(204); return; default: - c.status(405); if (returnAsJson) return c.json(''); - return c.html(''); + return c.html('', 405); } }; diff --git a/src/embed/status.ts b/src/embed/status.ts index 4205a92..99492f8 100644 --- a/src/embed/status.ts +++ b/src/embed/status.ts @@ -9,6 +9,7 @@ import { renderVideo } from '../render/video'; import { renderInstantView } from '../render/instantview'; import { constructTwitterThread } from '../providers/twitter/conversation'; import { Experiment, experimentCheck } from '../experiments'; +import { StatusCode } from 'hono/utils/http-status'; export const returnError = (c: Context, error: string): Response => { return c.html( @@ -78,7 +79,7 @@ export const handleStatus = async ( /* Catch this request if it's an API response */ if (flags?.api) { - c.status(api.code); + c.status(api.code as StatusCode); // Add every header from Constants.API_RESPONSE_HEADERS for (const [header, value] of Object.entries(Constants.API_RESPONSE_HEADERS)) { c.header(header, value); diff --git a/src/experiments.ts b/src/experiments.ts index 12a442c..50d6285 100644 --- a/src/experiments.ts +++ b/src/experiments.ts @@ -42,7 +42,7 @@ const Experiments: { [key in Experiment]: ExperimentConfig } = { [Experiment.IV_FORCE_THREAD_UNROLL]: { name: 'IV force thread unroll', description: 'Force thread unroll for Telegram Instant View', - percentage: 0.25 + percentage: 1 } }; diff --git a/src/providers/twitter/conversation.ts b/src/providers/twitter/conversation.ts index c61cc60..5aff2b6 100644 --- a/src/providers/twitter/conversation.ts +++ b/src/providers/twitter/conversation.ts @@ -4,6 +4,7 @@ import { buildAPITwitterStatus } from './processor'; import { Experiment, experimentCheck } from '../../experiments'; import { isGraphQLTwitterStatus } from '../../helpers/graphql'; import { Context } from 'hono'; +import { StatusCode } from 'hono/utils/http-status'; const writeDataPoint = ( c: Context, @@ -579,10 +580,9 @@ export const threadAPIProvider = async (c: Context) => { const processedResponse = await constructTwitterThread(id, true, c, undefined); - c.status(processedResponse.code); // Add every header from Constants.API_RESPONSE_HEADERS for (const [header, value] of Object.entries(Constants.API_RESPONSE_HEADERS)) { c.header(header, value); } - return c.json(processedResponse); + return c.json(processedResponse, processedResponse.code as StatusCode); }; diff --git a/src/realms/api/hit.ts b/src/realms/api/hit.ts index 4d8db8e..99822c2 100644 --- a/src/realms/api/hit.ts +++ b/src/realms/api/hit.ts @@ -5,7 +5,7 @@ export const linkHitRequest = async (c: Context) => { const userAgent = c.req.header('User-Agent') || ''; if (userAgent.includes('Telegram')) { - c.status(403); + return c.text('', 403); } // If param `url` exists, 302 redirect to it if (typeof c.req.query('url') === 'string') { diff --git a/src/realms/api/router.ts b/src/realms/api/router.ts index da08d56..a6bac4a 100644 --- a/src/realms/api/router.ts +++ b/src/realms/api/router.ts @@ -9,11 +9,10 @@ export const api = new Hono(); api.use('*', async (c, next) => { if (!c.req.header('user-agent')) { - c.status(401); return c.json({ error: "You must identify yourself with a User-Agent header in order to use the FixTweet API. We recommend using a descriptive User-Agent header to identify your app, such as 'MyAwesomeBot/1.0 (+http://example.com/myawesomebot)'. We don't track or save what kinds of data you are pulling, but you may be blocked if you send too many requests from an unidentifiable user agent." - }); + }, 401); } await next(); }); diff --git a/src/realms/twitter/routes/oembed.ts b/src/realms/twitter/routes/oembed.ts index c822b81..7cdb7af 100644 --- a/src/realms/twitter/routes/oembed.ts +++ b/src/realms/twitter/routes/oembed.ts @@ -28,7 +28,6 @@ export const oembed = async (c: Context) => { }; c.header('content-type', 'application/json'); - c.status(200); /* Stringify and send it on its way! */ - return c.text(JSON.stringify(data)); + return c.text(JSON.stringify(data), 200); }; diff --git a/src/realms/twitter/routes/redirects.ts b/src/realms/twitter/routes/redirects.ts index cfff1f2..a9a9b31 100644 --- a/src/realms/twitter/routes/redirects.ts +++ b/src/realms/twitter/routes/redirects.ts @@ -25,12 +25,10 @@ export const setRedirectRequest = async (c: Context) => { /* Check that origin either does not exist or is in our domain list */ const origin = c.req.header('origin'); if (origin && !Constants.STANDARD_DOMAIN_LIST.includes(new URL(origin).hostname)) { - c.status(403); - return c.html( Strings.MESSAGE_HTML.format({ message: `Failed to set base redirect: Your request seems to be originating from another domain, please open this up in a new tab if you are trying to set your base redirect.` - }) + }), 403 ); } @@ -46,11 +44,10 @@ export const setRedirectRequest = async (c: Context) => { 'content-security-policy', `frame-ancestors ${Constants.STANDARD_DOMAIN_LIST.join(' ')};` ); - c.status(200); return c.html( Strings.MESSAGE_HTML.format({ message: `Your base redirect has been cleared. To set one, please pass along the url parameter.` - }) + }), 200 ); } @@ -71,11 +68,10 @@ export const setRedirectRequest = async (c: Context) => { 'content-security-policy', `frame-ancestors ${Constants.STANDARD_DOMAIN_LIST.join(' ')};` ); - c.status(200); return c.html( Strings.MESSAGE_HTML.format({ message: `Your URL does not appear to be well-formed. Example: ?url=https://nitter.net` - }) + }), 200 ); } diff --git a/src/realms/twitter/routes/status.ts b/src/realms/twitter/routes/status.ts index dea9c21..1337e0c 100644 --- a/src/realms/twitter/routes/status.ts +++ b/src/realms/twitter/routes/status.ts @@ -131,8 +131,7 @@ export const statusRequest = async (c: Context) => { return statusResponse; } else { /* Somehow handleStatus sent us nothing. This should *never* happen, but we have a case for it. */ - c.status(500); - return c.text(Strings.ERROR_UNKNOWN); + return c.text(Strings.ERROR_UNKNOWN, 500); } } else { /* A human has clicked a fxtwitter.com/:screen_name/status/:id link! diff --git a/src/user.ts b/src/user.ts index 71d6503..9781345 100644 --- a/src/user.ts +++ b/src/user.ts @@ -2,6 +2,7 @@ import { Context } from 'hono'; import { Constants } from './constants'; import { Strings } from './strings'; import { userAPI } from './providers/twitter/profile'; +import { StatusCode } from 'hono/utils/http-status'; export const returnError = (c: Context, error: string): Response => { return c.html( @@ -29,7 +30,7 @@ export const handleProfile = async ( /* Catch this request if it's an API response */ // For now we just always return the API response while testing if (flags?.api) { - c.status(api.code); + c.status(api.code as StatusCode); // Add every header from Constants.API_RESPONSE_HEADERS for (const [header, value] of Object.entries(Constants.API_RESPONSE_HEADERS)) { c.header(header, value); diff --git a/src/worker.ts b/src/worker.ts index 3d5ab74..f162405 100644 --- a/src/worker.ts +++ b/src/worker.ts @@ -8,6 +8,7 @@ import { Constants } from './constants'; import { api } from './realms/api/router'; import { twitter } from './realms/twitter/router'; import { cacheMiddleware } from './caches'; +import { StatusCode } from 'hono/utils/http-status'; const noCache = 'max-age=0, no-cache, no-store, must-revalidate'; const embeddingClientRegex = @@ -89,10 +90,9 @@ app.onError((err, c) => { if (c.req.header('User-Agent')?.match(embeddingClientRegex)) { errorCode = 200; } - c.status(errorCode); c.header('cache-control', noCache); - return c.html(Strings.ERROR_HTML); + return c.html(Strings.ERROR_HTML, errorCode as StatusCode); }); const customLogger = (message: string, ...rest: string[]) => { @@ -138,12 +138,10 @@ app.all('/error', async c => { c.header('cache-control', noCache); if (c.req.header('User-Agent')?.match(embeddingClientRegex)) { - c.status(200); - return c.html(Strings.ERROR_HTML); + return c.html(Strings.ERROR_HTML, 200); } - c.status(400); /* We return it as a 200 so embedded applications can display the error */ - return c.body(''); + return c.body('', 400); }); export default { From 8ee96a93d3906dc3b9e5d661e2d6566fd5300717 Mon Sep 17 00:00:00 2001 From: dangered wolf Date: Tue, 30 Apr 2024 15:46:32 -0400 Subject: [PATCH 3/6] Run prettier --- src/realms/api/router.ts | 11 +++++++---- src/realms/twitter/routes/redirects.ts | 9 ++++++--- src/render/instantview.ts | 7 ++++--- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/realms/api/router.ts b/src/realms/api/router.ts index a6bac4a..d0d067e 100644 --- a/src/realms/api/router.ts +++ b/src/realms/api/router.ts @@ -9,10 +9,13 @@ export const api = new Hono(); api.use('*', async (c, next) => { if (!c.req.header('user-agent')) { - return c.json({ - error: - "You must identify yourself with a User-Agent header in order to use the FixTweet API. We recommend using a descriptive User-Agent header to identify your app, such as 'MyAwesomeBot/1.0 (+http://example.com/myawesomebot)'. We don't track or save what kinds of data you are pulling, but you may be blocked if you send too many requests from an unidentifiable user agent." - }, 401); + return c.json( + { + error: + "You must identify yourself with a User-Agent header in order to use the FixTweet API. We recommend using a descriptive User-Agent header to identify your app, such as 'MyAwesomeBot/1.0 (+http://example.com/myawesomebot)'. We don't track or save what kinds of data you are pulling, but you may be blocked if you send too many requests from an unidentifiable user agent." + }, + 401 + ); } await next(); }); diff --git a/src/realms/twitter/routes/redirects.ts b/src/realms/twitter/routes/redirects.ts index a9a9b31..fc711b8 100644 --- a/src/realms/twitter/routes/redirects.ts +++ b/src/realms/twitter/routes/redirects.ts @@ -28,7 +28,8 @@ export const setRedirectRequest = async (c: Context) => { return c.html( Strings.MESSAGE_HTML.format({ message: `Failed to set base redirect: Your request seems to be originating from another domain, please open this up in a new tab if you are trying to set your base redirect.` - }), 403 + }), + 403 ); } @@ -47,7 +48,8 @@ export const setRedirectRequest = async (c: Context) => { return c.html( Strings.MESSAGE_HTML.format({ message: `Your base redirect has been cleared. To set one, please pass along the url parameter.` - }), 200 + }), + 200 ); } @@ -71,7 +73,8 @@ export const setRedirectRequest = async (c: Context) => { return c.html( Strings.MESSAGE_HTML.format({ message: `Your URL does not appear to be well-formed. Example: ?url=https://nitter.net` - }), 200 + }), + 200 ); } diff --git a/src/render/instantview.ts b/src/render/instantview.ts index c42dda4..280925b 100644 --- a/src/render/instantview.ts +++ b/src/render/instantview.ts @@ -302,8 +302,8 @@ export const renderInstantView = (properties: RenderProperties): ResponseInstruc let previousThreadPieceAuthor: string | null = null; let originalAuthor: string | null = null; - - const useThread = thread?.thread ?? [ thread?.status ] + + const useThread = thread?.thread ?? [thread?.status]; if (!status) { throw new Error('Status is undefined'); @@ -339,7 +339,8 @@ export const renderInstantView = (properties: RenderProperties): ResponseInstruc View full thread

${status.author.name} (@${status.author.screen_name})

- ${useThread.map(status => { + ${useThread + .map(status => { console.log('previousThreadPieceAuthor', previousThreadPieceAuthor); if (!status) { return ''; From 4f4a2d5944effcbe7bd8cfbd62710792774ae7c7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 30 Apr 2024 19:49:41 +0000 Subject: [PATCH 4/6] chore(deps): update dependency wrangler to ^3.53.0 --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 919101d..8069f2a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,7 +32,7 @@ "ts-jest": "^29.1.2", "ts-loader": "^9.5.1", "typescript": "^5.4.5", - "wrangler": "^3.52.0" + "wrangler": "^3.53.0" } }, "node_modules/@ampproject/remapping": { @@ -9407,9 +9407,9 @@ } }, "node_modules/wrangler": { - "version": "3.52.0", - "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.52.0.tgz", - "integrity": "sha512-HR06jTym+yr7+CI3Ggld3nfp1OM9vSC7h4B8vwWHwhi5K0sYg8p44rxV514Gmsv9dkFHegkRP70SM3sjuuxxpQ==", + "version": "3.53.0", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.53.0.tgz", + "integrity": "sha512-JxkvCQekL9j8Mu4CEKM/HEVyDnymWzKQuMUuJH0yum1AilutD5HAP9kVVYmvu7BvwlRyRUAj8TI5OUxXnLCEpQ==", "dev": true, "dependencies": { "@cloudflare/kv-asset-handler": "0.3.2", diff --git a/package.json b/package.json index 3b0df53..ae11c20 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "ts-jest": "^29.1.2", "ts-loader": "^9.5.1", "typescript": "^5.4.5", - "wrangler": "^3.52.0" + "wrangler": "^3.53.0" }, "dependencies": { "@hono/sentry": "^1.0.1", From db1a54830b263d79e74da3b97fb8e0c6b4bf39e7 Mon Sep 17 00:00:00 2001 From: dangered wolf Date: Tue, 30 Apr 2024 15:52:10 -0400 Subject: [PATCH 5/6] Bump undici to patch CVE --- package-lock.json | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index 919101d..59b71d6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2082,7 +2082,7 @@ "@miniflare/core": "2.14.2", "@miniflare/shared": "2.14.2", "http-cache-semantics": "^4.1.0", - "undici": "5.28.2" + "undici": "5.28.4" }, "engines": { "node": ">=16.13" @@ -2102,7 +2102,7 @@ "dotenv": "^10.0.0", "kleur": "^4.1.4", "set-cookie-parser": "^2.4.8", - "undici": "5.28.2", + "undici": "5.28.4", "urlpattern-polyfill": "^4.0.3" }, "engines": { @@ -2140,7 +2140,7 @@ "@miniflare/core": "2.14.2", "@miniflare/shared": "2.14.2", "@miniflare/storage-memory": "2.14.2", - "undici": "5.28.2" + "undici": "5.28.4" }, "engines": { "node": ">=16.13" @@ -2155,7 +2155,7 @@ "@miniflare/core": "2.14.2", "@miniflare/shared": "2.14.2", "html-rewriter-wasm": "^0.4.1", - "undici": "5.28.2" + "undici": "5.28.4" }, "engines": { "node": ">=16.13" @@ -2193,7 +2193,7 @@ "dependencies": { "@miniflare/core": "2.14.2", "@miniflare/shared": "2.14.2", - "undici": "5.28.2" + "undici": "5.28.4" }, "engines": { "node": ">=16.13" @@ -2309,7 +2309,7 @@ "dependencies": { "@miniflare/core": "2.14.2", "@miniflare/shared": "2.14.2", - "undici": "5.28.2", + "undici": "5.28.4", "ws": "^8.2.2" }, "engines": { @@ -7370,7 +7370,7 @@ "exit-hook": "^2.2.1", "glob-to-regexp": "^0.4.1", "stoppable": "^1.1.0", - "undici": "^5.28.2", + "undici": "^5.28.4", "workerd": "1.20240419.0", "ws": "^8.11.0", "youch": "^3.2.2", @@ -9117,9 +9117,9 @@ } }, "node_modules/undici": { - "version": "5.28.2", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.2.tgz", - "integrity": "sha512-wh1pHJHnUeQV5Xa8/kyQhO7WFa8M34l026L5P/+2TYiakvGy5Rdc8jWZVyG7ieht/0WgJLEd3kcU5gKx+6GC8w==", + "version": "5.28.4", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz", + "integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==", "dev": true, "dependencies": { "@fastify/busboy": "^2.0.0" From 5b0c47dba71f526d490aa2e637986983e8245e5c Mon Sep 17 00:00:00 2001 From: dangered wolf Date: Tue, 30 Apr 2024 21:41:14 -0400 Subject: [PATCH 6/6] Fix bug in instant view when quoting another person --- src/render/instantview.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/render/instantview.ts b/src/render/instantview.ts index 280925b..238dd8c 100644 --- a/src/render/instantview.ts +++ b/src/render/instantview.ts @@ -290,7 +290,7 @@ const generateStatus = ( ${!isQuote && status.quote ? generateStatus(status.quote, author, true, null) : notApplicableComment} `.format({ quoteHeader: isQuote - ? `

Quoting ${author.name} (@${author.screen_name})

` + ? `

Quoting ${status.author.name} (@${status.author.screen_name})

` : '' }); };