diff --git a/README.md b/README.md index 434a9c0..89a9f0e 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ ### Change `x.com` to `fixupx.com` in your link -### For Twitter links on Discord, send a Twitter link and type `s/e/p` to make `twittpr.com`. +### For `twitter.com` links on Discord, send a link and type `s/e/p` to make `twittpr.com`. ### ℹ️ Note: Some extra features described may currently broken due to recent Twitter/X API changes. [Tracking thread for the API changes](https://github.com/FixTweet/FixTweet/issues/333) diff --git a/package-lock.json b/package-lock.json index 007aff5..5bfdc3e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,15 +13,15 @@ "toucan-js": "^3.3.0" }, "devDependencies": { - "@cloudflare/workers-types": "^4.20231010.0", + "@cloudflare/workers-types": "^4.20231016.0", "@microsoft/eslint-formatter-sarif": "^3.0.0", "@sentry/esbuild-plugin": "^2.8.0", - "@sentry/integrations": "^7.74.0", - "@types/jest": "^29.5.5", + "@sentry/integrations": "^7.74.1", + "@types/jest": "^29.5.6", "@typescript-eslint/eslint-plugin": "^6.8.0", "@typescript-eslint/parser": "^6.8.0", "dotenv": "^16.3.1", - "eslint": "^8.51.0", + "eslint": "^8.52.0", "eslint-config-prettier": "^9.0.0", "eslint-config-typescript": "^3.0.0", "eslint-plugin-optimize-regex": "^1.2.1", @@ -32,7 +32,7 @@ "ts-jest": "^29.1.1", "ts-loader": "^9.5.0", "typescript": "^5.2.2", - "wrangler": "^3.13.1" + "wrangler": "^3.14.0" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -1223,9 +1223,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.51.0.tgz", - "integrity": "sha512-HxjQ8Qn+4SI3/AFv6sOrDB+g6PpUTDwSJiQqOrnneEk8L71161srI9gjzzZvYVbzHiVg/BvcH95+cK/zfIt4pg==", + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.52.0.tgz", + "integrity": "sha512-mjZVbpaeMZludF2fsWLD0Z9gCref1Tk4i9+wddjRvpUNqqcndPkBD09N/Mapey0b3jaXbLm2kICwFv2E64QinA==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -1241,12 +1241,12 @@ } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.11", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", - "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", + "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", + "@humanwhocodes/object-schema": "^2.0.1", "debug": "^4.1.1", "minimatch": "^3.0.5" }, @@ -1268,9 +1268,9 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", + "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", "dev": true }, "node_modules/@iarna/toml": { @@ -2135,6 +2135,42 @@ "node": ">=8" } }, + "node_modules/@sentry/integrations/node_modules/@sentry/core": { + "version": "7.74.1", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.74.1.tgz", + "integrity": "sha512-LvEhOSfdIvwkr+PdlrT/aA/iOLhkXrSkvjqAQyogE4ddCWeYfS0NoirxNt1EaxMBAWKhYZRqzkA7WA4LDLbzlA==", + "dev": true, + "dependencies": { + "@sentry/types": "7.74.1", + "@sentry/utils": "7.74.1", + "tslib": "^2.4.1 || ^1.9.3" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/integrations/node_modules/@sentry/types": { + "version": "7.74.1", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.74.1.tgz", + "integrity": "sha512-2jIuPc+YKvXqZETwr2E8VYnsH1zsSUR/wkIvg1uTVeVNyoowJv+YsOtCdeGyL2AwiotUBSPKu7O1Lz0kq5rMOQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/integrations/node_modules/@sentry/utils": { + "version": "7.74.1", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.74.1.tgz", + "integrity": "sha512-qUsqufuHYcy5gFhLZslLxA5kcEOkkODITXW3c7D+x+8iP/AJqa8v8CeUCVNS7RetHCuIeWAbbTClC4c411EwQg==", + "dev": true, + "dependencies": { + "@sentry/types": "7.74.1", + "tslib": "^2.4.1 || ^1.9.3" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/@sentry/node": { "version": "7.74.1", "resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.74.1.tgz", @@ -2313,9 +2349,9 @@ } }, "node_modules/@types/jest": { - "version": "29.5.5", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.5.tgz", - "integrity": "sha512-ebylz2hnsWR9mYvmBFbXJXr+33UPc4+ZdxyDXh5w0FlPBTfCVN3wPL+kuOiQt3xvrK419v7XWeAs+AeOksafXg==", + "version": "29.5.6", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.6.tgz", + "integrity": "sha512-/t9NnzkOpXb4Nfvg17ieHE6EeSjDS2SGSpNYfoLbUAeL/EOueU/RSdOWFpfQTXBEM7BguYW1XQ0EbM+6RlIh6w==", "dev": true, "dependencies": { "expect": "^29.0.0", @@ -2553,6 +2589,12 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, "node_modules/@webassemblyjs/ast": { "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", @@ -3605,18 +3647,19 @@ } }, "node_modules/eslint": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.51.0.tgz", - "integrity": "sha512-2WuxRZBrlwnXi+/vFSJyjMqrNjtJqiasMzehF0shoLaW7DzS3/9Yvrmq5JiT66+pNjiX4UBnLDiKHcWAr/OInA==", + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.52.0.tgz", + "integrity": "sha512-zh/JHnaixqHZsolRB/w9/02akBk9EPrOs9JwcTP2ek7yL5bVvXuRariiaAjjoJ5DvuwQ1WAE/HsMz+w17YgBCg==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.51.0", - "@humanwhocodes/config-array": "^0.11.11", + "@eslint/js": "8.52.0", + "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", @@ -7320,9 +7363,9 @@ } }, "node_modules/wrangler": { - "version": "3.13.2", - "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.13.2.tgz", - "integrity": "sha512-Z/ZrAL2mJc7E4ialOV9c3wGry0qagp9DfPl5XVd67vtlFPeTSR+1pemtZ5+qI2BXi59kP3OGHBKrrIyG0d9csg==", + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.14.0.tgz", + "integrity": "sha512-4vzw11yG1/KXpYKbumvRJ61Iyhm/yKXb/ayOw/2xiIRdKdpsfN9/796d2l525+CDaGwZWswpLENe6ZMS0p/Ghg==", "dev": true, "dependencies": { "@cloudflare/kv-asset-handler": "^0.2.0", diff --git a/package.json b/package.json index 6eb00b3..959b400 100644 --- a/package.json +++ b/package.json @@ -16,15 +16,15 @@ "author": "dangered wolf", "license": "MIT", "devDependencies": { - "@cloudflare/workers-types": "^4.20231010.0", + "@cloudflare/workers-types": "^4.20231016.0", "@microsoft/eslint-formatter-sarif": "^3.0.0", "@sentry/esbuild-plugin": "^2.8.0", - "@sentry/integrations": "^7.74.0", - "@types/jest": "^29.5.5", + "@sentry/integrations": "^7.74.1", + "@types/jest": "^29.5.6", "@typescript-eslint/eslint-plugin": "^6.8.0", "@typescript-eslint/parser": "^6.8.0", "dotenv": "^16.3.1", - "eslint": "^8.51.0", + "eslint": "^8.52.0", "eslint-config-prettier": "^9.0.0", "eslint-config-typescript": "^3.0.0", "eslint-plugin-optimize-regex": "^1.2.1", @@ -35,7 +35,7 @@ "ts-jest": "^29.1.1", "ts-loader": "^9.5.0", "typescript": "^5.2.2", - "wrangler": "^3.13.1" + "wrangler": "^3.14.0" }, "dependencies": { "itty-router": "^4.0.23", diff --git a/src/api/status.ts b/src/api/status.ts index 9a8d4f5..7913b0a 100644 --- a/src/api/status.ts +++ b/src/api/status.ts @@ -300,9 +300,9 @@ export const statusAPI = async ( const quoteTweet = tweet.quoted_status_result; if (quoteTweet) { apiTweet.quote = (await populateTweetProperties(quoteTweet, res, language)) as APITweet; - /* Only override the embed_card if it's a basic tweet, since media always takes precedence */ - if (apiTweet.embed_card === 'tweet') { - apiTweet.embed_card = apiTweet.quote.embed_card; + /* Only override the twitter_card if it's a basic tweet, since media always takes precedence */ + if (apiTweet.twitter_card === 'tweet' && apiTweet.quote !== null) { + apiTweet.twitter_card = apiTweet.quote.twitter_card; } } diff --git a/src/embed/status.ts b/src/embed/status.ts index 7f7df01..a078ca6 100644 --- a/src/embed/status.ts +++ b/src/embed/status.ts @@ -36,6 +36,16 @@ export const handleStatus = async ( 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 */ + if (flags?.api) { + return { + response: new Response(JSON.stringify(api), { + headers: { ...Constants.RESPONSE_HEADERS, ...Constants.API_RESPONSE_HEADERS }, + status: api.code + }) + }; + } + /* If there was any errors fetching the Tweet, we'll return it */ switch (api.code) { case 401: @@ -66,16 +76,6 @@ export const handleStatus = async ( let ivbody = ''; - /* Catch this request if it's an API response */ - if (flags?.api) { - return { - response: new Response(JSON.stringify(api), { - headers: { ...Constants.RESPONSE_HEADERS, ...Constants.API_RESPONSE_HEADERS }, - status: api.code - }) - }; - } - let overrideMedia: APIMedia | undefined; // Check if mediaNumber exists, and if that media exists in tweet.media.all. If it does, we'll store overrideMedia variable diff --git a/src/helpers/mosaic.ts b/src/helpers/mosaic.ts index 44e5c60..e686269 100644 --- a/src/helpers/mosaic.ts +++ b/src/helpers/mosaic.ts @@ -1,16 +1,26 @@ import { Constants } from '../constants'; +const getDomain = (twitterId: string): string | null => { + const mosaicDomains = Constants.MOSAIC_DOMAIN_LIST; + + if (mosaicDomains.length === 0) { + return null; + } + + let hash = 0; + for (let i = 0; i < twitterId.length; i++) { + const char = twitterId.charCodeAt(i); + hash = (hash << 5) - hash + char; + } + return mosaicDomains[Math.abs(hash) % mosaicDomains.length]; +} + /* Handler for mosaic (multi-image combiner) */ export const handleMosaic = async ( mediaList: APIPhoto[], id: string ): Promise => { - const mosaicDomains = Constants.MOSAIC_DOMAIN_LIST; - let selectedDomain: string | null = null; - while (selectedDomain === null && mosaicDomains.length > 0) { - const domain = mosaicDomains[Math.floor(Math.random() * mosaicDomains.length)]; - selectedDomain = domain; - } + const selectedDomain: string | null = getDomain(id); /* Fallback if there are no Mosaic servers */ if (selectedDomain === null) { diff --git a/src/worker.ts b/src/worker.ts index 83f3d18..0565029 100644 --- a/src/worker.ts +++ b/src/worker.ts @@ -153,7 +153,7 @@ const statusRequest = async (request: IRequest, event: FetchEvent, flags: InputF Since we obviously have no media to give the user, we'll just redirect to the Tweet. Embeds will return as usual to bots as if direct media was never specified. */ - if (!isBotUA) { + if (!isBotUA && !flags.api) { const baseUrl = getBaseRedirectUrl(request); /* Do not cache if using a custom redirect */ const cacheControl = baseUrl !== Constants.TWITTER_ROOT ? 'max-age=0' : undefined;