background.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  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. console.log("currentHistory", currentHistory);
  140. const q = new URL(message.url);
  141. const v = q.searchParams.get("v");
  142. console.log("v??", message.url, q, q.searchParams.get("v"));
  143. console.log("history?", currentHistory[v]);
  144. if (currentHistory[v]) {
  145. const { [v]: existing, ...rest } = currentHistory;
  146. await browser.storage.local.set({
  147. history: {
  148. [v]: {
  149. ...existing,
  150. duration:
  151. !isNaN(existing.duration) && isFinite(existing.duration)
  152. ? Math.max(message.duration, existing.duration)
  153. : message.duration,
  154. history: [
  155. ...existing.history,
  156. {
  157. action: message.type,
  158. position: message.timestamp,
  159. timestamp: Date.now(),
  160. },
  161. ],
  162. },
  163. ...rest,
  164. },
  165. });
  166. } else {
  167. await browser.storage.local.set({
  168. history: {
  169. [v]: {
  170. url: message.url,
  171. title: message.title,
  172. duration: message.duration,
  173. history: [
  174. {
  175. action: message.type,
  176. position: message.timestamp,
  177. timestamp: Date.now(),
  178. },
  179. ],
  180. },
  181. ...currentHistory,
  182. },
  183. });
  184. }
  185. console.log("proposed:", {
  186. url: message.url,
  187. title: message.title,
  188. duration: message.duration,
  189. history: [
  190. {
  191. action: message.type,
  192. position: message.timestamp,
  193. timestamp: Date.now(),
  194. },
  195. ],
  196. });
  197. }
  198. // Listen for messages from popup or content scripts
  199. browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
  200. console.log("MESSAGE", message);
  201. switch (message.type) {
  202. case "ended":
  203. console.log("ENDED");
  204. case "play":
  205. case "playing":
  206. case "pause":
  207. updateHistory(message);
  208. break;
  209. }
  210. if (message.command === "navigate") {
  211. if (message.tabId && message.url) {
  212. navigateTab(message.tabId, message.url)
  213. .then(() => sendResponse({ status: "success" }))
  214. .catch((error) =>
  215. sendResponse({ status: "error", message: error.message }),
  216. );
  217. return true; // Required for async sendResponse
  218. }
  219. }
  220. });