Bluesky app fork with some witchin' additions 馃挮
0
fork

Configure Feed

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

at 82f42e734c50b34de31e8aff1e7ced248ab6e96f 1045 lines 32 kB view raw
1import {RichText} from '@atproto/api' 2 3import {parseEmbedPlayerFromUrl} from '#/lib/strings/embed-player' 4import { 5 createStarterPackGooglePlayUri, 6 createStarterPackLinkFromAndroidReferrer, 7 parseStarterPackUri, 8} from '#/lib/strings/starter-pack' 9import {tenorUrlToBskyGifUrl} from '#/state/queries/tenor' 10import {cleanError} from '../../src/lib/strings/errors' 11import {createFullHandle, makeValidHandle} from '../../src/lib/strings/handles' 12import {enforceLen} from '../../src/lib/strings/helpers' 13import {detectLinkables} from '../../src/lib/strings/rich-text-detection' 14import {shortenLinks} from '../../src/lib/strings/rich-text-manip' 15import { 16 makeRecordUri, 17 toNiceDomain, 18 toShareUrl, 19 toShortUrl, 20} from '../../src/lib/strings/url-helpers' 21 22describe('detectLinkables', () => { 23 const inputs = [ 24 'no linkable', 25 '@start middle end', 26 'start @middle end', 27 'start middle @end', 28 '@start @middle @end', 29 '@full123.test-of-chars', 30 'not@right', 31 '@bad!@#$chars', 32 '@newline1\n@newline2', 33 'parenthetical (@handle)', 34 'start https://middle.com end', 35 'start https://middle.com/foo/bar end', 36 'start https://middle.com/foo/bar?baz=bux end', 37 'start https://middle.com/foo/bar?baz=bux#hash end', 38 'https://start.com/foo/bar?baz=bux#hash middle end', 39 'start middle https://end.com/foo/bar?baz=bux#hash', 40 'https://newline1.com\nhttps://newline2.com', 41 'start middle.com end', 42 'start middle.com/foo/bar end', 43 'start middle.com/foo/bar?baz=bux end', 44 'start middle.com/foo/bar?baz=bux#hash end', 45 'start.com/foo/bar?baz=bux#hash middle end', 46 'start middle end.com/foo/bar?baz=bux#hash', 47 'newline1.com\nnewline2.com', 48 'not.. a..url ..here', 49 'e.g.', 50 'e.g. real.com fake.notreal', 51 'something-cool.jpg', 52 'website.com.jpg', 53 'e.g./foo', 54 'website.com.jpg/foo', 55 'Classic article https://socket3.wordpress.com/2018/02/03/designing-windows-95s-user-interface/', 56 'Classic article https://socket3.wordpress.com/2018/02/03/designing-windows-95s-user-interface/ ', 57 'https://foo.com https://bar.com/whatever https://baz.com', 58 'punctuation https://foo.com, https://bar.com/whatever; https://baz.com.', 59 'parenthetical (https://foo.com)', 60 'except for https://foo.com/thing_(cool)', 61 ] 62 const outputs = [ 63 ['no linkable'], 64 [{link: '@start'}, ' middle end'], 65 ['start ', {link: '@middle'}, ' end'], 66 ['start middle ', {link: '@end'}], 67 [{link: '@start'}, ' ', {link: '@middle'}, ' ', {link: '@end'}], 68 [{link: '@full123.test-of-chars'}], 69 ['not@right'], 70 [{link: '@bad'}, '!@#$chars'], 71 [{link: '@newline1'}, '\n', {link: '@newline2'}], 72 ['parenthetical (', {link: '@handle'}, ')'], 73 ['start ', {link: 'https://middle.com'}, ' end'], 74 ['start ', {link: 'https://middle.com/foo/bar'}, ' end'], 75 ['start ', {link: 'https://middle.com/foo/bar?baz=bux'}, ' end'], 76 ['start ', {link: 'https://middle.com/foo/bar?baz=bux#hash'}, ' end'], 77 [{link: 'https://start.com/foo/bar?baz=bux#hash'}, ' middle end'], 78 ['start middle ', {link: 'https://end.com/foo/bar?baz=bux#hash'}], 79 [{link: 'https://newline1.com'}, '\n', {link: 'https://newline2.com'}], 80 ['start ', {link: 'middle.com'}, ' end'], 81 ['start ', {link: 'middle.com/foo/bar'}, ' end'], 82 ['start ', {link: 'middle.com/foo/bar?baz=bux'}, ' end'], 83 ['start ', {link: 'middle.com/foo/bar?baz=bux#hash'}, ' end'], 84 [{link: 'start.com/foo/bar?baz=bux#hash'}, ' middle end'], 85 ['start middle ', {link: 'end.com/foo/bar?baz=bux#hash'}], 86 [{link: 'newline1.com'}, '\n', {link: 'newline2.com'}], 87 ['not.. a..url ..here'], 88 ['e.g.'], 89 ['e.g. ', {link: 'real.com'}, ' fake.notreal'], 90 ['something-cool.jpg'], 91 ['website.com.jpg'], 92 ['e.g./foo'], 93 ['website.com.jpg/foo'], 94 [ 95 'Classic article ', 96 { 97 link: 'https://socket3.wordpress.com/2018/02/03/designing-windows-95s-user-interface/', 98 }, 99 ], 100 [ 101 'Classic article ', 102 { 103 link: 'https://socket3.wordpress.com/2018/02/03/designing-windows-95s-user-interface/', 104 }, 105 ' ', 106 ], 107 [ 108 {link: 'https://foo.com'}, 109 ' ', 110 {link: 'https://bar.com/whatever'}, 111 ' ', 112 {link: 'https://baz.com'}, 113 ], 114 [ 115 'punctuation ', 116 {link: 'https://foo.com'}, 117 ', ', 118 {link: 'https://bar.com/whatever'}, 119 '; ', 120 {link: 'https://baz.com'}, 121 '.', 122 ], 123 ['parenthetical (', {link: 'https://foo.com'}, ')'], 124 ['except for ', {link: 'https://foo.com/thing_(cool)'}], 125 ] 126 it('correctly handles a set of text inputs', () => { 127 for (let i = 0; i < inputs.length; i++) { 128 const input = inputs[i] 129 const output = detectLinkables(input) 130 expect(output).toEqual(outputs[i]) 131 } 132 }) 133}) 134 135describe('makeRecordUri', () => { 136 const inputs: [string, string, string][] = [ 137 ['alice.test', 'app.bsky.feed.post', '3jk7x4irgv52r'], 138 ] 139 const outputs = ['at://alice.test/app.bsky.feed.post/3jk7x4irgv52r'] 140 141 it('correctly builds a record URI', () => { 142 for (let i = 0; i < inputs.length; i++) { 143 const input = inputs[i] 144 const result = makeRecordUri(...input) 145 expect(result).toEqual(outputs[i]) 146 } 147 }) 148}) 149 150describe('makeValidHandle', () => { 151 const inputs = [ 152 'test-handle-123', 153 'test!"#$%&/()=?_', 154 'this-handle-should-be-too-big', 155 ] 156 const outputs = ['test-handle-123', 'test', 'this-handle-should-b'] 157 158 it('correctly parses and corrects handles', () => { 159 for (let i = 0; i < inputs.length; i++) { 160 const result = makeValidHandle(inputs[i]) 161 expect(result).toEqual(outputs[i]) 162 } 163 }) 164}) 165 166describe('createFullHandle', () => { 167 const inputs: [string, string][] = [ 168 ['test-handle-123', 'test'], 169 ['.test.handle', 'test.test.'], 170 ['test.handle.', '.test.test'], 171 ] 172 const outputs = [ 173 'test-handle-123.test', 174 '.test.handle.test.test.', 175 'test.handle.test.test', 176 ] 177 178 it('correctly parses and corrects handles', () => { 179 for (let i = 0; i < inputs.length; i++) { 180 const input = inputs[i] 181 const result = createFullHandle(...input) 182 expect(result).toEqual(outputs[i]) 183 } 184 }) 185}) 186 187describe('enforceLen', () => { 188 const inputs: [string, number][] = [ 189 ['Hello World!', 5], 190 ['Hello World!', 20], 191 ['', 5], 192 ] 193 const outputs = ['Hello', 'Hello World!', ''] 194 195 it('correctly enforces defined length on a given string', () => { 196 for (let i = 0; i < inputs.length; i++) { 197 const input = inputs[i] 198 const result = enforceLen(...input) 199 expect(result).toEqual(outputs[i]) 200 } 201 }) 202}) 203 204describe('cleanError', () => { 205 const inputs = [ 206 'TypeError: Network request failed', 207 'Error: Aborted', 208 'Error: TypeError "x" is not a function', 209 'Error: SyntaxError unexpected token "export"', 210 'Some other error', 211 ] 212 const outputs = [ 213 'Unable to connect. Please check your internet connection and try again.', 214 'Unable to connect. Please check your internet connection and try again.', 215 'TypeError "x" is not a function', 216 'SyntaxError unexpected token "export"', 217 'Some other error', 218 ] 219 220 it('removes extra content from error message', () => { 221 for (let i = 0; i < inputs.length; i++) { 222 const result = cleanError(inputs[i]) 223 expect(result).toEqual(outputs[i]) 224 } 225 }) 226}) 227 228describe('toNiceDomain', () => { 229 const inputs = [ 230 'https://example.com/index.html', 231 'https://bsky.app', 232 'https://bsky.social', 233 '#123123123', 234 ] 235 const outputs = ['example.com', 'bsky.app', 'Bluesky Social', '#123123123'] 236 237 it("displays the url's host in a easily readable manner", () => { 238 for (let i = 0; i < inputs.length; i++) { 239 const result = toNiceDomain(inputs[i]) 240 expect(result).toEqual(outputs[i]) 241 } 242 }) 243}) 244 245describe('toShortUrl', () => { 246 const inputs = [ 247 'https://bsky.app', 248 'https://bsky.app/3jk7x4irgv52r', 249 'https://bsky.app/3jk7x4irgv52r2313y182h9', 250 'https://very-long-domain-name.com/foo', 251 'https://very-long-domain-name.com/foo?bar=baz#andsomemore', 252 ] 253 const outputs = [ 254 'bsky.app', 255 'bsky.app/3jk7x4irgv52r', 256 'bsky.app/3jk7x4irgv52...', 257 'very-long-domain-name.com/foo', 258 'very-long-domain-name.com/foo?bar=baz#...', 259 ] 260 261 it('shortens the url', () => { 262 for (let i = 0; i < inputs.length; i++) { 263 const result = toShortUrl(inputs[i]) 264 expect(result).toEqual(outputs[i]) 265 } 266 }) 267}) 268 269describe('toShareUrl', () => { 270 const inputs = ['https://bsky.app', '/3jk7x4irgv52r', 'item/test/123'] 271 const outputs = [ 272 'https://bsky.app', 273 'https://bsky.app/3jk7x4irgv52r', 274 'https://bsky.app/item/test/123', 275 ] 276 277 it('appends https, when not present', () => { 278 for (let i = 0; i < inputs.length; i++) { 279 const result = toShareUrl(inputs[i]) 280 expect(result).toEqual(outputs[i]) 281 } 282 }) 283}) 284 285describe('shortenLinks', () => { 286 const inputs = [ 287 'start https://middle.com/foo/bar?baz=bux#hash end', 288 'https://start.com/foo/bar?baz=bux#hash middle end', 289 'start middle https://end.com/foo/bar?baz=bux#hash', 290 'https://newline1.com/very/long/url/here\nhttps://newline2.com/very/long/url/here', 291 'Classic article https://socket3.wordpress.com/2018/02/03/designing-windows-95s-user-interface/', 292 ] 293 const outputs = [ 294 [ 295 'start middle.com/foo/bar?baz=... end', 296 ['https://middle.com/foo/bar?baz=bux#hash'], 297 ], 298 [ 299 'start.com/foo/bar?baz=... middle end', 300 ['https://start.com/foo/bar?baz=bux#hash'], 301 ], 302 [ 303 'start middle end.com/foo/bar?baz=...', 304 ['https://end.com/foo/bar?baz=bux#hash'], 305 ], 306 [ 307 'newline1.com/very/long/ur...\nnewline2.com/very/long/ur...', 308 [ 309 'https://newline1.com/very/long/url/here', 310 'https://newline2.com/very/long/url/here', 311 ], 312 ], 313 [ 314 'Classic article socket3.wordpress.com/2018/02/03/d...', 315 [ 316 'https://socket3.wordpress.com/2018/02/03/designing-windows-95s-user-interface/', 317 ], 318 ], 319 ] 320 321 it('correctly shortens rich text while preserving facet URIs', () => { 322 for (let i = 0; i < inputs.length; i++) { 323 const input = inputs[i] 324 const inputRT = new RichText({text: input}) 325 inputRT.detectFacetsWithoutResolution() 326 const outputRT = shortenLinks(inputRT) 327 expect(outputRT.text).toEqual(outputs[i][0]) 328 expect(outputRT.facets?.length).toEqual(outputs[i][1].length) 329 for (let j = 0; j < outputs[i][1].length; j++) { 330 expect(outputRT.facets![j].features[0].uri).toEqual(outputs[i][1][j]) 331 } 332 } 333 }) 334}) 335 336describe('parseEmbedPlayerFromUrl', () => { 337 const inputs = [ 338 'https://youtu.be/videoId', 339 'https://youtu.be/videoId?t=1s', 340 'https://www.youtube.com/watch?v=videoId', 341 'https://www.youtube.com/watch?v=videoId&feature=share', 342 'https://www.youtube.com/watch?v=videoId&t=1s', 343 'https://youtube.com/watch?v=videoId', 344 'https://youtube.com/watch?v=videoId&feature=share', 345 'https://youtube.com/shorts/videoId', 346 'https://youtube.com/live/videoId', 347 'https://m.youtube.com/watch?v=videoId', 348 'https://music.youtube.com/watch?v=videoId', 349 350 'https://youtube.com/shorts/', 351 'https://youtube.com/', 352 'https://youtube.com/random', 353 'https://youtube.com/live/', 354 355 'https://twitch.tv/channelName', 356 'https://www.twitch.tv/channelName', 357 'https://m.twitch.tv/channelName', 358 359 'https://twitch.tv/channelName/clip/clipId', 360 'https://twitch.tv/videos/videoId', 361 362 'https://open.spotify.com/playlist/playlistId', 363 'https://open.spotify.com/playlist/playlistId?param=value', 364 'https://open.spotify.com/locale/playlist/playlistId', 365 366 'https://open.spotify.com/track/songId', 367 'https://open.spotify.com/track/songId?param=value', 368 'https://open.spotify.com/locale/track/songId', 369 370 'https://open.spotify.com/album/albumId', 371 'https://open.spotify.com/album/albumId?param=value', 372 'https://open.spotify.com/locale/album/albumId', 373 374 'https://soundcloud.com/user/track', 375 'https://soundcloud.com/user/sets/set', 376 'https://soundcloud.com/user/', 377 378 'https://music.apple.com/us/playlist/playlistName/playlistId', 379 'https://music.apple.com/us/album/albumName/albumId', 380 'https://music.apple.com/us/album/albumName/albumId?i=songId', 381 'https://music.apple.com/us/song/songName/songId', 382 383 'https://vimeo.com/videoId', 384 'https://vimeo.com/videoId?autoplay=0', 385 386 'https://giphy.com/gifs/some-random-gif-name-gifId', 387 'https://giphy.com/gif/some-random-gif-name-gifId', 388 'https://giphy.com/gifs/', 389 390 'https://giphy.com/gifs/39248209509382934029?hh=100&ww=100', 391 392 'https://media.giphy.com/media/gifId/giphy.webp', 393 'https://media0.giphy.com/media/gifId/giphy.webp', 394 'https://media1.giphy.com/media/gifId/giphy.gif', 395 'https://media2.giphy.com/media/gifId/giphy.webp', 396 'https://media3.giphy.com/media/gifId/giphy.mp4', 397 'https://media4.giphy.com/media/gifId/giphy.webp', 398 'https://media5.giphy.com/media/gifId/giphy.mp4', 399 'https://media0.giphy.com/media/gifId/giphy.mp3', 400 'https://media1.google.com/media/gifId/giphy.webp', 401 402 'https://media.giphy.com/media/trackingId/gifId/giphy.webp', 403 404 'https://i.giphy.com/media/gifId/giphy.webp', 405 'https://i.giphy.com/media/gifId/giphy.webp', 406 'https://i.giphy.com/gifId.gif', 407 'https://i.giphy.com/gifId.gif', 408 409 'https://tenor.com/view/gifId', 410 'https://tenor.com/notView/gifId', 411 'https://tenor.com/view', 412 'https://tenor.com/view/gifId.gif', 413 'https://tenor.com/intl/view/gifId.gif', 414 415 'https://media.tenor.com/someID_AAAAC/someName.gif?hh=100&ww=100', 416 'https://media.tenor.com/someID_AAAAC/someName.gif', 417 'https://media.tenor.com/someID/someName.gif', 418 'https://media.tenor.com/someID', 419 'https://media.tenor.com', 420 421 'https://www.flickr.com/photos/username/albums/72177720308493661', 422 'https://flickr.com/photos/username/albums/72177720308493661', 423 'https://flickr.com/photos/username/albums/72177720308493661/', 424 'https://flickr.com/photos/username/albums/72177720308493661//', 425 'https://flic.kr/s/aHBqjAES3i', 426 427 'https://flickr.com/foetoes/username/albums/3903', 428 'https://flickr.com/albums/3903', 429 'https://flic.kr/s/OolI', 430 'https://flic.kr/t/aHBqjAES3i', 431 432 'https://www.flickr.com/groups/898944@N23/pool', 433 'https://flickr.com/groups/898944@N23/pool', 434 'https://flickr.com/groups/898944@N23/pool/', 435 'https://flickr.com/groups/898944@N23/pool//', 436 'https://flic.kr/go/8WJtR', 437 438 'https://www.flickr.com/groups/898944@N23/', 439 'https://www.flickr.com/groups', 440 441 'https://maxblansjaar.bandcamp.com/album/false-comforts', 442 'https://grmnygrmny.bandcamp.com/track/fluid', 443 'https://sufjanstevens.bandcamp.com/', 444 'https://sufjanstevens.bandcamp.com', 445 'https://bandcamp.com/', 446 'https://bandcamp.com', 447 ] 448 449 const outputs = [ 450 { 451 type: 'youtube_video', 452 source: 'youtube', 453 playerUri: 'https://bsky.app/iframe/youtube.html?videoId=videoId&start=0', 454 }, 455 { 456 type: 'youtube_video', 457 source: 'youtube', 458 playerUri: 'https://bsky.app/iframe/youtube.html?videoId=videoId&start=1', 459 }, 460 { 461 type: 'youtube_video', 462 source: 'youtube', 463 playerUri: 'https://bsky.app/iframe/youtube.html?videoId=videoId&start=0', 464 }, 465 { 466 type: 'youtube_video', 467 source: 'youtube', 468 playerUri: 'https://bsky.app/iframe/youtube.html?videoId=videoId&start=0', 469 }, 470 { 471 type: 'youtube_video', 472 source: 'youtube', 473 playerUri: 'https://bsky.app/iframe/youtube.html?videoId=videoId&start=1', 474 }, 475 { 476 type: 'youtube_video', 477 source: 'youtube', 478 playerUri: 'https://bsky.app/iframe/youtube.html?videoId=videoId&start=0', 479 }, 480 { 481 type: 'youtube_video', 482 source: 'youtube', 483 playerUri: 'https://bsky.app/iframe/youtube.html?videoId=videoId&start=0', 484 }, 485 { 486 type: 'youtube_short', 487 source: 'youtubeShorts', 488 hideDetails: true, 489 playerUri: 'https://bsky.app/iframe/youtube.html?videoId=videoId&start=0', 490 }, 491 { 492 type: 'youtube_video', 493 source: 'youtube', 494 playerUri: 'https://bsky.app/iframe/youtube.html?videoId=videoId&start=0', 495 }, 496 { 497 type: 'youtube_video', 498 source: 'youtube', 499 playerUri: 'https://bsky.app/iframe/youtube.html?videoId=videoId&start=0', 500 }, 501 { 502 type: 'youtube_video', 503 source: 'youtube', 504 playerUri: 'https://bsky.app/iframe/youtube.html?videoId=videoId&start=0', 505 }, 506 507 undefined, 508 undefined, 509 undefined, 510 undefined, 511 512 { 513 type: 'twitch_video', 514 source: 'twitch', 515 playerUri: `https://player.twitch.tv/?volume=0.5&!muted&autoplay&channel=channelName&parent=localhost`, 516 }, 517 { 518 type: 'twitch_video', 519 source: 'twitch', 520 playerUri: `https://player.twitch.tv/?volume=0.5&!muted&autoplay&channel=channelName&parent=localhost`, 521 }, 522 { 523 type: 'twitch_video', 524 source: 'twitch', 525 playerUri: `https://player.twitch.tv/?volume=0.5&!muted&autoplay&channel=channelName&parent=localhost`, 526 }, 527 { 528 type: 'twitch_video', 529 source: 'twitch', 530 playerUri: `https://clips.twitch.tv/embed?volume=0.5&autoplay=true&clip=clipId&parent=localhost`, 531 }, 532 { 533 type: 'twitch_video', 534 source: 'twitch', 535 playerUri: `https://player.twitch.tv/?volume=0.5&!muted&autoplay&video=videoId&parent=localhost`, 536 }, 537 538 { 539 type: 'spotify_playlist', 540 source: 'spotify', 541 playerUri: `https://open.spotify.com/embed/playlist/playlistId`, 542 }, 543 { 544 type: 'spotify_playlist', 545 source: 'spotify', 546 playerUri: `https://open.spotify.com/embed/playlist/playlistId`, 547 }, 548 { 549 type: 'spotify_playlist', 550 source: 'spotify', 551 playerUri: `https://open.spotify.com/embed/playlist/playlistId`, 552 }, 553 554 { 555 type: 'spotify_song', 556 source: 'spotify', 557 playerUri: `https://open.spotify.com/embed/track/songId`, 558 }, 559 { 560 type: 'spotify_song', 561 source: 'spotify', 562 playerUri: `https://open.spotify.com/embed/track/songId`, 563 }, 564 { 565 type: 'spotify_song', 566 source: 'spotify', 567 playerUri: `https://open.spotify.com/embed/track/songId`, 568 }, 569 570 { 571 type: 'spotify_album', 572 source: 'spotify', 573 playerUri: `https://open.spotify.com/embed/album/albumId`, 574 }, 575 { 576 type: 'spotify_album', 577 source: 'spotify', 578 playerUri: `https://open.spotify.com/embed/album/albumId`, 579 }, 580 { 581 type: 'spotify_album', 582 source: 'spotify', 583 playerUri: `https://open.spotify.com/embed/album/albumId`, 584 }, 585 586 { 587 type: 'soundcloud_track', 588 source: 'soundcloud', 589 playerUri: `https://w.soundcloud.com/player/?url=https://soundcloud.com/user/track&auto_play=true&visual=false&hide_related=true`, 590 }, 591 { 592 type: 'soundcloud_set', 593 source: 'soundcloud', 594 playerUri: `https://w.soundcloud.com/player/?url=https://soundcloud.com/user/sets/set&auto_play=true&visual=false&hide_related=true`, 595 }, 596 undefined, 597 598 { 599 type: 'apple_music_playlist', 600 source: 'appleMusic', 601 playerUri: 602 'https://embed.music.apple.com/us/playlist/playlistName/playlistId', 603 }, 604 { 605 type: 'apple_music_album', 606 source: 'appleMusic', 607 playerUri: 'https://embed.music.apple.com/us/album/albumName/albumId', 608 }, 609 { 610 type: 'apple_music_song', 611 source: 'appleMusic', 612 playerUri: 613 'https://embed.music.apple.com/us/album/albumName/albumId?i=songId', 614 }, 615 { 616 type: 'apple_music_song', 617 source: 'appleMusic', 618 playerUri: 'https://embed.music.apple.com/us/song/songName/songId', 619 }, 620 621 { 622 type: 'vimeo_video', 623 source: 'vimeo', 624 playerUri: 'https://player.vimeo.com/video/videoId?autoplay=1', 625 }, 626 { 627 type: 'vimeo_video', 628 source: 'vimeo', 629 playerUri: 'https://player.vimeo.com/video/videoId?autoplay=1', 630 }, 631 632 { 633 type: 'giphy_gif', 634 source: 'giphy', 635 isGif: true, 636 hideDetails: true, 637 metaUri: 'https://giphy.com/gifs/gifId', 638 playerUri: 'https://i.giphy.com/media/gifId/200.webp', 639 }, 640 undefined, 641 undefined, 642 { 643 type: 'giphy_gif', 644 source: 'giphy', 645 isGif: true, 646 hideDetails: true, 647 metaUri: 'https://giphy.com/gifs/39248209509382934029', 648 playerUri: 'https://i.giphy.com/media/39248209509382934029/200.webp', 649 }, 650 { 651 type: 'giphy_gif', 652 source: 'giphy', 653 isGif: true, 654 hideDetails: true, 655 metaUri: 'https://giphy.com/gifs/gifId', 656 playerUri: 'https://i.giphy.com/media/gifId/200.webp', 657 }, 658 { 659 type: 'giphy_gif', 660 source: 'giphy', 661 isGif: true, 662 hideDetails: true, 663 metaUri: 'https://giphy.com/gifs/gifId', 664 playerUri: 'https://i.giphy.com/media/gifId/200.webp', 665 }, 666 { 667 type: 'giphy_gif', 668 source: 'giphy', 669 isGif: true, 670 hideDetails: true, 671 metaUri: 'https://giphy.com/gifs/gifId', 672 playerUri: 'https://i.giphy.com/media/gifId/200.webp', 673 }, 674 { 675 type: 'giphy_gif', 676 source: 'giphy', 677 isGif: true, 678 hideDetails: true, 679 metaUri: 'https://giphy.com/gifs/gifId', 680 playerUri: 'https://i.giphy.com/media/gifId/200.webp', 681 }, 682 { 683 type: 'giphy_gif', 684 source: 'giphy', 685 isGif: true, 686 hideDetails: true, 687 metaUri: 'https://giphy.com/gifs/gifId', 688 playerUri: 'https://i.giphy.com/media/gifId/200.webp', 689 }, 690 { 691 type: 'giphy_gif', 692 source: 'giphy', 693 isGif: true, 694 hideDetails: true, 695 metaUri: 'https://giphy.com/gifs/gifId', 696 playerUri: 'https://i.giphy.com/media/gifId/200.webp', 697 }, 698 undefined, 699 undefined, 700 undefined, 701 702 { 703 type: 'giphy_gif', 704 source: 'giphy', 705 isGif: true, 706 hideDetails: true, 707 metaUri: 'https://giphy.com/gifs/gifId', 708 playerUri: 'https://i.giphy.com/media/gifId/200.webp', 709 }, 710 711 { 712 type: 'giphy_gif', 713 source: 'giphy', 714 isGif: true, 715 hideDetails: true, 716 metaUri: 'https://giphy.com/gifs/gifId', 717 playerUri: 'https://i.giphy.com/media/gifId/200.webp', 718 }, 719 { 720 type: 'giphy_gif', 721 source: 'giphy', 722 isGif: true, 723 hideDetails: true, 724 metaUri: 'https://giphy.com/gifs/gifId', 725 playerUri: 'https://i.giphy.com/media/gifId/200.webp', 726 }, 727 { 728 type: 'giphy_gif', 729 source: 'giphy', 730 isGif: true, 731 hideDetails: true, 732 metaUri: 'https://giphy.com/gifs/gifId', 733 playerUri: 'https://i.giphy.com/media/gifId/200.webp', 734 }, 735 { 736 type: 'giphy_gif', 737 source: 'giphy', 738 isGif: true, 739 hideDetails: true, 740 metaUri: 'https://giphy.com/gifs/gifId', 741 playerUri: 'https://i.giphy.com/media/gifId/200.webp', 742 }, 743 744 undefined, 745 undefined, 746 undefined, 747 undefined, 748 undefined, 749 750 { 751 type: 'tenor_gif', 752 source: 'tenor', 753 isGif: true, 754 hideDetails: true, 755 playerUri: 'https://t.gifs.bsky.app/someID_AAAAM/someName.gif', 756 dimensions: { 757 width: 100, 758 height: 100, 759 }, 760 }, 761 undefined, 762 undefined, 763 undefined, 764 undefined, 765 766 { 767 type: 'flickr_album', 768 source: 'flickr', 769 playerUri: 'https://embedr.flickr.com/photosets/72177720308493661', 770 }, 771 { 772 type: 'flickr_album', 773 source: 'flickr', 774 playerUri: 'https://embedr.flickr.com/photosets/72177720308493661', 775 }, 776 { 777 type: 'flickr_album', 778 source: 'flickr', 779 playerUri: 'https://embedr.flickr.com/photosets/72177720308493661', 780 }, 781 { 782 type: 'flickr_album', 783 source: 'flickr', 784 playerUri: 'https://embedr.flickr.com/photosets/72177720308493661', 785 }, 786 { 787 type: 'flickr_album', 788 source: 'flickr', 789 playerUri: 'https://embedr.flickr.com/photosets/72177720308493661', 790 }, 791 792 undefined, 793 undefined, 794 undefined, 795 undefined, 796 797 { 798 type: 'flickr_album', 799 source: 'flickr', 800 playerUri: 'https://embedr.flickr.com/groups/898944@N23', 801 }, 802 { 803 type: 'flickr_album', 804 source: 'flickr', 805 playerUri: 'https://embedr.flickr.com/groups/898944@N23', 806 }, 807 { 808 type: 'flickr_album', 809 source: 'flickr', 810 playerUri: 'https://embedr.flickr.com/groups/898944@N23', 811 }, 812 { 813 type: 'flickr_album', 814 source: 'flickr', 815 playerUri: 'https://embedr.flickr.com/groups/898944@N23', 816 }, 817 { 818 type: 'flickr_album', 819 source: 'flickr', 820 playerUri: 'https://embedr.flickr.com/groups/898944@N23', 821 }, 822 823 undefined, 824 undefined, 825 826 { 827 type: 'bandcamp_album', 828 source: 'bandcamp', 829 playerUri: 830 'https://bandcamp.com/EmbeddedPlayer/url=https%3A%2F%2Fmaxblansjaar.bandcamp.com%2Falbum%2Ffalse-comforts/size=large/bgcol=ffffff/linkcol=0687f5/minimal=true/transparent=true/', 831 }, 832 { 833 type: 'bandcamp_track', 834 source: 'bandcamp', 835 playerUri: 836 'https://bandcamp.com/EmbeddedPlayer/url=https%3A%2F%2Fgrmnygrmny.bandcamp.com%2Ftrack%2Ffluid/size=large/bgcol=ffffff/linkcol=0687f5/minimal=true/transparent=true/', 837 }, 838 undefined, 839 undefined, 840 undefined, 841 undefined, 842 ] 843 844 it('correctly grabs the correct id from uri', () => { 845 for (let i = 0; i < inputs.length; i++) { 846 const input = inputs[i] 847 const output = outputs[i] 848 849 const res = parseEmbedPlayerFromUrl(input) 850 851 expect(res).toEqual(output) 852 } 853 }) 854}) 855 856describe('createStarterPackLinkFromAndroidReferrer', () => { 857 const validOutput = 'at://haileyok.com/app.bsky.graph.starterpack/rkey' 858 859 it('returns a link when input contains utm_source and utm_content', () => { 860 expect( 861 createStarterPackLinkFromAndroidReferrer( 862 'utm_source=bluesky&utm_content=starterpack_haileyok.com_rkey', 863 ), 864 ).toEqual(validOutput) 865 866 expect( 867 createStarterPackLinkFromAndroidReferrer( 868 'utm_source=bluesky&utm_content=starterpack_test-lover-9000.com_rkey', 869 ), 870 ).toEqual('at://test-lover-9000.com/app.bsky.graph.starterpack/rkey') 871 }) 872 873 it('returns a link when input contains utm_source and utm_content in different order', () => { 874 expect( 875 createStarterPackLinkFromAndroidReferrer( 876 'utm_content=starterpack_haileyok.com_rkey&utm_source=bluesky', 877 ), 878 ).toEqual(validOutput) 879 }) 880 881 it('returns a link when input contains other parameters as well', () => { 882 expect( 883 createStarterPackLinkFromAndroidReferrer( 884 'utm_source=bluesky&utm_medium=starterpack&utm_content=starterpack_haileyok.com_rkey', 885 ), 886 ).toEqual(validOutput) 887 }) 888 889 it('returns null when utm_source is not present', () => { 890 expect( 891 createStarterPackLinkFromAndroidReferrer( 892 'utm_content=starterpack_haileyok.com_rkey', 893 ), 894 ).toEqual(null) 895 }) 896 897 it('returns null when utm_content is not present', () => { 898 expect( 899 createStarterPackLinkFromAndroidReferrer('utm_source=bluesky'), 900 ).toEqual(null) 901 }) 902 903 it('returns null when utm_content is malformed', () => { 904 expect( 905 createStarterPackLinkFromAndroidReferrer( 906 'utm_content=starterpack_haileyok.com', 907 ), 908 ).toEqual(null) 909 910 expect( 911 createStarterPackLinkFromAndroidReferrer('utm_content=starterpack'), 912 ).toEqual(null) 913 914 expect( 915 createStarterPackLinkFromAndroidReferrer( 916 'utm_content=starterpack_haileyok.com_rkey_more', 917 ), 918 ).toEqual(null) 919 920 expect( 921 createStarterPackLinkFromAndroidReferrer( 922 'utm_content=notastarterpack_haileyok.com_rkey', 923 ), 924 ).toEqual(null) 925 }) 926}) 927 928describe('parseStarterPackHttpUri', () => { 929 const baseUri = 'https://bsky.app/start' 930 931 it('returns a valid at uri when http uri is valid', () => { 932 const validHttpUri = `${baseUri}/haileyok.com/rkey` 933 expect(parseStarterPackUri(validHttpUri)).toEqual({ 934 name: 'haileyok.com', 935 rkey: 'rkey', 936 }) 937 938 const validHttpUri2 = `${baseUri}/haileyok.com/ilovetesting` 939 expect(parseStarterPackUri(validHttpUri2)).toEqual({ 940 name: 'haileyok.com', 941 rkey: 'ilovetesting', 942 }) 943 944 const validHttpUri3 = `${baseUri}/testlover9000.com/rkey` 945 expect(parseStarterPackUri(validHttpUri3)).toEqual({ 946 name: 'testlover9000.com', 947 rkey: 'rkey', 948 }) 949 }) 950 951 it('returns null when there is no rkey', () => { 952 const validHttpUri = `${baseUri}/haileyok.com` 953 expect(parseStarterPackUri(validHttpUri)).toEqual(null) 954 }) 955 956 it('returns null when there is an extra path', () => { 957 const validHttpUri = `${baseUri}/haileyok.com/rkey/other` 958 expect(parseStarterPackUri(validHttpUri)).toEqual(null) 959 }) 960 961 it('returns null when there is no handle or rkey', () => { 962 const validHttpUri = `${baseUri}` 963 expect(parseStarterPackUri(validHttpUri)).toEqual(null) 964 }) 965 966 it('returns null when the route is not /start or /starter-pack', () => { 967 const validHttpUri = 'https://bsky.app/start/haileyok.com/rkey' 968 expect(parseStarterPackUri(validHttpUri)).toEqual({ 969 name: 'haileyok.com', 970 rkey: 'rkey', 971 }) 972 973 const validHttpUri2 = 'https://bsky.app/starter-pack/haileyok.com/rkey' 974 expect(parseStarterPackUri(validHttpUri2)).toEqual({ 975 name: 'haileyok.com', 976 rkey: 'rkey', 977 }) 978 979 const invalidHttpUri = 'https://bsky.app/profile/haileyok.com/rkey' 980 expect(parseStarterPackUri(invalidHttpUri)).toEqual(null) 981 }) 982 983 it('returns the at uri when the input is a valid starterpack at uri', () => { 984 const validAtUri = 'at://did:plc:123/app.bsky.graph.starterpack/rkey' 985 expect(parseStarterPackUri(validAtUri)).toEqual({ 986 name: 'did:plc:123', 987 rkey: 'rkey', 988 }) 989 }) 990 991 it('returns null when the at uri has no rkey', () => { 992 const validAtUri = 'at://did:plc:123/app.bsky.graph.starterpack' 993 expect(parseStarterPackUri(validAtUri)).toEqual(null) 994 }) 995 996 it('returns null when the collection is not app.bsky.graph.starterpack', () => { 997 const validAtUri = 'at://did:plc:123/app.bsky.graph.list/rkey' 998 expect(parseStarterPackUri(validAtUri)).toEqual(null) 999 }) 1000 1001 it('returns null when the input is undefined', () => { 1002 expect(parseStarterPackUri(undefined)).toEqual(null) 1003 }) 1004}) 1005 1006describe('createStarterPackGooglePlayUri', () => { 1007 const base = 1008 'https://play.google.com/store/apps/details?id=xyz.blueskyweb.app&referrer=utm_source%3Dbluesky%26utm_medium%3Dstarterpack%26utm_content%3Dstarterpack_' 1009 1010 it('returns valid google play uri when input is valid', () => { 1011 expect(createStarterPackGooglePlayUri('name', 'rkey')).toEqual( 1012 `${base}name_rkey`, 1013 ) 1014 }) 1015 1016 it('returns null when no rkey is supplied', () => { 1017 // @ts-expect-error test 1018 expect(createStarterPackGooglePlayUri('name', undefined)).toEqual(null) 1019 }) 1020 1021 it('returns null when no name or rkey are supplied', () => { 1022 // @ts-expect-error test 1023 expect(createStarterPackGooglePlayUri(undefined, undefined)).toEqual(null) 1024 }) 1025 1026 it('returns null when rkey is supplied but no name', () => { 1027 // @ts-expect-error test 1028 expect(createStarterPackGooglePlayUri(undefined, 'rkey')).toEqual(null) 1029 }) 1030}) 1031 1032describe('tenorUrlToBskyGifUrl', () => { 1033 const inputs = [ 1034 'https://media.tenor.com/someID_AAAAC/someName.gif', 1035 'https://media.tenor.com/someID/someName.gif', 1036 ] 1037 1038 it.each(inputs)( 1039 'returns url with t.gifs.bsky.app as hostname for input url', 1040 input => { 1041 const out = tenorUrlToBskyGifUrl(input) 1042 expect(out.startsWith('https://t.gifs.bsky.app/')).toEqual(true) 1043 }, 1044 ) 1045})