background.js 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. // Background script for the extension
  2. browser.storage.local.get("playlists").then(function (data) {
  3. if (!data.playlists) {
  4. console.log("pre-populating playlists");
  5. browser.storage.local.set({
  6. playlists: {
  7. "listening-1": [],
  8. "listening-2": [],
  9. "listening-3": [],
  10. "watching-1": [],
  11. "watching-2": [],
  12. },
  13. });
  14. } else {
  15. console.log("no need to pre-populate playlists");
  16. }
  17. });
  18. browser.storage.local.get("history").then(function (data) {
  19. if (!data.history) {
  20. console.log("pre-populating history");
  21. browser.storage.local.set({
  22. history: {},
  23. });
  24. } else {
  25. console.log("no need to pre-populate history");
  26. }
  27. });
  28. // Create context menu items when the extension is installed
  29. browser.runtime.onInstalled.addListener(() => {
  30. // Parent menu item
  31. browser.contextMenus.create({
  32. id: "my-playlist-menu",
  33. title: "Add to Playlist",
  34. contexts: ["link"],
  35. targetUrlPatterns: ["https://*.youtube.com/watch*"],
  36. });
  37. // Sub-menu items
  38. browser.contextMenus.create({
  39. id: "listening-1",
  40. parentId: "my-playlist-menu",
  41. title: "Listening - 1",
  42. contexts: ["link"],
  43. });
  44. browser.contextMenus.create({
  45. id: "listening-2",
  46. parentId: "my-playlist-menu",
  47. title: "Listening - 2",
  48. contexts: ["link"],
  49. });
  50. browser.contextMenus.create({
  51. id: "listening-3",
  52. parentId: "my-playlist-menu",
  53. title: "Listening - 3",
  54. contexts: ["link"],
  55. });
  56. // Separator
  57. browser.contextMenus.create({
  58. id: "separator-1",
  59. parentId: "my-playlist-menu",
  60. type: "separator",
  61. contexts: ["link"],
  62. });
  63. browser.contextMenus.create({
  64. id: "watching-1",
  65. parentId: "my-playlist-menu",
  66. title: "Watching - 1",
  67. contexts: ["link"],
  68. });
  69. browser.contextMenus.create({
  70. id: "watching-2",
  71. parentId: "my-playlist-menu",
  72. title: "Watching - 2",
  73. contexts: ["link"],
  74. });
  75. // test message
  76. //browser.tabs.sendMessage(tab.id, payload);
  77. });
  78. function findMatchingUrlKey(current, inputUrl) {
  79. // Extract the "v" query parameter from input URL
  80. let inputVParam;
  81. try {
  82. const urlObj = new URL(inputUrl);
  83. inputVParam = urlObj.searchParams.get("v");
  84. } catch (e) {
  85. return false;
  86. }
  87. // If no "v" parameter found, return false
  88. if (!inputVParam) {
  89. return false;
  90. }
  91. // Check each key in the current object
  92. for (const key in current) {
  93. const items = current[key];
  94. // Check each item in the array
  95. for (const item of items) {
  96. try {
  97. const itemUrl = new URL(item.url);
  98. const itemVParam = itemUrl.searchParams.get("v");
  99. // If the "v" parameters match, return this key
  100. if (itemVParam === inputVParam) {
  101. return key;
  102. }
  103. } catch (e) {
  104. // Skip malformed URLs
  105. continue;
  106. }
  107. }
  108. }
  109. // No match found
  110. return false;
  111. }
  112. async function addLinkToPlaylist(plName, item) {
  113. const { playlists: currentPlaylists } =
  114. await browser.storage.local.get("playlists");
  115. const alreadyHave = findMatchingUrlKey(currentPlaylists, item.linkUrl);
  116. if (alreadyHave) {
  117. console.log("already have that link in", alreadyHave);
  118. } else {
  119. const { [plName]: playlist, ...others } = currentPlaylists;
  120. await browser.storage.local.set({
  121. playlists: {
  122. [plName]: [...playlist, { url: item.linkUrl, title: item.linkText }],
  123. ...others,
  124. },
  125. });
  126. }
  127. }
  128. // Context menu click handler
  129. browser.contextMenus.onClicked.addListener(async (item, _tab) => {
  130. addLinkToPlaylist(item.menuItemId, item);
  131. });
  132. // Function to navigate a tab to a new URL
  133. function navigateTab(tabId, url) {
  134. return browser.tabs.update(tabId, { url: url });
  135. }
  136. async function updateHistory(message) {
  137. const { history: currentHistory } =
  138. await browser.storage.local.get("history");
  139. const q = new URL(message.url);
  140. const v = q.searchParams.get("v");
  141. if (currentHistory[v]) {
  142. const { [v]: existing, ...rest } = currentHistory;
  143. const shouldIgnore =
  144. message.type === "playing" &&
  145. existing?.history?.length > 0 &&
  146. (existing.history[existing.history.length - 1].action === "play" ||
  147. existing.history[existing.history.length - 1].action === "playing") &&
  148. message.timestamp ===
  149. existing.history[existing.history.length - 1].position;
  150. if (!shouldIgnore) {
  151. await browser.storage.local.set({
  152. history: {
  153. [v]: {
  154. ...existing,
  155. duration:
  156. !isNaN(existing.duration) && isFinite(existing.duration)
  157. ? Math.max(message.duration, existing.duration)
  158. : message.duration,
  159. history: [
  160. ...existing.history,
  161. {
  162. action: message.type,
  163. position: message.timestamp,
  164. timestamp: Date.now(),
  165. },
  166. ],
  167. },
  168. ...rest,
  169. },
  170. });
  171. }
  172. } else {
  173. await browser.storage.local.set({
  174. history: {
  175. [v]: {
  176. url: message.url,
  177. title: message.title,
  178. duration: message.duration,
  179. history: [
  180. {
  181. action: message.type,
  182. position: message.timestamp,
  183. timestamp: Date.now(),
  184. },
  185. ],
  186. },
  187. ...currentHistory,
  188. },
  189. });
  190. }
  191. }
  192. // Listen for messages from popup or content scripts
  193. browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
  194. console.log("MESSAGE", message);
  195. switch (message.type) {
  196. case "play":
  197. case "playing":
  198. case "pause":
  199. updateHistory(message);
  200. break;
  201. case "ended":
  202. updateHistory(message);
  203. console.log("ENDED");
  204. break;
  205. }
  206. if (message.command === "navigate") {
  207. if (message.tabId && message.url) {
  208. navigateTab(message.tabId, message.url)
  209. .then(() => sendResponse({ status: "success" }))
  210. .catch((error) =>
  211. sendResponse({ status: "error", message: error.message }),
  212. );
  213. return true; // Required for async sendResponse
  214. }
  215. }
  216. });