Greasy Fork

fixTweetEmbed

아카라이브 트위터 임베드 정상화. 흰색배경 투명화, 길이조정, 텍스트 클릭시 링크 이동 방지 (번역 및 복사 가능하게끔)

目前为 2024-12-13 提交的版本。查看 最新版本

// ==UserScript==
// @name         fixTweetEmbed
// @namespace    Holobox
// @version      2024-12-13
// @description  아카라이브 트위터 임베드 정상화. 흰색배경 투명화, 길이조정, 텍스트 클릭시 링크 이동 방지 (번역 및 복사 가능하게끔)
// @author       아기비부영양제
// @match        https://arca.live/b/*
// @match        https://safefra.me/twitter/*
// @match        https://platform.twitter.com/*
// @icon         https://x.com/favicon.ico
// @grant        none
// @license MIT 
// ==/UserScript==

window.addEventListener(
  "message",
  function (e) {
    console.log("got message");
    if ("https://safefra.me" === e.origin && e.data.height && e.data.element) {
      var iframe = document.querySelector(
        `iframe.sweet[data-tweetid="${e.data.element}"]` // CSS.escape
      );
      console.log("iframe: ", iframe);
      console.log("data-tweetid: ", e.data.element);
      if (iframe && parseInt(e.data.height) !== 10) {
        iframe.height = parseInt(e.data.height) + 10 + "px";
        iframe.style.height = parseInt(e.data.height) + 10 + "px";
      }
    }
  },
  !1
);

// 내부에서 color-scheme: dark 설정, 텍스트 드래그 가능하게 설정
// @match        https://safefra.me/twitter/*
// @match        https://platform.twitter.com/*
(async function () {
  "use strict";
  const matchUrls = [
    "https://safefra.me/twitter/*",
    "https://platform.twitter.com/*",
  ];
  if (!isMatchURL(matchUrls)) return;

  // Create the meta element
  const meta = document.createElement("meta");
  meta.name = "color-scheme";
  meta.content = "dark";

  // Add it to the head of the document
  document.head.appendChild(meta);

  const app = await waitForElm("#app");
  // let isDragging = false;
  // app.addEventListener("mousedown", function () {
  //   isDragging = false;
  // });
  // app.addEventListener("mousemove", function () {
  //   isDragging = true;
  // });
  // app.addEventListener("mouseup", function (e) {});

  // app.addEventListener("click", function (e) {
  //   const selection = window.getSelection();
  //   const isTextSelected = selection && selection.toString().length > 0;

  //   if (!isDragging && !isTextSelected && e.target.tagName === "A") {
  //     // window.open(e.target.href, "_blank");
  //     // open link
  //     console.log("clicked on a link");
  //     return;
  //   } else {
  //     e.preventDefault();
  //     e.stopPropagation();
  //   }
  // });
  app.addEventListener(
    "click",
    function (e) {
      if (e.target.closest('div[data-testid="tweetText"]')) {
        e.preventDefault();
        e.stopPropagation();
        console.log("Tweet text clicked");
      }
    },
    true
  );
})();

// 외부에서 트위터 임베드 인식 및 postMessage
// @match        https://arca.live/b/*
(async function () {
  "use strict";
  const matchUrls = ["https://arca.live/b/*"];
  if (!isMatchURL(matchUrls)) return;

  // 에디터와 글 보기 화면 둘다 article이 .fr-view 임
  const articleContainer = await waitForElm(".fr-view");

  function processTweet(tweet) {
    if (tweet.dataset.tweetid) {
      return;
    }
    const url = new URL(tweet.src);
    url.searchParams.set("theme", "dark");

    const oldTweet = tweet;
    const parent = oldTweet.parentNode;
    parent.removeChild(oldTweet);
    tweet = oldTweet.cloneNode(true);
    tweet.classList.remove("tweet");
    tweet.classList.add("sweet");
    tweet.setAttribute("src", url.href);
    parent.appendChild(tweet);

    // 하나씩 차곡차곡 id 부여하는 함수를 만드는게 나을지도?
    tweet.dataset.tweetid = `tweet_${Math.random().toString(36).substr(2, 9)}`;
    // add event listener only once.
    tweet.addEventListener("load", function () {
      this.contentWindow.postMessage(
        {
          element: this.dataset.tweetid,
          query: "height",
        },
        "https://safefra.me"
      );
    });
  }

  // 이미 존재하는 트윗 처리
  const tweets = document.querySelectorAll("iframe.tweet");
  tweets.forEach(processTweet);

  // 새로 추가되는 트윗 처리
  const observer = new MutationObserver((mutations) => {
    mutations.forEach((mutation) => {
      mutation.addedNodes.forEach((node) => {
        if (node.nodeType === 1 && node.matches("iframe.tweet")) {
          console.log("new tweet detected");
          processTweet(node);
        }
      });
    });
  });

  observer.observe(articleContainer, {
    childList: true,
    subtree: true,
  });
})();

// 유틸리티 함수
function waitForElm(selector) {
  return new Promise((resolve) => {
    const elm = document.querySelector(selector);
    if (elm) {
      return resolve(elm);
    }
    const observer = new MutationObserver((mutations) => {
      if (document.querySelector(selector)) {
        resolve(document.querySelector(selector));
        observer.disconnect();
      }
    });
    observer.observe(document, {
      //document.body
      childList: true,
      subtree: true,
    });
  });
}

function isMatchURL(urls) {
  const currentUrl = window.location.href;
  return urls.some((url) => {
    const regex = new RegExp(url.replace(/\*/g, ".*"));
    return regex.test(currentUrl);
  });
}