forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1import {AppBskyRichtextFacet,type RichText,UnicodeString} from '@atproto/api'
2
3import {toShortUrl} from './url-helpers'
4
5export function restoreLinks(
6 text: string,
7 facets?: AppBskyRichtextFacet.Main[],
8): string {
9 if (!facets?.length) {
10 return text
11 }
12
13 const rt = new UnicodeString(text)
14 const parts: string[] = []
15 let lastIndex = 0
16
17 const sortedFacets = [...facets].sort(
18 (a, b) => a.index.byteStart - b.index.byteStart,
19 )
20
21 for (const facet of sortedFacets) {
22 const isLink = facet.features.find(AppBskyRichtextFacet.isLink)
23 if (!isLink) {
24 continue
25 }
26
27 parts.push(rt.slice(lastIndex, facet.index.byteStart))
28 parts.push(isLink.uri)
29 lastIndex = facet.index.byteEnd
30 }
31
32 parts.push(rt.slice(lastIndex))
33
34 return parts.join('')
35}
36
37export function shortenLinks(rt: RichText): RichText {
38 if (!rt.facets?.length) {
39 return rt
40 }
41 rt = rt.clone()
42 // enumerate the link facets
43 if (rt.facets) {
44 for (const facet of rt.facets) {
45 const isLink = !!facet.features.find(AppBskyRichtextFacet.isLink)
46 if (!isLink) {
47 continue
48 }
49
50 // extract and shorten the URL
51 const {byteStart, byteEnd} = facet.index
52 const url = rt.unicodeText.slice(byteStart, byteEnd)
53 const shortened = new UnicodeString(toShortUrl(url))
54
55 // insert the shorten URL
56 rt.insert(byteStart, shortened.utf16)
57 // update the facet to cover the new shortened URL
58 facet.index.byteStart = byteStart
59 facet.index.byteEnd = byteStart + shortened.length
60 // remove the old URL
61 rt.delete(byteStart + shortened.length, byteEnd + shortened.length)
62 }
63 }
64 return rt
65}
66
67// filter out any mention facets that didn't map to a user
68export function stripInvalidMentions(rt: RichText): RichText {
69 if (!rt.facets?.length) {
70 return rt
71 }
72 rt = rt.clone()
73 if (rt.facets) {
74 rt.facets = rt.facets?.filter(facet => {
75 const mention = facet.features.find(AppBskyRichtextFacet.isMention)
76 if (mention && !mention.did) {
77 return false
78 }
79 return true
80 })
81 }
82 return rt
83}
84
85export function parseMarkdownLinks(text: string): {
86 text: string
87 facets: AppBskyRichtextFacet.Main[]
88} {
89 const regex = /\[([^\]]+)\]\(([^)]+)\)/g
90 let match
91 let newText = ''
92 let lastIndex = 0
93 const facets: AppBskyRichtextFacet.Main[] = []
94
95 while ((match = regex.exec(text)) !== null) {
96 const [fullMatch, linkText, linkUrl] = match
97 const matchStart = match.index
98 newText += text.slice(lastIndex, matchStart)
99 const startByte = new UnicodeString(newText).length
100 newText += linkText
101 const endByte = new UnicodeString(newText).length
102 let validUrl = linkUrl
103 if (!validUrl.startsWith('http://') && !validUrl.startsWith('https://') && !validUrl.startsWith('mailto:')) {
104 validUrl = `https://${validUrl}`
105 }
106
107 facets.push({
108 index: {
109 byteStart: startByte,
110 byteEnd: endByte,
111 },
112 features: [
113 {
114 $type: 'app.bsky.richtext.facet#link',
115 uri: validUrl,
116 },
117 ],
118 })
119
120 lastIndex = matchStart + fullMatch.length
121 }
122
123 newText += text.slice(lastIndex)
124
125 return {text: newText, facets}
126}