Bluesky app fork with some witchin' additions 馃挮 witchsky.app
bluesky fork client
119
fork

Configure Feed

Select the types of activity you want to include in your feed.

at a876aae44ea07494ebea9727350aa060b81f317b 126 lines 3.2 kB view raw
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}