View difference between Paste ID: 5SAdHzCw and sEd3WRLn
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-
      // Instagram
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-
      // Twitter
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+