Skip to content

Commit

Permalink
feat: mention mapping
Browse files Browse the repository at this point in the history
  • Loading branch information
ralmn committed Nov 10, 2024
1 parent c143ced commit 17553c0
Show file tree
Hide file tree
Showing 12 changed files with 146 additions and 25 deletions.
5 changes: 5 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,8 @@ node_modules/
deployment/
docs/
Dockerfile

# Temporary files
cache*.json
cookies*.json
mention-mapping*.json
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ coverage/
# Temporary files
cache*.json
cookies*.json
mention-mapping*.json
23 changes: 23 additions & 0 deletions src/configuration/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand All @@ -13,6 +14,7 @@ import {
BLUESKY_PASSWORD,
MASTODON_ACCESS_TOKEN,
MASTODON_INSTANCE,
MENTION_MAPPING_PATH,
SYNC_BLUESKY,
SYNC_DRY_RUN,
SYNC_MASTODON,
Expand All @@ -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<{
Expand All @@ -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();
Expand Down Expand Up @@ -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,
};
};
1 change: 1 addition & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
4 changes: 3 additions & 1 deletion src/helpers/post/make-bluesky-post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ 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";

export const makeBlueskyPost = async (
client: AtpAgent,
tweet: Tweet,
mentionsMapping: MentionMapping[],
): Promise<BlueskyPost> => {
const username = await client
.getProfile({ actor: BLUESKY_IDENTIFIER })
Expand Down Expand Up @@ -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,
Expand Down
4 changes: 3 additions & 1 deletion src/helpers/post/make-mastodon-post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ 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";

export const makeMastodonPost = async (
client: mastodon.rest.Client,
tweet: Tweet,
mentionsMapping: MentionMapping[],
): Promise<MastodonPost> => {
const cachedPosts = await getCachedPosts();

Expand All @@ -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;
Expand Down
10 changes: 8 additions & 2 deletions src/helpers/post/make-post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -39,6 +40,7 @@ export const makePost = async (
blueskyClient: AtpAgent | null,
log: Ora,
counters: { current: number; total: number },
mentionsMapping: MentionMapping[],
): Promise<Post> => {
const postExcerpt = getPostExcerpt(tweet.text ?? VOID).padEnd(32, " ");
log.color = "magenta";
Expand All @@ -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 = {
Expand Down
64 changes: 53 additions & 11 deletions src/helpers/tweet/__tests__/split-tweet-text.spec.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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([
Expand All @@ -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]);
Expand All @@ -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([
Expand All @@ -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]);
Expand All @@ -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([
Expand All @@ -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([
Expand All @@ -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);

Expand Down Expand Up @@ -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([
Expand All @@ -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([
Expand All @@ -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([
Expand All @@ -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([
Expand All @@ -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: "[email protected]",
bluesky: "ralmn.fr",
},
];

const mastodonStatuses = await splitTextForMastodon(
tweet,
mentionsMapping,
MASTODON_USERNAME,
);
const blueskyStatuses = await splitTextForBluesky(tweet, mentionsMapping);

expect(mastodonStatuses).toEqual([
`Hello @[email protected] how are you ?`,
]);
expect(blueskyStatuses).toEqual([`Hello @ralmn.fr how are you ?`]);
});
});
});
Loading

0 comments on commit 17553c0

Please sign in to comment.