playlist-utils.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. // Shared playlist utility functions
  2. // Used by both popup.js and background.js
  3. const PlaylistUtils = {
  4. /**
  5. * Extract video ID from YouTube URL. Handles both standard watch URLs
  6. * (`/watch?v=<id>`) and Shorts URLs (`/shorts/<id>`, which carry the ID
  7. * in the path rather than a `v` query param).
  8. * @param {string} url - YouTube URL
  9. * @returns {string|null} - Video ID or null if not found
  10. */
  11. extractVideoId(url) {
  12. try {
  13. const urlObj = new URL(url);
  14. const vParam = urlObj.searchParams.get("v");
  15. if (vParam) {
  16. return vParam;
  17. }
  18. const shortsMatch = urlObj.pathname.match(/^\/shorts\/([^/?]+)/);
  19. return shortsMatch ? shortsMatch[1] : null;
  20. } catch (e) {
  21. return null;
  22. }
  23. },
  24. /**
  25. * Normalize a YouTube video URL down to just its video ID, stripping
  26. * queue/playlist params like `list`, `index`, and `pp` so that
  27. * navigating to the stored URL later doesn't drop the user back into
  28. * whatever queue or playlist they originally watched it from. Shorts
  29. * URLs keep the `/shorts/<id>` form rather than being rewritten to
  30. * `/watch?v=<id>`.
  31. * @param {string} url - YouTube URL
  32. * @returns {string} - Clean URL, or the original url if no video ID is found
  33. */
  34. cleanVideoUrl(url) {
  35. const videoId = this.extractVideoId(url);
  36. if (!videoId) {
  37. return url;
  38. }
  39. const isShorts = new URL(url).pathname.startsWith("/shorts/");
  40. return isShorts
  41. ? `https://www.youtube.com/shorts/${videoId}`
  42. : `https://www.youtube.com/watch?v=${videoId}`;
  43. },
  44. /**
  45. * Find a video in all playlists by URL
  46. * @param {Object} playlists - All playlists object
  47. * @param {string} url - Video URL to search for
  48. * @returns {Array|false} - [playlistName, videoIndex, isLastVideo] or false if not found
  49. */
  50. findVideoInPlaylists(playlists, url) {
  51. const videoId = this.extractVideoId(url);
  52. if (!videoId) {
  53. return false;
  54. }
  55. // Check each playlist
  56. for (const playlistName in playlists) {
  57. const videos = playlists[playlistName];
  58. // Check each video in the playlist
  59. for (let i = 0; i < videos.length; i++) {
  60. const itemVideoId = this.extractVideoId(videos[i].url);
  61. // If the video IDs match, return playlist info
  62. if (itemVideoId === videoId) {
  63. const isLastVideo = i === videos.length - 1;
  64. return [playlistName, i, isLastVideo];
  65. }
  66. }
  67. }
  68. return false;
  69. },
  70. /**
  71. * Find which playlist contains a video by URL
  72. * @param {Object} playlists - All playlists object
  73. * @param {string} url - Video URL to search for
  74. * @returns {string|false} - Playlist name or false if not found
  75. */
  76. findPlaylist(playlists, url) {
  77. const result = this.findVideoInPlaylists(playlists, url);
  78. return result ? result[0] : false;
  79. },
  80. /**
  81. * Add a video to a playlist if it's not already present in any playlist
  82. * @param {string} playlistName - Name of playlist to add to
  83. * @param {Object} video - Video object with url and title properties
  84. * @returns {Promise<boolean>} - True if added, false if already exists
  85. */
  86. async addVideoToPlaylist(playlistName, video) {
  87. const { playlists: currentPlaylists } =
  88. await browser.storage.local.get("playlists");
  89. const cleanedVideo = { ...video, url: this.cleanVideoUrl(video.url) };
  90. const alreadyExists = this.findPlaylist(
  91. currentPlaylists,
  92. cleanedVideo.url,
  93. );
  94. if (alreadyExists) {
  95. console.log("Video already exists in playlist:", alreadyExists);
  96. return false;
  97. }
  98. // Add video to the specified playlist
  99. const updatedPlaylist = [...currentPlaylists[playlistName], cleanedVideo];
  100. const updatedPlaylists = {
  101. ...currentPlaylists,
  102. [playlistName]: updatedPlaylist,
  103. };
  104. await browser.storage.local.set({ playlists: updatedPlaylists });
  105. console.log("Added video to playlist:", playlistName);
  106. return true;
  107. },
  108. /**
  109. * Legacy function for backward compatibility with context menu
  110. * @param {string} playlistName - Name of playlist to add to
  111. * @param {Object} item - Context menu item with linkUrl and linkText
  112. * @returns {Promise<boolean>} - True if added, false if already exists
  113. */
  114. async addLinkToPlaylist(playlistName, item) {
  115. const video = {
  116. url: item.linkUrl,
  117. title: item.linkText,
  118. };
  119. return await this.addVideoToPlaylist(playlistName, video);
  120. },
  121. };