Greasy Fork 支持简体中文。

Crosspost from Twitter

Twitter から 他の SNS に 半自動的に 同時投稿します。

// ==UserScript==
// @name         Crosspost from Twitter
// @namespace    http://tampermonkey.net/
// @version      2025-02-25
// @description  Twitter から 他の SNS に 半自動的に 同時投稿します。
// @author       トラネコマン
// @match        https://x.com/*
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant        none
// @license      MIT
// ==/UserScript==

//各SNSで共有ボタンの表示 (true=表示、false=非表示)
var btn_bluesky = true; //Bluesky
var btn_threads = true; //Threads
var btn_mastodon = true; //Mastodon
var btn_facebook = true; //Facebook
var btn_line = true; //LINE
var btn_fiicen = true; //Fiicen
var btn_substitute = true; //代用表記レンダラー
var btn_clipboard = true; //クリップボードにコピー

//Mastodonのインスタンス
var mastodonInstance = "https://mstdn.jp";

/**
 * content_script/System/TwitterCrawler.ts (Misstterより)
 */
const getTweetText = () => {
    let textContents = document.querySelectorAll(
        'div[data-testid="tweetTextarea_0"] div[data-block="true"]'
    )
    //スマホに対応
    let text
    if (textContents.length > 0) {
        text = Array.from(textContents)
            .map(textContent => {
            return textContent.textContent
        })
            .join("\n")
    }
    else {
        textContents = document.querySelector('textarea[data-testid="tweetTextarea_0"]')
        if (!textContents) return
        text = textContents.value
    }

    return text
}

var isPhone;

function displayMessage(text, error) {
    var div = document.createElement("div");
    div.style.position = "fixed";
    div.style.top = "0";
    div.style.left = "0";
    div.style.width = "100%";
    div.style.backgroundColor = error ? "#f00" : "#008000";
    div.style.color = "#fff";
    div.style.textAlign = "center";
    div.textContent = text;
    document.body.appendChild(div);
    setTimeout(() => { div.remove(); }, 3000);
}

function crosspost(url) {
    var text = getTweetText().trim();
    if (!text) {
        displayMessage("テキストがありません", true);
        return;
    }
    navigator.clipboard.writeText(text);
    window.open(`${url.replace("TEXT", encodeURIComponent(text))}`);
}

function crosspostToFacebook() {
    var text = getTweetText().trim();
    if (!URL.canParse(text)) {
        displayMessage("FacebookはURLのみ共有できます", true);
        return;
    }
    navigator.clipboard.writeText(text);
    window.open(`http://www.facebook.com/share.php?u=${encodeURIComponent(text)}`);
}

function copyToClipboard() {
    var text = getTweetText().trim();
    if (!text) {
        displayMessage("テキストがありません", true);
        return;
    }
    navigator.clipboard.writeText(text);
    displayMessage("テキストをクリップボードにコピーしました!", false);
}

function helpButton() {
    alert("画像について\n" +
          "共有ボタンはテキストのみを共有するので、画像を貼り付けたければテキストを共有してから画像を貼り付けてください");
    alert("リロードされてテキストが消えてしまったら\n" +
          "共有ボタンを押すときにテキストがクリップボードにもコピーされるので、本文に貼り付ければ復元できます");
    if (btn_fiicen) {
        alert("Fiicenに共有するには\n" +
              "テキストがクリップボードにコピーされるので、「サークルを飛ばす」を押して貼り付けてください");
    }
}

setInterval(() => {
    //ツールバーの取得
    var toolbar = document.querySelectorAll('div[data-testid="toolBar"]');
    if (toolbar.length == 0) return;

    for (var i = 0; i < toolbar.length; i++) {
        if (toolbar[i].getAttribute("data-second") == "true") continue;
        toolbar[i].setAttribute("data-second", "true");
        var toolbarlist = toolbar[i].querySelector('div[data-testid="ScrollSnap-List"]');

        //スマホ(絵文字ボタンの有無で判定)
        isPhone = toolbarlist.querySelector('button[aria-haspopup="menu"]') == null;

        //新規ツールバーの作成
        var toolbar2 = toolbarlist.cloneNode(false);
        toolbar2.setAttribute("data-testid", "toolBar2");
        toolbar2.style.marginLeft = "-8px";

        //ボタンの取得
        var btn = toolbarlist.firstElementChild.nextElementSibling;
        var newBtn;

        //Blueskyで共有ボタンの作成
        if (btn_bluesky) {
            newBtn = btn.cloneNode(true);
            newBtn = newBtn.firstElementChild;
            newBtn.style.backgroundColor = "#fff";
            newBtn.style.borderRadius = "0";
            newBtn.setAttribute("aria-label", "Blueskyで共有");
            newBtn.setAttribute("data-testid", "BlueskyButton");
            newBtn.querySelector("svg").style.color = "#0285FF";
            newBtn.querySelector("path").setAttribute("d", "M12 10.8c-1.087-2.114-4.046-6.053-6.798-7.995C2.566.944 1.561 1.266.902 1.565.139 1.908 0 3.08 0 3.768c0 .69.378 5.65.624 6.479.815 2.736 3.713 3.66 6.383 3.364.136-.02.275-.039.415-.056-.138.022-.276.04-.415.056-3.912.58-7.387 2.005-2.83 7.078 5.013 5.19 6.87-1.113 7.823-4.308.953 3.195 2.05 9.271 7.733 4.308 4.267-4.308 1.172-6.498-2.74-7.078a8.741 8.741 0 0 1-.415-.056c.14.017.279.036.415.056 2.67.297 5.568-.628 6.383-3.364.246-.828.624-5.79.624-6.478 0-.69-.139-1.861-.902-2.206-.659-.298-1.664-.62-4.3 1.24C16.046 4.748 13.087 8.687 12 10.8Z");
            newBtn.onclick = () => { crosspost("https://bsky.app/intent/compose?text=TEXT"); }
            toolbar2.appendChild(newBtn.parentNode);
        }

        //Threadsで共有ボタンの作成
        if (btn_threads) {
            newBtn = btn.cloneNode(true);
            newBtn = newBtn.firstElementChild;
            newBtn.style.backgroundColor = "#fff";
            newBtn.style.borderRadius = "0";
            newBtn.setAttribute("aria-label", "Threadsで共有");
            newBtn.setAttribute("data-testid", "ThreadsButton");
            newBtn.querySelector("svg").style.color = "#000000";
            newBtn.querySelector("path").setAttribute("d", "M12.186 24h-.007c-3.581-.024-6.334-1.205-8.184-3.509C2.35 18.44 1.5 15.586 1.472 12.01v-.017c.03-3.579.879-6.43 2.525-8.482C5.845 1.205 8.6.024 12.18 0h.014c2.746.02 5.043.725 6.826 2.098 1.677 1.29 2.858 3.13 3.509 5.467l-2.04.569c-1.104-3.96-3.898-5.984-8.304-6.015-2.91.022-5.11.936-6.54 2.717C4.307 6.504 3.616 8.914 3.589 12c.027 3.086.718 5.496 2.057 7.164 1.43 1.783 3.631 2.698 6.54 2.717 2.623-.02 4.358-.631 5.8-2.045 1.647-1.613 1.618-3.593 1.09-4.798-.31-.71-.873-1.3-1.634-1.75-.192 1.352-.622 2.446-1.284 3.272-.886 1.102-2.14 1.704-3.73 1.79-1.202.065-2.361-.218-3.259-.801-1.063-.689-1.685-1.74-1.752-2.964-.065-1.19.408-2.285 1.33-3.082.88-.76 2.119-1.207 3.583-1.291a13.853 13.853 0 0 1 3.02.142c-.126-.742-.375-1.332-.75-1.757-.513-.586-1.308-.883-2.359-.89h-.029c-.844 0-1.992.232-2.721 1.32L7.734 7.847c.98-1.454 2.568-2.256 4.478-2.256h.044c3.194.02 5.097 1.975 5.287 5.388.108.046.216.094.321.142 1.49.7 2.58 1.761 3.154 3.07.797 1.82.871 4.79-1.548 7.158-1.85 1.81-4.094 2.628-7.277 2.65Zm1.003-11.69c-.242 0-.487.007-.739.021-1.836.103-2.98.946-2.916 2.143.067 1.256 1.452 1.839 2.784 1.767 1.224-.065 2.818-.543 3.086-3.71a10.5 10.5 0 0 0-2.215-.221z");
            newBtn.onclick = () => { crosspost("https://www.threads.net/intent/post?text=TEXT"); }
            toolbar2.appendChild(newBtn.parentNode);
        }

        //Mastodonで共有ボタンの作成
        if (btn_mastodon) {
            newBtn = btn.cloneNode(true);
            newBtn = newBtn.firstElementChild;
            newBtn.style.backgroundColor = "#fff";
            newBtn.style.borderRadius = "0";
            newBtn.setAttribute("aria-label", "Mastodonで共有");
            newBtn.setAttribute("data-testid", "MastodonButton");
            newBtn.querySelector("svg").style.color = "#6364FF";
            newBtn.querySelector("path").setAttribute("d", "M23.268 5.313c-.35-2.578-2.617-4.61-5.304-5.004C17.51.242 15.792 0 11.813 0h-.03c-3.98 0-4.835.242-5.288.309C3.882.692 1.496 2.518.917 5.127.64 6.412.61 7.837.661 9.143c.074 1.874.088 3.745.26 5.611.118 1.24.325 2.47.62 3.68.55 2.237 2.777 4.098 4.96 4.857 2.336.792 4.849.923 7.256.38.265-.061.527-.132.786-.213.585-.184 1.27-.39 1.774-.753a.057.057 0 0 0 .023-.043v-1.809a.052.052 0 0 0-.02-.041.053.053 0 0 0-.046-.01 20.282 20.282 0 0 1-4.709.545c-2.73 0-3.463-1.284-3.674-1.818a5.593 5.593 0 0 1-.319-1.433.053.053 0 0 1 .066-.054c1.517.363 3.072.546 4.632.546.376 0 .75 0 1.125-.01 1.57-.044 3.224-.124 4.768-.422.038-.008.077-.015.11-.024 2.435-.464 4.753-1.92 4.989-5.604.008-.145.03-1.52.03-1.67.002-.512.167-3.63-.024-5.545zm-3.748 9.195h-2.561V8.29c0-1.309-.55-1.976-1.67-1.976-1.23 0-1.846.79-1.846 2.35v3.403h-2.546V8.663c0-1.56-.617-2.35-1.848-2.35-1.112 0-1.668.668-1.67 1.977v6.218H4.822V8.102c0-1.31.337-2.35 1.011-3.12.696-.77 1.608-1.164 2.74-1.164 1.311 0 2.302.5 2.962 1.498l.638 1.06.638-1.06c.66-.999 1.65-1.498 2.96-1.498 1.13 0 2.043.395 2.74 1.164.675.77 1.012 1.81 1.012 3.12z");
            newBtn.onclick = () => { crosspost(`${mastodonInstance}/share?text=TEXT`); }
            toolbar2.appendChild(newBtn.parentNode);
        }

        //Facebookで共有ボタンの作成
        if (btn_facebook) {
            newBtn = btn.cloneNode(true);
            newBtn = newBtn.firstElementChild;
            newBtn.style.backgroundColor = "#fff";
            newBtn.style.borderRadius = "0";
            newBtn.setAttribute("aria-label", "Facebookで共有");
            newBtn.setAttribute("data-testid", "FacebookButton");
            newBtn.querySelector("svg").style.color = "#0866FF";
            newBtn.querySelector("path").setAttribute("d", "M9.101 23.691v-7.98H6.627v-3.667h2.474v-1.58c0-4.085 1.848-5.978 5.858-5.978.401 0 .955.042 1.468.103a8.68 8.68 0 0 1 1.141.195v3.325a8.623 8.623 0 0 0-.653-.036 26.805 26.805 0 0 0-.733-.009c-.707 0-1.259.096-1.675.309a1.686 1.686 0 0 0-.679.622c-.258.42-.374.995-.374 1.752v1.297h3.919l-.386 2.103-.287 1.564h-3.246v8.245C19.396 23.238 24 18.179 24 12.044c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.628 3.874 10.35 9.101 11.647Z");
            newBtn.onclick = crosspostToFacebook;
            toolbar2.appendChild(newBtn.parentNode);
        }

        //LINEで共有ボタンの作成
        if (btn_line) {
            newBtn = btn.cloneNode(true);
            newBtn = newBtn.firstElementChild;
            newBtn.style.backgroundColor = "#fff";
            newBtn.style.borderRadius = "0";
            newBtn.setAttribute("aria-label", "LINEで共有");
            newBtn.setAttribute("data-testid", "LINEButton");
            var linebrd = document.createElement("div");
            linebrd.style.width = "24px";
            linebrd.style.height = "24px";
            linebrd.style.borderRadius = "4px";
            linebrd.style.backgroundColor = "#00C300";
            linebrd.appendChild(newBtn.querySelector("svg"));
            newBtn.firstElementChild.appendChild(linebrd);
            newBtn.querySelector("svg").style.margin = "4px";
            newBtn.querySelector("svg").style.color = "#fff";
            newBtn.querySelector("svg").style.width = "16px";
            newBtn.querySelector("svg").style.height = "16px";
            newBtn.querySelector("path").setAttribute("d", "M19.365 9.863c.349 0 .63.285.63.631 0 .345-.281.63-.63.63H17.61v1.125h1.755c.349 0 .63.283.63.63 0 .344-.281.629-.63.629h-2.386c-.345 0-.627-.285-.627-.629V8.108c0-.345.282-.63.63-.63h2.386c.346 0 .627.285.627.63 0 .349-.281.63-.63.63H17.61v1.125h1.755zm-3.855 3.016c0 .27-.174.51-.432.596-.064.021-.133.031-.199.031-.211 0-.391-.09-.51-.25l-2.443-3.317v2.94c0 .344-.279.629-.631.629-.346 0-.626-.285-.626-.629V8.108c0-.27.173-.51.43-.595.06-.023.136-.033.194-.033.195 0 .375.104.495.254l2.462 3.33V8.108c0-.345.282-.63.63-.63.345 0 .63.285.63.63v4.771zm-5.741 0c0 .344-.282.629-.631.629-.345 0-.627-.285-.627-.629V8.108c0-.345.282-.63.63-.63.346 0 .628.285.628.63v4.771zm-2.466.629H4.917c-.345 0-.63-.285-.63-.629V8.108c0-.345.285-.63.63-.63.348 0 .63.285.63.63v4.141h1.756c.348 0 .629.283.629.63 0 .344-.282.629-.629.629M24 10.314C24 4.943 18.615.572 12 .572S0 4.943 0 10.314c0 4.811 4.27 8.842 10.035 9.608.391.082.923.258 1.058.59.12.301.079.766.038 1.08l-.164 1.02c-.045.301-.24 1.186 1.049.645 1.291-.539 6.916-4.078 9.436-6.975C23.176 14.393 24 12.458 24 10.314");
            if (isPhone) {
                newBtn.onclick = () => { crosspost("https://line.me/R/share?text=TEXT"); }
            }
            else {
                newBtn.onclick = () => { crosspost("https://social-plugins.line.me/lineit/share?text=TEXT"); }
            }
            toolbar2.appendChild(newBtn.parentNode);
        }

        //Fiicenで共有ボタンの作成
        if (btn_fiicen) {
            newBtn = btn.cloneNode(true);
            newBtn = newBtn.firstElementChild;
            newBtn.style.backgroundColor = "#fff";
            newBtn.style.borderRadius = "0";
            newBtn.setAttribute("aria-label", "Fiicenで共有");
            newBtn.setAttribute("data-testid", "FiicenButton");
            newBtn.querySelector("div").style.color = "rgb(79, 70, 229)";
            newBtn.querySelector("svg").remove();
            newBtn.querySelector("span").textContent = "Fi";
            newBtn.onclick = () => { crosspost("https://fiicen.jp"); }
            toolbar2.appendChild(newBtn.parentNode);
        }

        //代用表記レンダラーボタンの作成
        if (btn_substitute) {
            newBtn = btn.cloneNode(true);
            newBtn = newBtn.firstElementChild;
            newBtn.setAttribute("aria-label", "代用表記レンダラー");
            newBtn.setAttribute("data-testid", "SubstituteButton");
            newBtn.querySelector("svg").remove();
            newBtn.querySelector("span").textContent = "代";
            newBtn.onclick = () => { crosspost("https://toracatman.github.io/SubstituteRenderer/?text=TEXT"); }
            toolbar2.appendChild(newBtn.parentNode);
        }

        //クリップボードにコピーボタンの作成
        if (btn_clipboard) {
            newBtn = btn.cloneNode(true);
            newBtn = newBtn.firstElementChild;
            newBtn.setAttribute("aria-label", "クリップボードにコピー");
            newBtn.setAttribute("data-testid", "CopyToClipboardButton");
            newBtn.querySelector("svg").innerHTML = '<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M9 5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2"/><path d="M9 5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v0a2 2 0 0 1-2 2h-2a2 2 0 0 1-2-2"/></g>';
            newBtn.onclick = copyToClipboard;
            toolbar2.appendChild(newBtn.parentNode);
        }

        //ヘルプボタンの作成
        newBtn = btn.cloneNode(true);
        newBtn = newBtn.firstElementChild;
        newBtn.setAttribute("aria-label", "ヘルプ");
        newBtn.setAttribute("data-testid", "HelpButton");
        newBtn.querySelector("svg").innerHTML = '<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M3 12a9 9 0 1 0 18 0a9 9 0 1 0-18 0m9 5v.01"/><path d="M12 13.5a1.5 1.5 0 0 1 1-1.5a2.6 2.6 0 1 0-3-4"/></g>';
        newBtn.onclick = helpButton;
        toolbar2.appendChild(newBtn.parentNode);

        //新規ツールバーの追加
        toolbar[i].after(toolbar2);
    }
}, 100);