Skip to content

Instantly share code, notes, and snippets.

@alextes
Created March 25, 2022 13:25
Show Gist options
  • Save alextes/2318083aabf80849c0164b601ef33360 to your computer and use it in GitHub Desktop.
Save alextes/2318083aabf80849c0164b601ef33360 to your computer and use it in GitHub Desktop.
Turning a plain text Twitter bio into one containing urls, mentions, hashtags, and cashtags using TypeScript and React.
// Types describing the various objects found in twitter's entities.
type LinkableUrl = {
display_url: string;
end: number;
expanded_url: string;
start: number;
};
type LinkableMention = {
start: number;
end: number;
username: string;
};
type LinkableCashtag = { start: number; end: number; tag: string };
type LinkableHashtag = { start: number; end: number; tag: string };
// This example only links entities found in the bio. That's `entities.description` in the V2 API.
type Linkables = {
cashtags?: LinkableCashtag[];
hashtags?: LinkableHashtag[];
mentions?: LinkableMention[];
urls?: LinkableUrl[];
};
// These are the elements that together encode a rich bio.
type Text = { type: "text"; text: string[] };
type Url = { type: "url"; linkable: LinkableUrl };
type Hashtag = { type: "hashtag"; linkable: LinkableHashtag };
type Cashtag = { type: "cashtag"; linkable: LinkableCashtag };
type Mention = { type: "mention"; linkable: LinkableMention };
type BioElement = Text | Url | Hashtag | Cashtag | Mention;
// Helps identify what kind of linkable we're working with, then sorts them into a list.
const getSortedLinkables = (linkables: Linkables) => {
const urlLinkables =
linkables.urls?.map((linkable) => ({
...linkable,
type: "url" as const,
})) ?? [];
const mentionLinkables =
linkables.mentions?.map((linkable) => ({
...linkable,
type: "mention" as const,
})) ?? [];
const hashtagLinkables =
linkables.hashtags?.map((linkable) => ({
...linkable,
type: "hashtag" as const,
})) ?? [];
const cashtagLinkables =
linkables.cashtags?.map((linkable) => ({
...linkable,
type: "cashtag" as const,
})) ?? [];
return [
...urlLinkables,
...mentionLinkables,
...hashtagLinkables,
...cashtagLinkables,
].sort((l1, l2) => (l1.start < l2.start ? -1 : 1));
};
const buildBioElements = (bio: string, linkables: Linkables) => {
// Linkable indices appear to assume a list of code points not UTF code units which JS uses by default.
const bioCodePoints = Array.from(bio);
const sortedLinkables = getSortedLinkables(linkables);
const bioElements: BioElement[] = [];
let lastEndIndex = 0;
for (const linkable of sortedLinkables) {
const text = bioCodePoints.slice(lastEndIndex, linkable.start);
bioElements.push({ type: "text", text });
bioElements.push({ type: linkable.type, linkable } as BioElement);
lastEndIndex = linkable.end;
}
if (lastEndIndex !== bioCodePoints.length - 1) {
bioElements.push({
type: "text",
text: bioCodePoints.slice(lastEndIndex ?? 0),
});
}
return bioElements;
};
@alextes
Copy link
Author

alextes commented Mar 25, 2022

This was a pain to write, I didn't find any helpful code, just old libraries or gists using jQuery. Thought I'd share.

This snippet effectively produces a list of objects telling you exactly what you need to render. i.e. piece of text, now a link with this text but that href, now a mention etc. How you render each of those is completely up to you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment