From 2ddde92e847cd20e3937059b35458bc7be4861a4 Mon Sep 17 00:00:00 2001 From: Mathieu Hirel <1053712+ralmn@users.noreply.github.com> Date: Sun, 10 Nov 2024 17:27:40 +0100 Subject: [PATCH] feat: mention mapping --- .dockerignore | 5 + .gitignore | 1 + package-lock.json | 617 +++++++++++++++++- package.json | 4 +- src/configuration/configuration.ts | 23 + src/constants.ts | 1 + src/helpers/post/make-bluesky-post.ts | 4 +- src/helpers/post/make-mastodon-post.ts | 4 +- src/helpers/post/make-post.ts | 10 +- .../tweet/__tests__/split-tweet-text.spec.ts | 64 +- .../split-tweet-text/split-tweet-text.ts | 49 +- src/index.ts | 2 + src/services/posts-synchronizer.service.ts | 3 + src/types/mentionMapping.ts | 5 + 14 files changed, 759 insertions(+), 33 deletions(-) create mode 100644 src/types/mentionMapping.ts diff --git a/.dockerignore b/.dockerignore index 0590d59..5dbe713 100644 --- a/.dockerignore +++ b/.dockerignore @@ -3,3 +3,8 @@ node_modules/ deployment/ docs/ Dockerfile + +# Temporary files +cache*.json +cookies*.json +mention-mapping*.json diff --git a/.gitignore b/.gitignore index 5b4bfdc..cd3cb65 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ coverage/ # Temporary files cache*.json cookies*.json +mention-mapping*.json diff --git a/package-lock.json b/package-lock.json index 342b71a..a09766d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,9 @@ "node-html-parser": "^6.1.13", "ora": "^8.1.0", "sharp": "^0.33.5", - "tough-cookie": "^4.1.4" + "tough-cookie": "^4.1.4", + "ts-node": "^10.9.2", + "tsx": "^4.19.2" }, "devDependencies": { "@commitlint/cli": "^19.5.0", @@ -647,6 +649,28 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@emnapi/runtime": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.2.0.tgz", @@ -950,6 +974,22 @@ "node": ">=12" } }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz", + "integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/openbsd-x64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", @@ -1606,7 +1646,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", - "dev": true, "engines": { "node": ">=6.0.0" } @@ -1623,8 +1662,7 @@ "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.25", @@ -2163,6 +2201,30 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "license": "MIT" + }, "node_modules/@types/argparse": { "version": "1.0.38", "resolved": "https://registry.npmjs.org/@types/argparse/-/argparse-1.0.38.tgz", @@ -2705,7 +2767,6 @@ "version": "8.11.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", - "dev": true, "bin": { "acorn": "bin/acorn" }, @@ -2722,6 +2783,18 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/ajv": { "version": "8.13.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.13.0.tgz", @@ -2793,6 +2866,12 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "license": "MIT" + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -3359,6 +3438,12 @@ "typescript": ">=4" } }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "license": "MIT" + }, "node_modules/cross-fetch": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", @@ -3568,6 +3653,15 @@ "node": ">=8" } }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -4437,7 +4531,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "optional": true, "os": [ @@ -4547,6 +4640,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-tsconfig": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.1.tgz", + "integrity": "sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==", + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/git-raw-commits": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-4.0.0.tgz", @@ -5690,6 +5795,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "license": "ISC" + }, "node_modules/masto": { "version": "6.8.0", "resolved": "https://registry.npmjs.org/masto/-/masto-6.8.0.tgz", @@ -6503,6 +6614,15 @@ "node": ">=8" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/restore-cursor": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", @@ -7324,6 +7444,49 @@ "node": ">=14.0.0" } }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, "node_modules/tsconfig-paths": { "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", @@ -7362,6 +7525,432 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==" }, + "node_modules/tsx": { + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.2.tgz", + "integrity": "sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g==", + "license": "MIT", + "dependencies": { + "esbuild": "~0.23.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/tsx/node_modules/@esbuild/aix-ppc64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz", + "integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz", + "integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz", + "integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz", + "integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz", + "integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz", + "integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz", + "integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz", + "integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz", + "integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz", + "integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ia32": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz", + "integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-loong64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz", + "integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-mips64el": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz", + "integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ppc64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz", + "integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-riscv64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz", + "integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-s390x": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz", + "integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz", + "integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz", + "integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz", + "integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/sunos-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz", + "integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz", + "integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-ia32": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz", + "integrity": "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz", + "integrity": "sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/esbuild": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz", + "integrity": "sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.23.1", + "@esbuild/android-arm": "0.23.1", + "@esbuild/android-arm64": "0.23.1", + "@esbuild/android-x64": "0.23.1", + "@esbuild/darwin-arm64": "0.23.1", + "@esbuild/darwin-x64": "0.23.1", + "@esbuild/freebsd-arm64": "0.23.1", + "@esbuild/freebsd-x64": "0.23.1", + "@esbuild/linux-arm": "0.23.1", + "@esbuild/linux-arm64": "0.23.1", + "@esbuild/linux-ia32": "0.23.1", + "@esbuild/linux-loong64": "0.23.1", + "@esbuild/linux-mips64el": "0.23.1", + "@esbuild/linux-ppc64": "0.23.1", + "@esbuild/linux-riscv64": "0.23.1", + "@esbuild/linux-s390x": "0.23.1", + "@esbuild/linux-x64": "0.23.1", + "@esbuild/netbsd-x64": "0.23.1", + "@esbuild/openbsd-arm64": "0.23.1", + "@esbuild/openbsd-x64": "0.23.1", + "@esbuild/sunos-x64": "0.23.1", + "@esbuild/win32-arm64": "0.23.1", + "@esbuild/win32-ia32": "0.23.1", + "@esbuild/win32-x64": "0.23.1" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -7463,7 +8052,6 @@ "version": "5.6.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", - "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -7571,6 +8159,12 @@ "requires-port": "^1.0.0" } }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "license": "MIT" + }, "node_modules/vite": { "version": "5.4.8", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.8.tgz", @@ -7987,6 +8581,15 @@ "node": ">=8" } }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 3510466..117f88e 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,9 @@ "node-html-parser": "^6.1.13", "ora": "^8.1.0", "sharp": "^0.33.5", - "tough-cookie": "^4.1.4" + "tough-cookie": "^4.1.4", + "ts-node": "^10.9.2", + "tsx": "^4.19.2" }, "devDependencies": { "@commitlint/cli": "^19.5.0", diff --git a/src/configuration/configuration.ts b/src/configuration/configuration.ts index b14b6f0..cbff301 100644 --- a/src/configuration/configuration.ts +++ b/src/configuration/configuration.ts @@ -4,6 +4,7 @@ import pm2 from "@pm2/io"; import type Counter from "@pm2/io/build/main/utils/metrics/counter"; import type Gauge from "@pm2/io/build/main/utils/metrics/gauge"; import { Scraper } from "@the-convocation/twitter-scraper"; +import * as fs from "fs/promises"; import { createRestAPIClient, mastodon } from "masto"; import ora from "ora"; @@ -13,6 +14,7 @@ import { BLUESKY_PASSWORD, MASTODON_ACCESS_TOKEN, MASTODON_INSTANCE, + MENTION_MAPPING_PATH, SYNC_BLUESKY, SYNC_DRY_RUN, SYNC_MASTODON, @@ -25,6 +27,7 @@ import { getCachedPosts } from "../helpers/cache/get-cached-posts"; import { runMigrations } from "../helpers/cache/run-migrations"; import { TouitomamoutError } from "../helpers/error"; import { oraPrefixer } from "../helpers/logs"; +import { MentionMapping } from "../types/mentionMapping"; import { buildConfigurationRules } from "./build-configuration-rules"; export const configuration = async (): Promise<{ @@ -33,6 +36,7 @@ export const configuration = async (): Promise<{ twitterClient: Scraper; mastodonClient: null | mastodon.rest.Client; blueskyClient: null | AtpAgent; + mentionsMapping: MentionMapping[]; }> => { // Error handling const rules = buildConfigurationRules(); @@ -160,11 +164,30 @@ export const configuration = async (): Promise<{ }); } + let mentionsMapping: MentionMapping[] = []; + //accessSync + try { + const content = (await fs.readFile(MENTION_MAPPING_PATH)).toString(); + mentionsMapping = JSON.parse(content) as MentionMapping[]; + } catch (e) { + const log = ora({ + color: "gray", + prefixText: oraPrefixer("mention-mapping"), + }); + if (e instanceof Error && "code" in e && e.code == "ENOENT") { + log.warn(`No mention mapping file found (${MENTION_MAPPING_PATH})`); + } else { + log.fail(`Error when read ${MENTION_MAPPING_PATH}`); + console.error(e); + } + } + return { mastodonClient, twitterClient, blueskyClient, synchronizedPostsCountAllTime, synchronizedPostsCountThisRun, + mentionsMapping, }; }; diff --git a/src/constants.ts b/src/constants.ts index b25c333..c506d4e 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -44,6 +44,7 @@ export const INSTANCE_ID = (TWITTER_HANDLE ?? "instance") export const STORAGE_DIR = process.env.STORAGE_DIR ?? process.cwd(); export const CACHE_PATH = `${STORAGE_DIR}/cache.${INSTANCE_ID}.json`; export const COOKIES_PATH = `${STORAGE_DIR}/cookies.${INSTANCE_ID}.json`; +export const MENTION_MAPPING_PATH = `${STORAGE_DIR}/mention-mapping.${INSTANCE_ID}.json`; export const SYNC_MASTODON = (process.env.SYNC_MASTODON ?? "false") === "true"; export const SYNC_BLUESKY = (process.env.SYNC_BLUESKY ?? "false") === "true"; export const BACKDATE_BLUESKY_POSTS = diff --git a/src/helpers/post/make-bluesky-post.ts b/src/helpers/post/make-bluesky-post.ts index 5026fb7..28412ed 100644 --- a/src/helpers/post/make-bluesky-post.ts +++ b/src/helpers/post/make-bluesky-post.ts @@ -3,6 +3,7 @@ import { Tweet } from "@the-convocation/twitter-scraper"; import { BLUESKY_IDENTIFIER } from "../../constants"; import { BlueskyCacheChunk, Platform } from "../../types"; +import { MentionMapping } from "../../types/mentionMapping"; import { BlueskyPost } from "../../types/post"; import { getCachedPostChunk } from "../cache/get-cached-post-chunk"; import { splitTextForBluesky } from "../tweet/split-tweet-text"; @@ -10,6 +11,7 @@ import { splitTextForBluesky } from "../tweet/split-tweet-text"; export const makeBlueskyPost = async ( client: AtpAgent, tweet: Tweet, + mentionsMapping: MentionMapping[], ): Promise => { const username = await client .getProfile({ actor: BLUESKY_IDENTIFIER }) @@ -57,7 +59,7 @@ export const makeBlueskyPost = async ( await post.detectFacets(client); // automatically detects mentions and links return { - chunks: await splitTextForBluesky(tweet), + chunks: await splitTextForBluesky(tweet, mentionsMapping), username, replyPost, quotePost, diff --git a/src/helpers/post/make-mastodon-post.ts b/src/helpers/post/make-mastodon-post.ts index 97a87d4..0f62eb3 100644 --- a/src/helpers/post/make-mastodon-post.ts +++ b/src/helpers/post/make-mastodon-post.ts @@ -2,6 +2,7 @@ import { Tweet } from "@the-convocation/twitter-scraper"; import { mastodon } from "masto"; import { Platform } from "../../types"; +import { MentionMapping } from "../../types/mentionMapping"; import { MastodonPost } from "../../types/post"; import { getCachedPosts } from "../cache/get-cached-posts"; import { splitTextForMastodon } from "../tweet/split-tweet-text"; @@ -9,6 +10,7 @@ import { splitTextForMastodon } from "../tweet/split-tweet-text"; export const makeMastodonPost = async ( client: mastodon.rest.Client, tweet: Tweet, + mentionsMapping: MentionMapping[], ): Promise => { const cachedPosts = await getCachedPosts(); @@ -17,7 +19,7 @@ export const makeMastodonPost = async ( .then((account) => account.username); // Get post chunks (including quote in first one when needed) - const chunks = await splitTextForMastodon(tweet, username); + const chunks = await splitTextForMastodon(tweet, mentionsMapping, username); // Get in reply post references let inReplyToId = undefined; diff --git a/src/helpers/post/make-post.ts b/src/helpers/post/make-post.ts index dc0e525..87cea62 100644 --- a/src/helpers/post/make-post.ts +++ b/src/helpers/post/make-post.ts @@ -4,6 +4,7 @@ import { mastodon } from "masto"; import { Ora } from "ora"; import { VOID } from "../../constants"; +import { MentionMapping } from "../../types/mentionMapping"; import { BlueskyPost, MastodonPost, Post } from "../../types/post"; import { oraProgress } from "../logs"; import { getPostExcerpt } from "./get-post-excerpt"; @@ -39,6 +40,7 @@ export const makePost = async ( blueskyClient: AtpAgent | null, log: Ora, counters: { current: number; total: number }, + mentionsMapping: MentionMapping[], ): Promise => { const postExcerpt = getPostExcerpt(tweet.text ?? VOID).padEnd(32, " "); log.color = "magenta"; @@ -52,13 +54,17 @@ export const makePost = async ( // Mastodon post let mastodonPost = null; if (mastodonClient) { - mastodonPost = await makeMastodonPost(mastodonClient, tweet); + mastodonPost = await makeMastodonPost( + mastodonClient, + tweet, + mentionsMapping, + ); } // Bluesky post let blueskyPost = null; if (blueskyClient) { - blueskyPost = await makeBlueskyPost(blueskyClient, tweet); + blueskyPost = await makeBlueskyPost(blueskyClient, tweet, mentionsMapping); } const chunksByPlatform = { diff --git a/src/helpers/tweet/__tests__/split-tweet-text.spec.ts b/src/helpers/tweet/__tests__/split-tweet-text.spec.ts index 3457771..8207f1d 100644 --- a/src/helpers/tweet/__tests__/split-tweet-text.spec.ts +++ b/src/helpers/tweet/__tests__/split-tweet-text.spec.ts @@ -1,6 +1,9 @@ +import { describe } from "node:test"; + import { Tweet } from "@the-convocation/twitter-scraper"; import { MASTODON_INSTANCE } from "../../../constants"; +import { MentionMapping } from "../../../types/mentionMapping"; import { splitTextForBluesky, splitTextForMastodon, @@ -63,9 +66,10 @@ describe("splitTweetText", () => { } as Tweet; const mastodonStatuses = await splitTextForMastodon( tweet, + [], MASTODON_USERNAME, ); - const blueskyStatuses = await splitTextForBluesky(tweet); + const blueskyStatuses = await splitTextForBluesky(tweet, []); checkChunksLengthExpectations(mastodonStatuses, blueskyStatuses); expect(mastodonStatuses).toEqual([ @@ -85,9 +89,10 @@ describe("splitTweetText", () => { } as unknown as Tweet; const mastodonStatuses = await splitTextForMastodon( tweet, + [], MASTODON_USERNAME, ); - const blueskyStatuses = await splitTextForBluesky(tweet); + const blueskyStatuses = await splitTextForBluesky(tweet, []); checkChunksLengthExpectations(mastodonStatuses, blueskyStatuses); expect(mastodonStatuses).toEqual([POST_99_CHARS]); @@ -103,9 +108,10 @@ describe("splitTweetText", () => { } as unknown as Tweet; const mastodonStatuses = await splitTextForMastodon( tweet, + [], MASTODON_USERNAME, ); - const blueskyStatuses = await splitTextForBluesky(tweet); + const blueskyStatuses = await splitTextForBluesky(tweet, []); checkChunksLengthExpectations(mastodonStatuses, blueskyStatuses); expect(mastodonStatuses).toEqual([ @@ -124,9 +130,10 @@ describe("splitTweetText", () => { } as Tweet; const mastodonStatuses = await splitTextForMastodon( tweet, + [], MASTODON_USERNAME, ); - const blueskyStatuses = await splitTextForBluesky(tweet); + const blueskyStatuses = await splitTextForBluesky(tweet, []); checkChunksLengthExpectations(mastodonStatuses, blueskyStatuses); expect(mastodonStatuses).toEqual([POST_299_CHARS]); @@ -142,9 +149,10 @@ describe("splitTweetText", () => { } as unknown as Tweet; const mastodonStatuses = await splitTextForMastodon( tweet, + [], MASTODON_USERNAME, ); - const blueskyStatuses = await splitTextForBluesky(tweet); + const blueskyStatuses = await splitTextForBluesky(tweet, []); checkChunksLengthExpectations(mastodonStatuses, blueskyStatuses); expect(mastodonStatuses).toEqual([ @@ -164,9 +172,10 @@ describe("splitTweetText", () => { } as unknown as Tweet; const mastodonStatuses = await splitTextForMastodon( tweet, + [], MASTODON_USERNAME, ); - const blueskyStatuses = await splitTextForBluesky(tweet); + const blueskyStatuses = await splitTextForBluesky(tweet, []); checkChunksLengthExpectations(mastodonStatuses, blueskyStatuses); expect(mastodonStatuses).toStrictEqual([ @@ -191,9 +200,10 @@ describe("splitTweetText", () => { } as unknown as Tweet; const mastodonStatuses = await splitTextForMastodon( tweet, + [], MASTODON_USERNAME, ); - const blueskyStatuses = await splitTextForBluesky(tweet); + const blueskyStatuses = await splitTextForBluesky(tweet, []); checkChunksLengthExpectations(mastodonStatuses, blueskyStatuses); @@ -223,9 +233,10 @@ describe("splitTweetText", () => { } as Tweet; const mastodonStatuses = await splitTextForMastodon( tweet, + [], MASTODON_USERNAME, ); - const blueskyStatuses = await splitTextForBluesky(tweet); + const blueskyStatuses = await splitTextForBluesky(tweet, []); checkChunksLengthExpectations(mastodonStatuses, blueskyStatuses); expect(mastodonStatuses).toEqual([ @@ -244,9 +255,10 @@ describe("splitTweetText", () => { } as Tweet; const mastodonStatuses = await splitTextForMastodon( tweet, + [], MASTODON_USERNAME, ); - const blueskyStatuses = await splitTextForBluesky(tweet); + const blueskyStatuses = await splitTextForBluesky(tweet, []); checkChunksLengthExpectations(mastodonStatuses, blueskyStatuses); expect(mastodonStatuses).toEqual([ @@ -271,9 +283,10 @@ describe("splitTweetText", () => { } as Tweet; const mastodonStatuses = await splitTextForMastodon( tweet, + [], MASTODON_USERNAME, ); - const blueskyStatuses = await splitTextForBluesky(tweet); + const blueskyStatuses = await splitTextForBluesky(tweet, []); checkChunksLengthExpectations(mastodonStatuses, blueskyStatuses); expect(mastodonStatuses).toEqual([ @@ -294,9 +307,10 @@ describe("splitTweetText", () => { } as Tweet; const mastodonStatuses = await splitTextForMastodon( tweet, + [], MASTODON_USERNAME, ); - const blueskyStatuses = await splitTextForBluesky(tweet); + const blueskyStatuses = await splitTextForBluesky(tweet, []); checkChunksLengthExpectations(mastodonStatuses, blueskyStatuses); expect(mastodonStatuses).toEqual([ @@ -308,4 +322,32 @@ describe("splitTweetText", () => { ]); }); }); + + describe("when mention user with existing mention mapping", () => { + it("should return text with the replaced mention", async () => { + const tweet = { + text: "Hello @ralmn45 how are you ?", + mentions: [{ id: "12345", username: "ralmn45", name: "ralmn" }], + } as Tweet; + const mentionsMapping: MentionMapping[] = [ + { + twitter: "ralmn45", + mastodon: "ralmn@mastodon.xyz", + bluesky: "ralmn.fr", + }, + ]; + + const mastodonStatuses = await splitTextForMastodon( + tweet, + mentionsMapping, + MASTODON_USERNAME, + ); + const blueskyStatuses = await splitTextForBluesky(tweet, mentionsMapping); + + expect(mastodonStatuses).toEqual([ + `Hello @ralmn@mastodon.xyz how are you ?`, + ]); + expect(blueskyStatuses).toEqual([`Hello @ralmn.fr how are you ?`]); + }); + }); }); diff --git a/src/helpers/tweet/split-tweet-text/split-tweet-text.ts b/src/helpers/tweet/split-tweet-text/split-tweet-text.ts index 1169aee..0bc765d 100644 --- a/src/helpers/tweet/split-tweet-text/split-tweet-text.ts +++ b/src/helpers/tweet/split-tweet-text/split-tweet-text.ts @@ -5,13 +5,15 @@ import { MASTODON_MAX_POST_LENGTH, } from "../../../constants"; import { Platform } from "../../../types"; +import { MentionMapping } from "../../../types/mentionMapping"; import { buildChunksFromSplitterEntries } from "./build-chunks-from-splitter-entries"; import { extractWordsAndSpacers } from "./extract-words-and-spacers"; import { getMastodonQuoteLinkSection } from "./get-mastodon-quote-link-section"; const splitTweetText = async ( - { text, quotedStatusId, urls }: Tweet, + { text, quotedStatusId, urls, mentions }: Tweet, platform: Platform, + mentionsMapping: MentionMapping[], mastodonUsername?: string, ): Promise => { const maxChunkSize = @@ -24,19 +26,40 @@ const splitTweetText = async ( mastodonUsername, ); + let tweetText = text!; + + if (mentions != null) { + for (const tweetMention of mentions) { + const mapped = mentionsMapping.find( + (mapping) => tweetMention.username == mapping.twitter, + ); + if (mapped == null) { + continue; + } + const newMention = + platform === Platform.MASTODON ? mapped!.mastodon : mapped!.bluesky; + if (newMention != null) { + tweetText = tweetText.replaceAll( + `@${tweetMention.username}`, + `@${newMention}`, + ); + } + } + } + // Small post optimization if (quotedStatusId) { if (platform === Platform.MASTODON) { // Specific optimization for Mastodon (it has to include a link to the quoted status) - if (text!.length - quotedStatusLinkSection.length <= maxChunkSize) { - return [text + quotedStatusLinkSection]; + if (tweetText.length - quotedStatusLinkSection.length <= maxChunkSize) { + return [tweetText + quotedStatusLinkSection]; } } - } else if (text!.length <= maxChunkSize) { - return [text!]; + } else if (tweetText.length <= maxChunkSize) { + return [tweetText]; } - const entries = extractWordsAndSpacers(text!, urls); + const entries = extractWordsAndSpacers(tweetText, urls); return buildChunksFromSplitterEntries( entries, platform, @@ -46,8 +69,14 @@ const splitTweetText = async ( ); }; -export const splitTextForMastodon = (tweet: Tweet, mastodonUsername: string) => - splitTweetText(tweet, Platform.MASTODON, mastodonUsername); +export const splitTextForMastodon = ( + tweet: Tweet, + mentionsMapping: MentionMapping[], + mastodonUsername: string, +) => + splitTweetText(tweet, Platform.MASTODON, mentionsMapping, mastodonUsername); -export const splitTextForBluesky = (tweet: Tweet) => - splitTweetText(tweet, Platform.BLUESKY); +export const splitTextForBluesky = ( + tweet: Tweet, + mentionsMapping: MentionMapping[], +) => splitTweetText(tweet, Platform.BLUESKY, mentionsMapping); diff --git a/src/index.ts b/src/index.ts index e5f5afd..c03e538 100644 --- a/src/index.ts +++ b/src/index.ts @@ -18,6 +18,7 @@ const { synchronizedPostsCountAllTime, synchronizedPostsCountThisRun, blueskyClient, + mentionsMapping, } = await configuration(); /** @@ -47,6 +48,7 @@ const touitomamout = async () => { mastodonClient, blueskyClient, synchronizedPostsCountThisRun, + mentionsMapping, ); synchronizedPostsCountAllTime.set(postsSyncResponse.metrics.totalSynced); diff --git a/src/services/posts-synchronizer.service.ts b/src/services/posts-synchronizer.service.ts index 5c7fea1..8f98f33 100644 --- a/src/services/posts-synchronizer.service.ts +++ b/src/services/posts-synchronizer.service.ts @@ -9,6 +9,7 @@ import { getCachedPosts } from "../helpers/cache/get-cached-posts"; import { oraPrefixer } from "../helpers/logs"; import { makePost } from "../helpers/post/make-post"; import { Media, Metrics, SynchronizerResponse } from "../types"; +import { MentionMapping } from "../types/mentionMapping"; import { blueskySenderService } from "./bluesky-sender.service"; import { mastodonSenderService } from "./mastodon-sender.service"; import { tweetsGetterService } from "./tweets-getter.service"; @@ -21,6 +22,7 @@ export const postsSynchronizerService = async ( mastodonClient: mastodon.rest.Client | null, blueskyClient: AtpAgent | null, synchronizedPostsCountThisRun: Counter.default, + mentionsMapping: MentionMapping[], ): Promise => { const tweets = await tweetsGetterService(twitterClient); @@ -44,6 +46,7 @@ export const postsSynchronizerService = async ( blueskyClient, log, { current: tweetIndex, total: tweets.length }, + mentionsMapping, ); if (!SYNC_DRY_RUN) { diff --git a/src/types/mentionMapping.ts b/src/types/mentionMapping.ts new file mode 100644 index 0000000..4b4b671 --- /dev/null +++ b/src/types/mentionMapping.ts @@ -0,0 +1,5 @@ +export interface MentionMapping { + twitter: string; + mastodon?: string; + bluesky?: string; +}