Advertisement
Guest User

Untitled

a guest
May 17th, 2025
22
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  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}`;
  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}`;
  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}`
  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(/šŸ†” (.*)/);
  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. })();
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement