// ==UserScript==
// @name Patreon IFrame Embed Into Clipboard
// @license MIT
// @namespace rtonne
// @match https://www.patreon.com/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=patreon.com
// @version 1.4
// @author Rtonne
// @description Adds a button to turn Patreon IFrame embedded posts into custom text on your clipboard
// @run-at document-end
// @grant GM.setClipboard
// @downloadURL none
// ==/UserScript==
function getClipboardContent(post_title_text, post_url, post_iframe_url) {
// The content of this function is for my use case, and was put into this
// function so you could change it for your use case easier, so go ahead!
const post_iframe_search_url = post_iframe_url.substring(
post_iframe_url.indexOf("?") + 1,
);
const post_iframe_search_params = new URLSearchParams(post_iframe_search_url);
const post_streamable_url = post_iframe_search_params.get("src");
return `
### ${post_title_text} [](${post_url})
- [ ] :LiEye:
`;
}
const clipboard_svg = /*svg*/ `
`;
const url_regex = /^https:\/\/www.patreon.com\/[^\/]+?\/posts.*$/;
const observer = new MutationObserver(async () => {
if (!url_regex.test(window.location.href)) {
return;
}
const elements = await waitForElements(
document,
"li > div[data-tag='post-card']",
);
for (const element of elements) {
if (element.querySelector(".rtonne-patreon-streamable-button")) {
continue;
}
const post_video_figure = element.querySelector(
"figure[title='video thumbnail']",
);
if (!post_video_figure) {
continue;
}
const more_actions_button_container = element.querySelector(
"div:has(> button[data-tag='more-actions-button'])",
);
const more_actions_button = more_actions_button_container.querySelector(
"button[data-tag='more-actions-button']",
);
const clipboard_button = document.createElement("button");
clipboard_button.className = more_actions_button.className;
clipboard_button.classList.add("rtonne-patreon-streamable-button");
more_actions_button_container.before(clipboard_button);
const clipboard_button_svg_container = document.createElement("div");
clipboard_button_svg_container.className =
more_actions_button.children[0].className;
clipboard_button_svg_container.innerHTML = clipboard_svg;
clipboard_button.append(clipboard_button_svg_container);
clipboard_button.onclick = async () => {
post_video_figure
.querySelector("div[data-tag='media-container']")
.click();
const [post_iframe] = await waitForElements(element, "iframe");
const post_iframe_url = post_iframe.src;
const post_title = element.querySelector(
"span[data-tag='post-title'] > a",
);
const post_title_text = post_title.innerText.trim();
const post_url = post_title.href;
GM.setClipboard(
getClipboardContent(post_title_text, post_url, post_iframe_url),
);
};
}
});
observer.observe(document.body, {
childList: true,
subtree: true,
});
/**
* Uses a MutationObserver to wait until the element we want exists.
* This function is required because elements take a while to appear sometimes.
* https://stackoverflow.com/questions/5525071/how-to-wait-until-an-element-exists
* @param {HTMLElement} node The element we want to search in.
* @param {string} selector A string for node.querySelector describing the elements we want.
* @returns {Promise} The list of elements found.
*/
function waitForElements(node, selector) {
return new Promise((resolve) => {
if (node.querySelector(selector)) {
return resolve(node.querySelectorAll(selector));
}
const observer = new MutationObserver(() => {
if (node.querySelector(selector)) {
observer.disconnect();
resolve(node.querySelectorAll(selector));
}
});
observer.observe(document.body, {
childList: true,
subtree: true,
});
});
}