SHOW:
|
|
- or go back to the newest paste.
| 1 | // ==UserScript== | |
| 2 | - | // @name Instagram s9e Stealthgram Link Inserter |
| 2 | + | // @name Stealthgram Profile Link & Clipboard Tools |
| 3 | // @namespace https://stealthgram.com/ | |
| 4 | - | // @version 2025.08.16.2 |
| 4 | + | // @version 1.1 |
| 5 | - | // @description Seamlessly insert Stealthgram links under Instagram embeds and Twstalker/xstalk/archive/nitter/etc. links under Twitter embeds (s9e), with duplicate prevention, spacing, and dynamic embed handling |
| 5 | + | // @description Inserts profile link, copy buttons, and notifications under profile on Stealthgram post pages, even if DOM reloads dynamically |
| 6 | - | // @match *://*www.thecoli.com/threads/* |
| 6 | + | // @author OpenAI Perplexity |
| 7 | - | // @match *://*/threads/* |
| 7 | + | // @match https://stealthgram.com/media/* |
| 8 | - | // @grant none |
| 8 | + | // @grant GM_setClipboard |
| 9 | // @run-at document-end | |
| 10 | // ==/UserScript== | |
| 11 | ||
| 12 | (function() {
| |
| 13 | 'use strict'; | |
| 14 | - | const debug = false; // Set to true for console.log debugging |
| 14 | + | |
| 15 | - | function log(msg) { if (debug) console.log('[Stealthgram userscript]', msg); }
|
| 15 | + | // Toast notification creator |
| 16 | function showToast(msg) {
| |
| 17 | - | // ========================= |
| 17 | + | let toast = document.createElement("div");
|
| 18 | - | // Instagram support |
| 18 | + | toast.textContent = msg; |
| 19 | - | // ========================= |
| 19 | + | toast.style.position = "fixed"; |
| 20 | - | function getInstagramMediaId(iframe) {
|
| 20 | + | toast.style.bottom = "32px"; |
| 21 | - | if (!iframe || !iframe.src) return null; |
| 21 | + | toast.style.left = "50%"; |
| 22 | - | const match = iframe.src.match(/#([A-Za-z0-9_-]{5,})/);
|
| 22 | + | toast.style.transform = "translateX(-50%)"; |
| 23 | - | return match ? match[1] : null; |
| 23 | + | toast.style.background = "rgba(30,30,30,0.98)"; |
| 24 | toast.style.color = "white"; | |
| 25 | toast.style.padding = "12px 32px"; | |
| 26 | - | function insertStealthgramLink(iframe, mediaId) {
|
| 26 | + | toast.style.fontSize = "1rem"; |
| 27 | - | if (!iframe || !mediaId) return; |
| 27 | + | toast.style.borderRadius = "16px"; |
| 28 | - | if (iframe.getAttribute('data-stealthgram-done')) return;
|
| 28 | + | toast.style.zIndex = 99999; |
| 29 | toast.style.boxShadow = "0 4px 32px #000a"; | |
| 30 | - | // Add spacing |
| 30 | + | toast.style.fontWeight = "500"; |
| 31 | - | const br1 = document.createElement('br');
|
| 31 | + | document.body.appendChild(toast); |
| 32 | - | const br2 = document.createElement('br');
|
| 32 | + | setTimeout(() => {toast.remove();}, 1800);
|
| 33 | - | const link = document.createElement('a');
|
| 33 | + | |
| 34 | - | link.href = 'https://stealthgram.com/media/' + mediaId; |
| 34 | + | |
| 35 | - | link.textContent = 'Stealthgram link'; |
| 35 | + | // Insert tools if profile header exists |
| 36 | - | link.target = '_blank'; |
| 36 | + | function insertTools() {
|
| 37 | - | link.rel = 'noopener noreferrer'; |
| 37 | + | const h = document.querySelector('h5.header');
|
| 38 | - | link.className = 'stealthgram-link'; |
| 38 | + | if (!h) return false; |
| 39 | - | link.style = 'font-size:90%;font-family:monospace;text-decoration:underline;'; |
| 39 | + | |
| 40 | let profileName = h.textContent.trim(); | |
| 41 | - | iframe.insertAdjacentElement('afterend', br1);
|
| 41 | + | if (!profileName) return false; |
| 42 | - | br1.insertAdjacentElement('afterend', link);
|
| 42 | + | |
| 43 | - | link.insertAdjacentElement('afterend', br2);
|
| 43 | + | let profileUrl = `https://stealthgram.com/profile/${profileName}`;
|
| 44 | let m = window.location.pathname.match(/\/media\/([^\/?#]+)/); | |
| 45 | - | iframe.setAttribute('data-stealthgram-done', '1');
|
| 45 | + | if (!m) return false; |
| 46 | - | log('Inserted Stealthgram link for ' + mediaId);
|
| 46 | + | let postId = m[1]; |
| 47 | let filename = `instagram_p_${postId} by ${profileName}`;
| |
| 48 | ||
| 49 | - | // ========================= |
| 49 | + | // Prevent adding twice |
| 50 | - | // Twitter support |
| 50 | + | if (h.parentNode.querySelector('.stealth-profiletools')) return true;
|
| 51 | - | // ========================= |
| 51 | + | |
| 52 | - | function createTwitterLinks(tweetId) {
|
| 52 | + | let container = document.createElement('div');
|
| 53 | - | const baseTwitter = `https://twitter.com/undefined/status/${tweetId}`;
|
| 53 | + | container.className = 'stealth-profiletools'; |
| 54 | - | const baseTwstalker = `https://twstalker.com/undefined/status/${tweetId}`;
|
| 54 | + | container.style.marginTop = '10px'; |
| 55 | - | const baseXstalk = `https://xstalk.com/profile/undefined/status/${tweetId}`;
|
| 55 | + | container.style.display = 'flex'; |
| 56 | - | const baseXcancel = `https://xcancel.com/undefined/status/${tweetId}`;
|
| 56 | + | container.style.alignItems = 'center'; |
| 57 | - | const baseNitter = `https://nitter.poast.org/undefined/status/${tweetId}`;
|
| 57 | + | container.style.gap = '10px'; |
| 58 | ||
| 59 | - | return {
|
| 59 | + | // Profile link |
| 60 | - | twstalker: baseTwstalker, |
| 60 | + | let a = document.createElement('a');
|
| 61 | - | archiveOrgSave: `https://web.archive.org/save/${baseTwstalker}`,
|
| 61 | + | a.href = profileUrl; |
| 62 | - | archiveIs: `https://archive.is/?run=1&url=${baseTwstalker}`,
|
| 62 | + | a.textContent = profileUrl; |
| 63 | - | xstalk: baseXstalk, |
| 63 | + | a.target = '_blank'; |
| 64 | - | xstalkSave: `https://web.archive.org/save/${baseXstalk}`,
|
| 64 | + | a.style.color = '#1da1f2'; |
| 65 | - | archiveIsXstalk: `https://archive.is/?run=1&url=${baseXstalk}`,
|
| 65 | + | a.style.fontSize = '1rem'; |
| 66 | - | archiveIsX: `https://archive.is/?run=1&url=${baseTwitter}`,
|
| 66 | + | a.style.textDecoration = 'underline'; |
| 67 | - | nitter: baseNitter, |
| 67 | + | container.appendChild(a); |
| 68 | - | xcancel: baseXcancel, |
| 68 | + | |
| 69 | - | xcancelSave: `https://archive.is/?run=1&url=${baseXcancel}`
|
| 69 | + | // Copy link button |
| 70 | let btnClipboard = document.createElement('button');
| |
| 71 | btnClipboard.textContent = '📋'; | |
| 72 | btnClipboard.title = 'Copy profile link'; | |
| 73 | - | function insertTwitterLinks(iframe, tweetId) {
|
| 73 | + | btnClipboard.style.cursor = 'pointer'; |
| 74 | - | if (!iframe || !tweetId) return; |
| 74 | + | btnClipboard.style.border = 'none'; |
| 75 | - | if (iframe.getAttribute('data-twstalker-done')) return;
|
| 75 | + | btnClipboard.style.background = 'transparent'; |
| 76 | btnClipboard.style.fontSize = '1.2em'; | |
| 77 | - | const links = createTwitterLinks(tweetId); |
| 77 | + | btnClipboard.onclick = function() {
|
| 78 | - | const container = document.createElement('div');
|
| 78 | + | if (typeof GM_setClipboard === "function") {
|
| 79 | - | container.style = 'font-size:90%;font-family:monospace;'; |
| 79 | + | GM_setClipboard(profileUrl); |
| 80 | - | container.innerHTML = ` |
| 80 | + | } else {
|
| 81 | - | <a href="${links.twstalker}" target="_blank" style="text-decoration:underline;">twstalker</a> |
|
| 81 | + | navigator.clipboard.writeText(profileUrl); |
| 82 | - | <a href="${links.archiveOrgSave}" target="_blank" style="text-decoration:underline;">archive.org(save)</a> |
|
| 82 | + | |
| 83 | - | <a href="${links.archiveIs}" target="_blank" style="text-decoration:underline;">archive.is</a> |
|
| 83 | + | showToast('Profile link copied!');
|
| 84 | - | <a href="${links.xstalk}" target="_blank" style="text-decoration:underline;">xstalk</a> |
|
| 84 | + | |
| 85 | - | <a href="${links.xstalkSave}" target="_blank" style="text-decoration:underline;">xstalk(save)</a> |
|
| 85 | + | container.appendChild(btnClipboard); |
| 86 | - | <a href="${links.archiveIsXstalk}" target="_blank" style="text-decoration:underline;">archive.is(xstalk)</a> |
|
| 86 | + | |
| 87 | - | <a href="${links.archiveIsX}" target="_blank" style="text-decoration:underline;">archive.is(X)</a> |
|
| 87 | + | // Copy filename button |
| 88 | - | <a href="${links.nitter}" target="_blank" style="text-decoration:underline;">nitter</a> |
|
| 88 | + | let btnFilename = document.createElement('button');
|
| 89 | - | <a href="${links.xcancel}" target="_blank" style="text-decoration:underline;">xcancel</a> |
|
| 89 | + | btnFilename.textContent = 'Filename'; |
| 90 | - | <a href="${links.xcancelSave}" target="_blank" style="text-decoration:underline;">xcancel(save)</a>
|
| 90 | + | btnFilename.title = 'Copy filename'; |
| 91 | - | `; |
| 91 | + | btnFilename.style.cursor = 'pointer'; |
| 92 | btnFilename.style.border = '1px solid #bbb'; | |
| 93 | - | // Add spacing |
| 93 | + | btnFilename.style.background = 'white'; |
| 94 | - | const br1 = document.createElement('br');
|
| 94 | + | btnFilename.style.borderRadius = '7px'; |
| 95 | - | const br2 = document.createElement('br');
|
| 95 | + | btnFilename.style.fontSize = '0.95em'; |
| 96 | - | iframe.insertAdjacentElement('afterend', br1);
|
| 96 | + | btnFilename.style.marginLeft = '6px'; |
| 97 | - | br1.insertAdjacentElement('afterend', container);
|
| 97 | + | btnFilename.onclick = function() {
|
| 98 | - | container.insertAdjacentElement('afterend', br2);
|
| 98 | + | if (typeof GM_setClipboard === "function") {
|
| 99 | GM_setClipboard(filename); | |
| 100 | - | iframe.setAttribute('data-twstalker-done', '1');
|
| 100 | + | } else {
|
| 101 | - | log('Inserted Twstalker/xstalk/etc links for tweet ' + tweetId);
|
| 101 | + | navigator.clipboard.writeText(filename); |
| 102 | } | |
| 103 | showToast('Filename copied!');
| |
| 104 | - | function getTweetId(iframe) {
|
| 104 | + | |
| 105 | - | if (!iframe || !iframe.src) return null; |
| 105 | + | container.appendChild(btnFilename); |
| 106 | - | const match = iframe.src.match(/#(\d{5,})/);
|
| 106 | + | |
| 107 | - | return match ? match[1] : null; |
| 107 | + | h.parentNode.insertBefore(container, h.nextSibling); |
| 108 | ||
| 109 | return true; | |
| 110 | - | // ========================= |
| 110 | + | |
| 111 | - | // Processing |
| 111 | + | |
| 112 | - | // ========================= |
| 112 | + | // Timed retries (for slow loading) |
| 113 | - | let throttleTimeout; |
| 113 | + | let tries = 0; |
| 114 | - | function processEmbeds() {
|
| 114 | + | let maxTries = 40; |
| 115 | - | if (throttleTimeout) return; |
| 115 | + | function tryInsertTools() {
|
| 116 | - | throttleTimeout = setTimeout(() => {
|
| 116 | + | if (!insertTools() && (++tries < maxTries)) {
|
| 117 | - | throttleTimeout = null; |
| 117 | + | setTimeout(tryInsertTools, 100); |
| 118 | } | |
| 119 | - | |
| 119 | + | |
| 120 | - | const igEmbeds = document.querySelectorAll('iframe[data-s9e-mediaembed="instagram"]');
|
| 120 | + | tryInsertTools(); |
| 121 | - | for (let iframe of igEmbeds) {
|
| 121 | + | |
| 122 | - | const mediaId = getInstagramMediaId(iframe); |
| 122 | + | // MutationObserver (for dynamic DOM reloads) |
| 123 | - | if (mediaId) insertStealthgramLink(iframe, mediaId); |
| 123 | + | const observer = new MutationObserver(() => {
|
| 124 | insertTools(); | |
| 125 | }); | |
| 126 | - | |
| 126 | + | observer.observe(document.body, { childList: true, subtree: true });
|
| 127 | - | const twEmbeds = document.querySelectorAll('iframe[data-s9e-mediaembed="twitter"]');
|
| 127 | + | |
| 128 | - | for (let iframe of twEmbeds) {
|
| 128 | + |