This repository has no description
0

Configure Feed

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

semble / src / webapp / lib / utils / link.ts
6.4 kB 217 lines
1export const getDomain = (url: string) => { 2 return new URL(url).hostname; 3}; 4 5export const getUrlFromSlug = (slug: string[]) => { 6 const decoded = slug.map(decodeURIComponent); 7 const url = decoded.join('/'); 8 9 // only normalize if the scheme has a single slash after it (i.e. malformed) 10 const normalizedUrl = /^([a-zA-Z]+:)\/[^/]/.test(url) 11 ? url.replace(/^([a-zA-Z]+:)\//, '$1//') 12 : url; 13 14 return normalizedUrl; 15}; 16 17export const isCollectionPage = (url: string = window.location.pathname) => { 18 try { 19 const { pathname } = new URL(url); 20 // expect /profile/:handle/collections/:id 21 const pattern = /^\/profile\/[^/]+\/collections\/[^/]+\/?$/; 22 return pattern.test(pathname); 23 } catch { 24 // invalid URL 25 return false; 26 } 27}; 28 29export const isProfilePage = (url: string = window.location.pathname) => { 30 try { 31 const { pathname } = new URL(url, window.location.origin); 32 // matches: 33 // /profile/:handle 34 // /profile/:handle/:subroute 35 const pattern = /^\/profile\/[^/]+(?:\/[^/]+)?\/?$/; 36 return pattern.test(pathname); 37 } catch { 38 // invalid URL 39 return false; 40 } 41}; 42 43export enum SupportedPlatform { 44 BLUESKY_POST = 'bluesky post', 45 BLACKSKY_POST = 'blacksky post', 46 SEMBLE_COLLECTION = 'semble collection', 47 SPOTIFY = 'spotify', 48 PLYRFM_TRACK = 'plyr.fm', 49 YOUTUBE_VIDEO = 'youtube video', 50 BANDCAMP_ALBUM = 'bandcamp album', 51 BANDCAMP_TRACK = 'bandcamp track', 52 DEFAULT = 'default', 53} 54 55type PlatformData = 56 | { 57 type: SupportedPlatform.SEMBLE_COLLECTION; 58 url: string; 59 handle: string; 60 rkey: string; 61 } 62 | { 63 type: Exclude<SupportedPlatform, SupportedPlatform.SEMBLE_COLLECTION>; 64 url: string; 65 }; 66 67export const detectUrlPlatform = (url: string): PlatformData => { 68 if (isCollectionPage(url)) { 69 try { 70 const parsedUrl = new URL(url, window.location.origin); 71 const pathParts = parsedUrl.pathname.split('/').filter(Boolean); 72 // expected format: profile/:handle/collections/:rkey 73 const handle = pathParts[1]; 74 const rkey = pathParts[3]; 75 return { type: SupportedPlatform.SEMBLE_COLLECTION, url, handle, rkey }; 76 } catch { 77 return { 78 type: SupportedPlatform.SEMBLE_COLLECTION, 79 url, 80 handle: '', 81 rkey: '', 82 }; 83 } 84 } 85 86 try { 87 const parsedUrl = new URL(url); 88 89 // bluesky posts 90 // https://bsky.app/profile/handle/post/id 91 if ( 92 parsedUrl.hostname === 'bsky.app' && 93 parsedUrl.pathname.includes('/post/') 94 ) { 95 return { type: SupportedPlatform.BLUESKY_POST, url }; 96 } 97 98 // blacksky posts 99 // https://blacksky.community/profile/handle/post/id 100 if ( 101 parsedUrl.hostname === 'blacksky.community' && 102 parsedUrl.pathname.includes('/post/') 103 ) { 104 return { type: SupportedPlatform.BLACKSKY_POST, url }; 105 } 106 107 // youtube 108 if (parsedUrl.hostname === 'youtu.be') { 109 const videoId = parsedUrl.pathname.split('/')[1]; 110 const t = parsedUrl.searchParams.get('t') ?? '0'; 111 const seek = encodeURIComponent(t.replace(/s$/, '')); 112 113 if (videoId) { 114 return { 115 type: SupportedPlatform.YOUTUBE_VIDEO, 116 url: `https://www.youtube.com/embed/${videoId}?start=${seek}`, 117 }; 118 } 119 } 120 121 // youtube 122 if ( 123 parsedUrl.hostname === 'www.youtube.com' || 124 parsedUrl.hostname === 'youtube.com' || 125 parsedUrl.hostname === 'm.youtube.com' || 126 parsedUrl.hostname === 'music.youtube.com' 127 ) { 128 const [__, page, shortOrLiveVideoId] = parsedUrl.pathname.split('/'); 129 130 const isShorts = page === 'shorts'; 131 const isLive = page === 'live'; 132 const videoId = 133 isShorts || isLive 134 ? shortOrLiveVideoId 135 : (parsedUrl.searchParams.get('v') as string); 136 const t = parsedUrl.searchParams.get('t') ?? '0'; 137 const seek = encodeURIComponent(t.replace(/s$/, '')); 138 139 return { 140 type: SupportedPlatform.YOUTUBE_VIDEO, 141 url: `https://www.youtube.com/embed/${videoId}?start=${seek}`, 142 }; 143 } 144 145 // spotify 146 if (parsedUrl.hostname === 'open.spotify.com') { 147 const [__, typeOrLocale, idOrType, id] = parsedUrl.pathname.split('/'); 148 149 if (typeOrLocale === 'album' || idOrType === 'album') { 150 return { 151 type: SupportedPlatform.SPOTIFY, 152 url: `https://open.spotify.com/embed/album/${id ?? idOrType}`, 153 }; 154 } 155 if (typeOrLocale === 'track' || idOrType === 'track') { 156 return { 157 type: SupportedPlatform.SPOTIFY, 158 url: `https://open.spotify.com/embed/track/${id ?? idOrType}`, 159 }; 160 } 161 if (typeOrLocale === 'episode' || idOrType === 'episode') { 162 return { 163 type: SupportedPlatform.SPOTIFY, 164 url: `https://open.spotify.com/embed/episode/${id ?? idOrType}`, 165 }; 166 } 167 if (typeOrLocale === 'show' || idOrType === 'show') { 168 return { 169 type: SupportedPlatform.SPOTIFY, 170 url: `https://open.spotify.com/embed/show/${id ?? idOrType}`, 171 }; 172 } 173 } 174 175 // plyr.fm 176 if ( 177 parsedUrl.hostname === 'plyr.fm' || 178 parsedUrl.hostname === 'www.plyr.fm' 179 ) { 180 const [__, type, id] = parsedUrl.pathname.split('/'); 181 182 if (type === 'track' && id) { 183 return { 184 type: SupportedPlatform.PLYRFM_TRACK, 185 url: `https://plyr.fm/embed/track/${id}`, 186 }; 187 } 188 } 189 190 // bandcamp 191 const bandcampRegex = /^[a-z\d][a-z\d-]{2,}[a-z\d]\.bandcamp\.com$/i; 192 if (bandcampRegex.test(parsedUrl.hostname)) { 193 const pathComponents = parsedUrl.pathname.split('/'); 194 switch (pathComponents[1]) { 195 case 'album': 196 return { 197 type: SupportedPlatform.BANDCAMP_ALBUM, 198 url: `https://bandcamp.com/EmbeddedPlayer/url=${encodeURIComponent( 199 parsedUrl.href, 200 )}/size=medium/minimal=true/transparent=true/`, 201 }; 202 case 'track': 203 return { 204 type: SupportedPlatform.BANDCAMP_TRACK, 205 url: `https://bandcamp.com/EmbeddedPlayer/url=${encodeURIComponent( 206 parsedUrl.href, 207 )}/size=medium/minimal=true/transparent=true/`, 208 }; 209 } 210 } 211 212 return { type: SupportedPlatform.DEFAULT, url }; // no supported service detected 213 } catch (e) { 214 // invalid url 215 return { type: SupportedPlatform.DEFAULT, url }; 216 } 217};