View difference between Paste ID: wLBTWN1d and mu1LVzTK
SHOW: | | - or go back to the newest paste.
1
javascript:(function(){
2
  const MAX_IMAGES_PER_POST = 10;
3
  function replaceDomain(e){return e.replace(/^(https?:\/\/)([^\/]+)/,"$1bsky.app")}
4
  function cleanText(e){
5
    return e ? e.replace(/<a href="([^"]+)"[^>]*>([^<]+)<\/a>/g, (match, url, text) => (url.startsWith('/') ? `[U][URL=https://bsky.app${url}]${text}[/URL][/U]` : `[U][URL=${url}]${text}[/URL][/U]`)).replace(/<br\s*\/?>/g, "\n").replace(/<[^>]+>/g, "").trim() : ""
6
  }
7
  function extractMedia(element, excludeSelectors = []) { 
8
    let images = Array.from(element.querySelectorAll('img[src*="cdn.bsky.app"]'));
9
    
10
    
11
    if (excludeSelectors.length > 0) {
12
        excludeSelectors.forEach(selector => {
13
            element.querySelectorAll(selector).forEach(excludedContainer => {
14
                images = images.filter(img => !excludedContainer.contains(img));
15
            });
16
        });
17
    }
18
    
19
    images = images.map(e => e.src).filter(e => !e.includes("/avatar_thumbnail/"));
20
    
21
    let videoSourcesQuery = "video source[src], video[src]"; 
22
    
23
    if (excludeSelectors.length > 0) {
24
        excludeSelectors.forEach(selector => {
25
             
26
             
27
             
28
        });
29
    }
30
    const videoSources = new Set([...Array.from(element.querySelectorAll(videoSourcesQuery)).map(e => e.src)]);
31
    
32
    return {images, videos: Array.from(videoSources)}
33
  }
34
  function showToast(e){
35
    const t = document.createElement("div"); t.style.cssText = "position:fixed;bottom:20px;left:20px;padding:10px 20px;background:#333;color:#fff;border-radius:4px;z-index:10000;font-family:Arial,sans-serif;font-size:14px;opacity=1;transition:opacity 1s;box-shadow:0 2px 5px rgba(0,0,0,0.3)"; t.innerText = e; document.body.appendChild(t); setTimeout(() => {t.style.opacity = "0"; setTimeout(() => document.body.removeChild(t), 1e3)}, 2e3)
36
  }
37
  function createPostCountPrompt(e){
38
    const t = document.createElement("div"); t.style.cssText = "position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.7);z-index:9999;display:flex;justify-content:center;align-items:center"; const r = document.createElement("div"); r.style.cssText = "background:white;padding:20px;border-radius:8px;box-shadow:0 0 10px rgba(0,0,0,0.3);text-align:center"; const n = document.createElement("input"); n.type = "text"; n.value = "12"; n.style.cssText = "font-size:30px;width:100px;text-align:center;margin-bottom:20px"; const o = document.createElement("div"); o.style.cssText = "display:grid;grid-template-columns:repeat(3,1fr);gap:10px"; "1234567890".split("").forEach(e => {const t = document.createElement("button"); t.textContent = e; t.style.cssText = "font-size:20px;padding:10px;cursor:pointer"; t.onclick = () => n.value += e; o.appendChild(t)}); const p = document.createElement("div");p.style.cssText="display:flex;gap:10px;margin-bottom:20px";[1,2,10,25,35,50].forEach(num=>{const b=document.createElement("button");b.textContent=num;b.style.cssText="font-size:16px;padding:8px;cursor:pointer";b.onclick=()=>n.value=num;p.appendChild(b)}); const s = document.createElement("button"); s.textContent = "Clear"; s.style.cssText = "font-size:18px;margin-top:10px;padding:10px 20px;cursor:pointer"; s.onclick = () => n.value = ""; const a = document.createElement("button"); a.textContent = "Set Number of Posts"; a.style.cssText = "font-size:18px;margin-top:20px;padding:10px 20px;cursor:pointer"; a.onclick = () => {const o = parseInt(n.value, 10); isNaN(o) || o <= 0 ? showToast("Please enter a valid number.") : (document.body.removeChild(t), e(o))}; r.append(n,p,o, s, a); t.appendChild(r); document.body.appendChild(t)
39
  }
40
  async function getEmbedUrl(threadData){ 
41
    let url = null; const post = threadData?.thread?.post;
42
    if (post?.embed?.$type === "app.bsky.embed.video#view" && post.embed.playlist) {url = post.embed.playlist;}
43
    else if (post?.record?.embed?.$type === "app.bsky.embed.video" && post.author?.did && post.record.embed.video?.ref?.$link) {
44
      url = constructVideoUrl(post.author.did, post.record.embed.video);
45
    }
46
    return url
47
  }
48
  function constructVideoUrl(did, videoEmbed){
49
    if (videoEmbed.mimeType === "video/webm") return `https://bsky.social/xrpc/com.atproto.sync.getBlob?did=${encodeURIComponent(did)}&cid=${encodeURIComponent(videoEmbed.ref.$link)}`;
50
    return `https://video.bsky.app/watch/${encodeURIComponent(did)}/${encodeURIComponent(videoEmbed.ref.$link)}/playlist.m3u8`
51
  }
52
  function cleanHandle(handle) {
53
    if (!handle) return "Unknown";
54
    let cleaned = handle.replace(/[\u202a-\u202f\u00AD\u200B-\u200D\uFEFF\s]/g, "");
55
    cleaned = cleaned.trim();
56
    cleaned = cleaned.replace(/^@+/, "");
57
    return cleaned.trim();
58
  }
59
60
  async function extractPost(postElement, isActivePost = false){
61
    if (!(postElement instanceof Element)) return console.error("Invalid post element:", postElement), {text: "", author: "", url: "", threadData: null, quotedUrl: null};
62
    
63
    
64
    let thisPostsAuthorHandleFromTestId = postElement.getAttribute("data-testid")?.match(/postThreadItem-by-(.+)/)?.[1];
65
    let thisPostsUrl = null;
66
    let thisPostsId = null;
67
    const quoteEmbedSelector = '[role="link"][aria-label*="Post by"]'; 
68
69
    if (thisPostsAuthorHandleFromTestId) {
70
      thisPostsAuthorHandleFromTestId = cleanHandle(thisPostsAuthorHandleFromTestId);
71
      
72
      
73
      const timestampLinks = Array.from(postElement.querySelectorAll('[data-testid="postTimestamp"] a[href*="/post/"], a[href*="/post/"]'));
74
      let foundLinkForThisPost = null;
75
      for (const link of timestampLinks) {
76
          if (!link.closest(quoteEmbedSelector)) { 
77
              foundLinkForThisPost = link;
78
              break;
79
          }
80
      }
81
      if (!foundLinkForThisPost && timestampLinks.length > 0 && !postElement.querySelector(quoteEmbedSelector)) {
82
          
83
          foundLinkForThisPost = timestampLinks[0];
84
      }
85
86
87
      if (foundLinkForThisPost) {
88
          thisPostsId = foundLinkForThisPost.href.split('/').pop().split('?')[0];
89
          thisPostsUrl = `https://bsky.app/profile/${thisPostsAuthorHandleFromTestId}/post/${thisPostsId}`;
90
          console.log(`extractPost: DOM URL for self (postElement): ${thisPostsUrl}`);
91
      } else {
92
          console.warn(`extractPost: Could not find reliable DOM URL for self (postElement). TestID handle: ${thisPostsAuthorHandleFromTestId}`);
93
      }
94
    }
95
96
    
97
    let threadDataForThisPost = null;
98
    if (thisPostsAuthorHandleFromTestId && (thisPostsId || isActivePost)) {
99
      
100
      const postIdForApi = thisPostsId || (isActivePost ? window.location.href.split('/').pop().split('?')[0] : null);
101
      if (postIdForApi) {
102
        const apiUri = `at://${thisPostsAuthorHandleFromTestId}/app.bsky.feed.post/${postIdForApi}`;
103
        const apiUrl = `https://public.api.bsky.app/xrpc/app.bsky.feed.getPostThread?uri=${encodeURIComponent(apiUri)}&depth=0`;
104
        try {
105
          const res = await fetch(apiUrl);
106
          threadDataForThisPost = res.ok ? await res.json() : null;
107
          if (threadDataForThisPost?.thread?.post?.uri) {
108
            
109
            const apiUriParts = threadDataForThisPost.thread.post.uri.split('/');
110
            thisPostsId = apiUriParts.pop();
111
            const apiAuthorHandle = cleanHandle(threadDataForThisPost.thread.post.author.handle); 
112
            thisPostsAuthorHandleFromTestId = apiAuthorHandle; 
113
            thisPostsUrl = `https://bsky.app/profile/${apiAuthorHandle}/post/${thisPostsId}`;
114
            console.log(`extractPost: API OVERRIDE/CONFIRMED URL for self: ${thisPostsUrl}`);
115
          } else if (threadDataForThisPost) {
116
            console.log("extractPost: API data fetched but no post.uri for self:", thisPostsUrl, threadDataForThisPost);
117
          } else {
118
             console.warn("extractPost: No API data or error for self:", thisPostsUrl || `at://${thisPostsAuthorHandleFromTestId}/app.bsky.feed.post/${postIdForApi}`);
119
          }
120
        } catch (err) {
121
          console.warn("extractPost: API fetch failed for self:", thisPostsUrl, err);
122
        }
123
      }
124
    }
125
     if (!thisPostsUrl && isActivePost && thisPostsAuthorHandleFromTestId) { 
126
        thisPostsUrl = `https://bsky.app/profile/${thisPostsAuthorHandleFromTestId}/post/${window.location.href.split('/').pop().split('?')[0]}`;
127
        console.log("extractPost: Fallback URL for active post (no API/DOM ID):", thisPostsUrl);
128
    }
129
130
131
    
132
    const textContentElem = postElement.querySelector('div[data-word-wrap="1"]:not([data-testid="quoteEmbed"]) div[data-word-wrap="1"], [data-testid="postText"]:not([data-testid="quoteEmbed"]) [data-testid="postText"]'); 
133
    
134
    const mainTextContentElement = postElement.querySelector(':scope > div > [data-testid="postText"], :scope > div > div > [data-testid="postText"], :scope > div > div > div[data-word-wrap="1"]'); 
135
    
136
    let postOutputText = "";
137
    if (mainTextContentElement) {
138
        
139
        const clonedTextContainer = mainTextContentElement.cloneNode(true);
140
        clonedTextContainer.querySelector(quoteEmbedSelector)?.remove(); 
141
        postOutputText = cleanText(clonedTextContainer.innerHTML || clonedTextContainer.textContent);
142
    } else { 
143
        const tempDiv = document.createElement('div');
144
        tempDiv.innerHTML = postElement.innerHTML;
145
        tempDiv.querySelector(quoteEmbedSelector)?.remove(); 
146
        const mainTextOnlyElem = tempDiv.querySelector('div[data-word-wrap="1"], [data-testid="postText"]');
147
        postOutputText = mainTextOnlyElem ? cleanText(mainTextOnlyElem.innerHTML || mainTextOnlyElem.textContent) : "";
148
    }
149
150
151
    
152
    const mediaForThisPost = extractMedia(postElement, [quoteEmbedSelector]);
153
    mediaForThisPost.images.slice(0, MAX_IMAGES_PER_POST).forEach(imgUrl => postOutputText += `\n[img]${imgUrl}[/img]`);
154
    if (threadDataForThisPost) { 
155
      const videoUrl = await getEmbedUrl(threadDataForThisPost);
156
      if (videoUrl) postOutputText += `\n[U][URL]${videoUrl}[/URL][/U]`;
157
    }
158
159
160
    
161
    let urlOfQuotedPostByThis = null; 
162
    const quoteEmbedElement = postElement.querySelector(quoteEmbedSelector);
163
164
    
165
    const apiQuoteRecordInfo = threadDataForThisPost?.thread?.post?.record?.embed?.record;
166
    if (apiQuoteRecordInfo?.uri && (apiQuoteRecordInfo?.$type === "app.bsky.feed.defs#postView" || apiQuoteRecordInfo?.$type === "app.bsky.embed.record#viewRecord" || apiQuoteRecordInfo?.$type === "app.bsky.embed.recordWithMedia#viewRecord")) {
167
        let actualQuotedRecord = apiQuoteRecordInfo.value ? apiQuoteRecordInfo : apiQuoteRecordInfo.record || apiQuoteRecordInfo;
168
        if (actualQuotedRecord?.uri) {
169
            const qAuthorHandleRaw = actualQuotedRecord.author?.handle || actualQuotedRecord.uri.split('/')[2];
170
            const qAuthorHandleClean = cleanHandle(qAuthorHandleRaw);
171
            const qPostId = actualQuotedRecord.uri.split('/')[4];
172
            const qTextRaw = actualQuotedRecord.value?.text || "";
173
            const qTextClean = cleanText(qTextRaw);
174
            
175
            urlOfQuotedPostByThis = `https://bsky.app/profile/${qAuthorHandleClean}/post/${qPostId}`;
176-
            postOutputText += `\n\n[QUOTED POST]\nπŸ‡ΊπŸ‡Έ ${qAuthorHandleClean}\n${qTextClean}`;
176+
            postOutputText += `\n\n[QUOTED POST]\nπŸ†” ${qAuthorHandleClean}\n${qTextClean}`;
177
            console.log(`extractPost: API Quoted Post BY post ${thisPostsId}: URL='${urlOfQuotedPostByThis}'`);
178
179
            const quotedEmbeds = actualQuotedRecord.embeds || actualQuotedRecord.value?.embeds;
180
            if (quotedEmbeds?.length > 0) {
181
                quotedEmbeds.forEach(emb => {
182
                    if (emb.$type === "app.bsky.embed.images#view" && emb.images) {
183
                        emb.images.slice(0, MAX_IMAGES_PER_POST).forEach(img => postOutputText += `\n[img]${img.thumb}[/img]`); 
184
                    } else if (emb.$type === "app.bsky.embed.video#view" && emb.playlist) {
185
                         postOutputText += `\n[U][URL]${emb.playlist}[/URL][/U]`;
186
                    } else if (emb.$type === "app.bsky.embed.images" && emb.images && actualQuotedRecord.author?.did) {
187
                         emb.images.slice(0, MAX_IMAGES_PER_POST).forEach(img => postOutputText += `\n[img]${img.image.ref?.$link ? `https://cdn.bsky.app/img/feed_fullsize/plain/${actualQuotedRecord.author.did}/${img.image.ref.$link}@${img.image.mimeType.split('/')[1]}` : 'fallback.jpg'}[/img]`);
188
                    }
189
                });
190
            }
191
        }
192
    }
193
    
194
    else if (quoteEmbedElement) {
195
        const qTextContentElem = quoteEmbedElement.querySelector('div[data-word-wrap="1"]'); 
196
        const qTextRaw = qTextContentElem?.innerHTML || "";
197
        if (qTextRaw) {
198
            const qTextClean = cleanText(qTextRaw);
199
            let qAuthorHandleRaw = quoteEmbedElement.querySelector(".css-146c3p1.r-dnmrzs.r-1udh08x.r-1udbk01.r-3s2u2q.r-1iln25a")?.textContent?.trim() || "Unknown";
200
            const qAuthorHandleClean = cleanHandle(qAuthorHandleRaw);
201
            
202
            let qPostId = null;
203
            
204
            const qPostLink = quoteEmbedElement.querySelector('a[href*="/post/"]'); 
205
            if (qPostLink) {
206
                qPostId = qPostLink.href.split('/').pop().split('?')[0];
207
            }
208
            
209-
            postOutputText += `\n\n[QUOTED POST]\nπŸ‡ΊπŸ‡Έ ${qAuthorHandleClean}\n${qTextClean}`;
209+
            postOutputText += `\n\n[QUOTED POST]\nπŸ†” ${qAuthorHandleClean}\n${qTextClean}`;
210
            if (qAuthorHandleClean !== "Unknown" && qPostId) {
211
                 urlOfQuotedPostByThis = `https://bsky.app/profile/${qAuthorHandleClean}/post/${qPostId}`;
212
                 console.log(`extractPost: DOM Quoted Post BY post ${thisPostsId}: URL='${urlOfQuotedPostByThis}' (ID='${qPostId}')`);
213
            }
214
215
            
216
            const mediaForTheQuotedPost = extractMedia(quoteEmbedElement); 
217
            mediaForTheQuotedPost.images.slice(0, MAX_IMAGES_PER_POST).forEach(imgUrl => postOutputText += `\n[img]${imgUrl}[/img]`);
218
            
219
            
220
            let quotedVideoUrlDOM = null;
221
            if (quoteEmbedElement.querySelector('video[poster]')) { /* ... video logic ... */ }
222
            if (quotedVideoUrlDOM) postOutputText += `\n[U][URL]${quotedVideoUrlDOM}[/URL][/U]`;
223
        }
224
    }
225
226
    
227
    const normalizeUrl = (url) => (url.startsWith('/') ? `https://bsky.app${url}` : url).replace(/\/+$/, "").toLowerCase();
228
    
229
    const externalLinks = new Set(
230
        Array.from(postElement.querySelectorAll("a[href]"))
231
        .filter(link => !link.closest(quoteEmbedSelector)) 
232
        .map(link => normalizeUrl(link.href))
233
        .filter(href => href.startsWith("http") && !href.includes("/post/") && !href.includes("/profile/") && !postOutputText.includes(href))
234
    );
235
    externalLinks.forEach(linkUrl => postOutputText += `\n[U][URL]${linkUrl}[/URL][/U]`);
236
    
237
    
238
    const finalAuthorString = thisPostsAuthorHandleFromTestId && thisPostsAuthorHandleFromTestId !== "Unknown"
239-
        ? `πŸ‡ΊπŸ‡Έ ${thisPostsAuthorHandleFromTestId}`
239+
        ? `πŸ†” ${thisPostsAuthorHandleFromTestId}`
240-
        : `πŸ‡ΊπŸ‡Έ ${cleanHandle(postElement.querySelector(".css-146c3p1.r-dnmrzs.r-1udh08x.r-1udbk01.r-3s2u2q.r-1iln25a")?.textContent?.trim() || "Unknown")}`;
240+
        : `πŸ†” ${cleanHandle(postElement.querySelector(".css-146c3p1.r-dnmrzs.r-1udh08x.r-1udbk01.r-3s2u2q.r-1iln25a")?.textContent?.trim() || "Unknown")}`;
241
242
    return {
243
        text: postOutputText,
244
        author: finalAuthorString,
245
        url: thisPostsUrl, 
246
        threadData: threadDataForThisPost,
247
        quotedUrl: urlOfQuotedPostByThis 
248
    };
249
  }
250
251
252
  async function extractPosts(e_count){
253
    const allPosts = Array.from(document.querySelectorAll('[data-testid^="postThreadItem-by-"], [data-testid="postThreadItem"]'));
254
    if (!allPosts.length) return void showToast("No posts found on this page.");
255
256
    const quoteEmbedSelector = '[role="link"][aria-label*="Post by"]';
257
    let activePostElement = allPosts.find(p => {
258
        const link = p.querySelector('a[href*="/post/' + window.location.href.split('/').pop().split('?')[0] + '"]');
259
        
260
        return link && !link.closest(quoteEmbedSelector);
261
    }) || allPosts.find(p => !p.closest(quoteEmbedSelector)); 
262
    if (!activePostElement && allPosts.length > 0) activePostElement = allPosts[0];
263
264
265
    const activeIndex = allPosts.indexOf(activePostElement);
266
267
    
268
    const activeData = await extractPost(activePostElement, true);
269
    const activeUrl = activeData.url; 
270
    const activeText = activeData.text;
271
    const activeAuthor = activeData.author;
272
    const activeThreadData = activeData.threadData;
273
    const activeQuotedUrl = activeData.quotedUrl; 
274
275
    console.log("extractPosts: Active Post URL for main output:", activeUrl);
276
    if (!activeUrl) {
277
        showToast("Critical error: Could not determine URL for the active post. Aborting.");
278
        return;
279
    }
280
281
282
    let mainAuthorCleanedHandle = "";
283-
    const activeAuthorMatch = activeAuthor.match(/πŸ‡ΊπŸ‡Έ (.*)/);
283+
    const activeAuthorMatch = activeAuthor.match(/πŸ†” (.*)/);
284
    if (activeAuthorMatch && activeAuthorMatch[1] !== "Unknown") {
285
        mainAuthorCleanedHandle = activeAuthorMatch[1];
286
    } else {
287
        mainAuthorCleanedHandle = cleanHandle(document.querySelector('[data-testid="profileHandle"]')?.textContent?.trim() || "");
288
    }
289
    if (!mainAuthorCleanedHandle || mainAuthorCleanedHandle === "Unknown") return void showToast("Could not determine main username.");
290
    console.log("extractPosts: Main Author Cleaned Handle:", mainAuthorCleanedHandle);
291
292
293
    let postsToFormat = [];
294
    let parentUrl = null, parentText = null, parentAuthor = null;
295
    let topLevelQuotedPostUrlForSpoiler = activeQuotedUrl; 
296
297
    
298
    const apiParentInfo = activeThreadData?.thread?.post?.reply?.parent;
299
    if (apiParentInfo?.uri) {
300
        
301
        
302
        
303
        
304
        
305
        
306
        
307
        
308
        
309
        
310
        
311
        
312
        
313
    } else if (activeIndex > 0) { /* DOM parent */ }
314
315
    
316
    postsToFormat.push({author: activeAuthor, text: activeText});
317
318
319
    
320
    const repliesElements = allPosts.slice(activeIndex + 1, activeIndex + e_count - postsToFormat.length + 1);
321
    for (const replyElement of repliesElements) {
322
        if (postsToFormat.length >= e_count) break;
323
        const replyData = await extractPost(replyElement, false); 
324
        if (replyData.text || (replyData.text && replyData.text.includes("[img]"))) {
325
            postsToFormat.push({author: replyData.author, text: replyData.text});
326
            
327
            if (replyData.url && replyData.author && replyData.author.includes(mainAuthorCleanedHandle)) {
328
                
329
            }
330
        }
331
    }
332
333
    
334
    
335
    
336
    let mainPostUrls = new Set([activeUrl]); 
337
    if (parentUrl && parentAuthor && parentAuthor.includes(mainAuthorCleanedHandle)) mainPostUrls.add(parentUrl);
338
    
339
340
341
    let outputChunks = [];
342
    let currentChunkPostsData = postsToFormat; 
343
    let totalPostsFormatted = postsToFormat.length;
344
    
345
346
    const hasThreadsInfo = (parentUrl && parentAuthor && parentAuthor.includes(mainAuthorCleanedHandle) && parentUrl !== activeUrl) ||
347
                           ([...mainPostUrls].filter(u => u !== activeUrl && u !== parentUrl).length > 0) || 
348
                           (topLevelQuotedPostUrlForSpoiler && topLevelQuotedPostUrlForSpoiler !== activeUrl && !mainPostUrls.has(topLevelQuotedPostUrlForSpoiler) && (parentUrl ? topLevelQuotedPostUrlForSpoiler !== parentUrl : true) );
349
350
    let chunkString = `${activeUrl}`; 
351
    if (hasThreadsInfo) {
352
        chunkString += `\n[SPOILER="Threads Continued"]`;
353
        if (parentUrl && parentAuthor && parentAuthor.includes(mainAuthorCleanedHandle) && parentUrl !== activeUrl) {
354
            chunkString += `\n${parentUrl}`;
355
        }
356
        [...mainPostUrls].filter(u => u !== activeUrl && u !== parentUrl).forEach(u => chunkString += `\n${u}`);
357
        if (topLevelQuotedPostUrlForSpoiler && topLevelQuotedPostUrlForSpoiler !== activeUrl && !mainPostUrls.has(topLevelQuotedPostUrlForSpoiler) && (parentUrl ? topLevelQuotedPostUrlForSpoiler !== parentUrl : true)) {
358
             chunkString += `\n${topLevelQuotedPostUrlForSpoiler}`;
359
        }
360
        chunkString += `\n[/SPOILER]`;
361
    }
362
    chunkString += `\n[SPOILER="full text & large images"]\n\n${currentChunkPostsData.map((p, idx) => `${idx + 1}/${currentChunkPostsData.length}\n${p.author}\n${p.text}`).join("\n\n")}`;
363
    chunkString += `\n\n[COLOR=rgb(184,49,47)][B][SIZE=5]To post tweets in this format, more info here: [URL]https://www.thecoli.com/threads/tips-and-tricks-for-posting-the-coli-megathread.984734/post-52211196[/URL][/SIZE][/B][/COLOR]\n[/SPOILER]`;
364
    outputChunks.push(chunkString);
365
366
    const finalOutput = outputChunks.join("\n\n[threads continued]\n\n");
367
    const d = document.createElement("textarea"); d.value = finalOutput; document.body.appendChild(d); d.select(); document.execCommand("copy"); document.body.removeChild(d); showToast(`Copied: ${totalPostsFormatted} posts`)
368
  }
369
  createPostCountPrompt(extractPosts)
370
})();