아카라이브 트위터 첨부 정상화
// ==UserScript==
// @name 아카라이브 트위터첨부 오류수정
// @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");
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);
// assign a unique id to the tweet (random)
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);
});
}