diff --git a/src/app/screens/Home/index.tsx b/src/app/screens/Home/index.tsx index 4aa5399b55..00088c08f8 100644 --- a/src/app/screens/Home/index.tsx +++ b/src/app/screens/Home/index.tsx @@ -65,7 +65,7 @@ const Home: FC = () => { if (currentUrl.startsWith("http")) { browser.tabs.sendMessage(tabs[0].id as number, { - action: "extractLightningData", + action: "getCurrentLightningData", }); } } else { diff --git a/src/extension/content-script/batteries/YouTubeVideo.ts b/src/extension/content-script/batteries/YouTubeVideo.ts index cc5447204b..12af4a1508 100644 --- a/src/extension/content-script/batteries/YouTubeVideo.ts +++ b/src/extension/content-script/batteries/YouTubeVideo.ts @@ -1,36 +1,65 @@ import getOriginData from "../originData"; import { findLnurlFromYouTubeAboutPage } from "./YouTubeChannel"; -import { findLightningAddressInText, setLightningData } from "./helpers"; +import { + findLightningAddressInText, + resetLightningData, + setLightningData, +} from "./helpers"; + +declare global { + interface Window { + ALBY_BATTERY: boolean; + } +} const urlMatcher = /^https:\/\/www\.youtube.com\/watch.*/; -const battery = async (): Promise => { +let oldVideoId: string; +let observer: MutationObserver | null = null; + +const setData = async (): Promise => { + const searchParams = new URLSearchParams(window.location.search); + const videoId = searchParams.get("v"); + + // to keep the battery info stable after the description settles + // youtube returns old lightning data even when switching on new video for few initial seconds. so we just return from here instead of showing old lightning data + if (videoId === oldVideoId) { + return; + } + + oldVideoId = videoId as string; + let text = ""; document .querySelectorAll( - "#columns #primary #primary-inner #meta-contents #description .content" + "ytd-watch-metadata #above-the-fold #bottom-row #description #description-inner #description-inline-expander yt-attributed-string .yt-core-attributed-string" ) .forEach((e) => { - text += ` ${e.textContent}`; + text += `${e.textContent} `; }); + const channelLink = document.querySelector( - "#columns #primary #primary-inner #meta-contents .ytd-channel-name a" + "ytd-watch-metadata #above-the-fold #top-row #owner #upload-info .ytd-channel-name yt-formatted-string a" ); - if (!text || !channelLink) { + + if (!channelLink) { + resetLightningData(); return; } + let match; let lnurl; - // check for an lnurl + + // Check for an lnurl if ((match = text.match(/(lnurlp:)(\S+)/i))) { lnurl = match[2]; } - // if there is no lnurl we check for a zap emoji with a lightning address - // we check for the @-sign to try to limit the possibility to match some invalid text (e.g. random emoji usage) + // If no lnurl, check for a zap emoji with a lightning address else if ((match = findLightningAddressInText(text))) { lnurl = match; - } else { - // load the about page to check for a lightning address + } + // Load the about page to check for a lightning address + else { const match = channelLink.href.match( /^https:\/\/www\.youtube.com\/(((channel|c)\/([^/]+))|(@[^/]+)).*/ ); @@ -39,7 +68,10 @@ const battery = async (): Promise => { } } - if (!lnurl) return; + if (!lnurl) { + resetLightningData(); + return; + } const name = channelLink.textContent || ""; const imageUrl = @@ -50,18 +82,62 @@ const battery = async (): Promise => { "#columns #primary #primary-inner #owner #avatar img" // support maybe new UI being rolled out 2022/09 )?.src || ""; + setLightningData([ { method: "lnurl", address: lnurl, ...getOriginData(), name, - description: "", // we can not reliably find a description (the meta tag might be of a different video) + description: "", // We cannot reliably find a description (the meta tag might be of a different video) icon: imageUrl, }, ]); }; +const battery = (): void => { + function waitForChannelLinkAndDescription() { + const description = document.querySelector( + "ytd-watch-metadata #above-the-fold #bottom-row #description #description-inner #description-inline-expander yt-attributed-string .yt-core-attributed-string" + ); + + const channelLink = document.querySelector( + "ytd-watch-metadata #above-the-fold #top-row #owner #upload-info .ytd-channel-name yt-formatted-string a" + ); + + // Ensure all elements are present before proceeding + if (description && channelLink) { + clearInterval(descriptionInterval); // Stop checking for the elements + + // run this only once, during the first load + if (!window.ALBY_BATTERY) { + window.ALBY_BATTERY = true; + + // we need to run setData if this is the first time the user is + // visiting youtube as the observer doesn't run intially on attach + setData(); + + // Re-initialize the observer + observer = new MutationObserver(() => { + setData(); + }); + + // Observe changes in the description (without subtree it didn't work properly) + observer.observe(description, { childList: true, subtree: true }); + // Observe changes in the channelLink + observer.observe(channelLink, { childList: true, subtree: true }); + } + } + } + + // Wait for the channel link and description to load before initializing the observer and fetching the data + const descriptionInterval = setInterval( + waitForChannelLinkAndDescription, + 100 + ); + setTimeout(() => clearInterval(descriptionInterval), 2000); +}; + const YouTubeVideo = { urlMatcher, battery, diff --git a/src/extension/content-script/batteries/helpers.ts b/src/extension/content-script/batteries/helpers.ts index f619457add..c54d3677c9 100644 --- a/src/extension/content-script/batteries/helpers.ts +++ b/src/extension/content-script/batteries/helpers.ts @@ -48,11 +48,24 @@ export const findLightningAddressInText = (text: string): string | null => { return null; }; +let lightningData: [Battery] | null; + +export const resetLightningData = (): void => { + lightningData = null; + msg.request("setIcon", { icon: ExtensionIcon.Default }); +}; + +export const sendLightningData = () => { + if (lightningData) { + browser.runtime.sendMessage({ + application: "LBE", + action: "lightningData", + args: lightningData, + }); + } +}; + export const setLightningData = (data: [Battery]): void => { - browser.runtime.sendMessage({ - application: "LBE", - action: "lightningData", - args: data, - }); + lightningData = data; msg.request("setIcon", { icon: ExtensionIcon.Tipping }); }; diff --git a/src/extension/content-script/webln.js b/src/extension/content-script/webln.js index 8f38c31b20..cd1ac25949 100644 --- a/src/extension/content-script/webln.js +++ b/src/extension/content-script/webln.js @@ -1,6 +1,7 @@ import browser from "webextension-polyfill"; import extractLightningData from "./batteries"; +import { sendLightningData } from "./batteries/helpers"; import getOriginData from "./originData"; import shouldInject from "./shouldInject"; @@ -46,6 +47,10 @@ async function init() { window.location.origin ); } + // homepage requests this to get current lightning data from battery + if (request.action === "getCurrentLightningData") { + sendLightningData(); + } }); // message listener to listen to inpage webln/webbtc calls