View difference between Paste ID: sEd3WRLn and CedUj6vb
SHOW: | | - or go back to the newest paste.
1
// ==UserScript==
2
// @name         Instagram s9e Stealthgram Link Inserter
3
// @namespace    https://stealthgram.com/
4-
// @version      2025.08.16
4+
// @version      2025.08.16.2
5-
// @description  Seamlessly insert Stealthgram links under Instagram embeds and Twstalker/xstalk/archive/nitter/etc. links under Twitter embeds (s9e), with duplicate prevention and dynamic embed handling
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
6
// @match        *://*www.thecoli.com/threads/*
7
// @match        *://*/threads/*
8
// @grant        none
9
// ==/UserScript==
10
11
(function() {
12
  'use strict';
13
14
  const debug = false; // Set to true for console.log debugging
15
  function log(msg) { if (debug) console.log('[Stealthgram userscript]', msg); }
16
17
  // =========================
18
  // Instagram support
19
  // =========================
20
  function getInstagramMediaId(iframe) {
21
    if (!iframe || !iframe.src) return null;
22
    const match = iframe.src.match(/#([A-Za-z0-9_-]{5,})/);
23
    return match ? match[1] : null;
24
  }
25
26
  function insertStealthgramLink(iframe, mediaId) {
27
    if (!iframe || !mediaId) return;
28
    if (iframe.getAttribute('data-stealthgram-done')) return;
29
30-
    const br = document.createElement('br');
30+
    // Add spacing
31
    const br1 = document.createElement('br');
32
    const br2 = document.createElement('br');
33
    const link = document.createElement('a');
34
    link.href = 'https://stealthgram.com/media/' + mediaId;
35
    link.textContent = 'Stealthgram link';
36
    link.target = '_blank';
37-
    link.style = 'font-size:90%;font-family:monospace;';
37+
38-
    iframe.insertAdjacentElement('afterend', br);
38+
39-
    br.insertAdjacentElement('afterend', link);
39+
    link.style = 'font-size:90%;font-family:monospace;text-decoration:underline;';
40
41
    iframe.insertAdjacentElement('afterend', br1);
42
    br1.insertAdjacentElement('afterend', link);
43
    link.insertAdjacentElement('afterend', br2);
44
45
    iframe.setAttribute('data-stealthgram-done', '1');
46
    log('Inserted Stealthgram link for ' + mediaId);
47
  }
48
49
  // =========================
50
  // Twitter support
51
  // =========================
52
  function createTwitterLinks(tweetId) {
53
    const baseTwitter = `https://twitter.com/undefined/status/${tweetId}`;
54
    const baseTwstalker = `https://twstalker.com/undefined/status/${tweetId}`;
55
    const baseXstalk = `https://xstalk.com/profile/undefined/status/${tweetId}`;
56
    const baseXcancel = `https://xcancel.com/undefined/status/${tweetId}`;
57
    const baseNitter = `https://nitter.poast.org/undefined/status/${tweetId}`;
58
59
    return {
60
      twstalker: baseTwstalker,
61
      archiveOrgSave: `https://web.archive.org/save/${baseTwstalker}`,
62
      archiveIs: `https://archive.is/?run=1&url=${baseTwstalker}`,
63
      xstalk: baseXstalk,
64
      xstalkSave: `https://web.archive.org/save/${baseXstalk}`,
65
      archiveIsXstalk: `https://archive.is/?run=1&url=${baseXstalk}`,
66
      archiveIsX: `https://archive.is/?run=1&url=${baseTwitter}`,
67
      nitter: baseNitter,
68
      xcancel: baseXcancel,
69
      xcancelSave: `https://archive.is/?run=1&url=${baseXcancel}`
70
    };
71
  }
72
73
  function insertTwitterLinks(iframe, tweetId) {
74
    if (!iframe || !tweetId) return;
75
    if (iframe.getAttribute('data-twstalker-done')) return;
76-
      <a href="${links.twstalker}" target="_blank">twstalker</a> |
76+
77-
      <a href="${links.archiveOrgSave}" target="_blank">archive.org(save)</a> |
77+
78-
      <a href="${links.archiveIs}" target="_blank">archive.is</a> |
78+
79-
      <a href="${links.xstalk}" target="_blank">xstalk</a> |
79+
80-
      <a href="${links.xstalkSave}" target="_blank">xstalk(save)</a> |
80+
81-
      <a href="${links.archiveIsXstalk}" target="_blank">archive.is(xstalk)</a> |
81+
      <a href="${links.twstalker}" target="_blank" style="text-decoration:underline;">twstalker</a> |
82-
      <a href="${links.archiveIsX}" target="_blank">archive.is(X)</a> |
82+
      <a href="${links.archiveOrgSave}" target="_blank" style="text-decoration:underline;">archive.org(save)</a> |
83-
      <a href="${links.nitter}" target="_blank">nitter</a> |
83+
      <a href="${links.archiveIs}" target="_blank" style="text-decoration:underline;">archive.is</a> |
84-
      <a href="${links.xcancel}" target="_blank">xcancel</a> |
84+
      <a href="${links.xstalk}" target="_blank" style="text-decoration:underline;">xstalk</a> |
85-
      <a href="${links.xcancelSave}" target="_blank">xcancel(save)</a>
85+
      <a href="${links.xstalkSave}" target="_blank" style="text-decoration:underline;">xstalk(save)</a> |
86
      <a href="${links.archiveIsXstalk}" target="_blank" style="text-decoration:underline;">archive.is(xstalk)</a> |
87-
    iframe.insertAdjacentElement('afterend', document.createElement('br'));
87+
      <a href="${links.archiveIsX}" target="_blank" style="text-decoration:underline;">archive.is(X)</a> |
88-
    iframe.nextElementSibling.insertAdjacentElement('afterend', container);
88+
      <a href="${links.nitter}" target="_blank" style="text-decoration:underline;">nitter</a> |
89
      <a href="${links.xcancel}" target="_blank" style="text-decoration:underline;">xcancel</a> |
90
      <a href="${links.xcancelSave}" target="_blank" style="text-decoration:underline;">xcancel(save)</a>
91
    `;
92
93
    // Add spacing
94
    const br1 = document.createElement('br');
95
    const br2 = document.createElement('br');
96
    iframe.insertAdjacentElement('afterend', br1);
97
    br1.insertAdjacentElement('afterend', container);
98
    container.insertAdjacentElement('afterend', br2);
99
100
    iframe.setAttribute('data-twstalker-done', '1');
101
    log('Inserted Twstalker/xstalk/etc links for tweet ' + tweetId);
102
  }
103
104
  function getTweetId(iframe) {
105
    if (!iframe || !iframe.src) return null;
106
    const match = iframe.src.match(/#(\d{5,})/);
107
    return match ? match[1] : null;
108
  }
109
110
  // =========================
111
  // Processing
112
  // =========================
113
  let throttleTimeout;
114
  function processEmbeds() {
115
    if (throttleTimeout) return;
116
    throttleTimeout = setTimeout(() => {
117
      throttleTimeout = null;
118
119
      // Instagram
120
      const igEmbeds = document.querySelectorAll('iframe[data-s9e-mediaembed="instagram"]');
121
      for (let iframe of igEmbeds) {
122
        const mediaId = getInstagramMediaId(iframe);
123
        if (mediaId) insertStealthgramLink(iframe, mediaId);
124
      }
125
126
      // Twitter
127
      const twEmbeds = document.querySelectorAll('iframe[data-s9e-mediaembed="twitter"]');
128
      for (let iframe of twEmbeds) {
129
        const tweetId = getTweetId(iframe);
130
        if (tweetId) insertTwitterLinks(iframe, tweetId);
131
      }
132
    }, 200);
133
  }
134
135
  processEmbeds();
136
137
  const observer = new MutationObserver(() => { processEmbeds(); });
138
  observer.observe(document.body, {childList: true, subtree: true});
139
140
  log('Instagram + Twitter userscript loaded.');
141
})();
142