const obs_cb = (ml, _) => { ml.forEach((mutation) => { if (mutation.type === "attributes" && mutation.attributeName === "data-testid") { const old = mutation.oldValue.toLocaleLowerCase(); const _new = mutation.target.dataset.testid.toLocaleLowerCase(); if (old == "like" && _new == "unlike") { const tweet = mutation.target.closest('article[data-testid="tweet"]'); if (tweet) { const rect = tweet.getBoundingClientRect(); const ratio = window.devicePixelRatio; const parts = tweet.querySelector("time")?.closest("a")?.href?.split("/"); if (parts && parts.length > 5) { const id = parts[5]; const by = { name: tweet.querySelector("div[data-testid='User-Name'] a")?.innerText, username: parts[3] }; const me_data = document.querySelector("button[aria-label='Account menu']")?.innerText.split("\n"); const me = me_data && me_data.length > 1 ? { name: me_data[0], username: me_data[1] }: undefined; const is_private = tweet.querySelector("div[data-testid='User-Name'] svg[data-testid='icon-lock']") !== null; chrome.runtime.sendMessage({ action: "like", rect, ratio, id, by, me, is_private }); } } } // @TODO: unlike feature! } }); }; const obs = new MutationObserver(obs_cb); const config = { subtree: true, attributeFilter: ["data-testid"], attributeOldValue: true, }; obs.observe(document, config);