Guest User

Untitled

a guest
Mar 24th, 2025
28
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.  
  4.   function replaceDomain(e){return e.replace(/^(https?:\/\/)([^\/]+)/,"$1bsky.app")}
  5.   function cleanText(e){
  6.     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():""
  7.   }
  8.   function extractMedia(e){
  9.     const images=Array.from(e.querySelectorAll('img[src*="cdn.bsky.app"]')).map(e=>e.src).filter(e=>!e.includes("/avatar_thumbnail/")),
  10.           videoSources=new Set([...Array.from(e.querySelectorAll("video source[src]")).map(e=>e.src),...Array.from(e.querySelectorAll("video[src]")).map(e=>e.src)]);
  11.     return{images,videos:Array.from(videoSources)}
  12.   }
  13.   function showToast(e){
  14.     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%201s;box-shadow:0%202px%205px%20rgba(0,0,0,0.3)";t.innerText=e;document.body.appendChild(t);setTimeout(()=>{t.style.opacity="0";setTimeout(()=>document.body.removeChild(t),1e3)},2e3)
  15.   }
  16.   function createPostCountPrompt(e){
  17.     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%200%2010px%20rgba(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%2020px;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%2020px;cursor:pointer";a.onclick=()=>{const o=parseInt(n.value,10);isNaN(o)||o<=0?showToast("Please%20enter%20a%20valid%20number."):(document.body.removeChild(t),e(o))};r.append(n,p,o,s,a);t.appendChild(r);document.body.appendChild(t)
  18.   }
  19.   async function getEmbedUrl(threadData){
  20.     let url=null;const post=threadData?.thread?.post;
  21.     if(post?.embed?.$type==="app.bsky.embed.video#view"&&post.embed.playlist){url=post.embed.playlist;console.log("Found playlist (main):",url)}
  22.     else if(post?.record?.embed?.$type==="app.bsky.embed.video"&&post.author.did&&post.record.embed.video.ref?.$link){
  23.       url=constructVideoUrl(post.author.did,post.record.embed.video);console.log("Constructed URL (main):",url)}
  24.     return url
  25.   }
  26.   function constructVideoUrl(did,videoEmbed){
  27.     if(videoEmbed.mimeType==="video/webm")return`https://bsky.social/xrpc/com.atproto.sync.getBlob?did=${encodeURIComponent(did)}&cid=${encodeURIComponent(videoEmbed.ref.$link)}`;
  28.     return`https://video.bsky.app/watch/${encodeURIComponent(did)}/${encodeURIComponent(videoEmbed.ref.$link)}/playlist.m3u8`
  29.   }
  30.   async function extractPost(e,isActivePost=false){
  31.     if(!(e instanceof Element))return console.error("Invalid%20post%20element:",e),{text:"",author:"",url:"",threadData:null};
  32.     const t=e.querySelector(".css-146c3p1.r-dnmrzs.r-1udh08x.r-1udbk01.r-3s2u2q.r-1iln25a"),r=t?.textContent?.trim()||"Unknown",
  33.           n=e.querySelector('div[data-word-wrap="1"]')||e.querySelector('[data-testid="postText"]');
  34.     let o=n?cleanText(n.innerHTML||n.textContent):"",s=e.getAttribute("data-testid")?.match(/postThreadItem-by-(.+)/)?.[1],a=null;
  35.     if(s){
  36.       let postId=e.querySelector('[data-testid="postTimestamp"] a[href*="/post/"]')?.href.split('/').pop().split('?')[0]||
  37.                 e.querySelector('a[href*="/post/"]')?.href.split('/').pop().split('?')[0]||
  38.                 e.closest('[data-testid^="postThreadItem-by-"]')?.querySelector('a[href*="/post/"]')?.href.split('/').pop().split('?')[0];
  39.       a=postId?`https://bsky.app/profile/${s}/post/${postId}`:"";
  40.       if(!postId&&isActivePost){
  41.         const apiUri=`at://${s}/app.bsky.feed.post/${postId||window.location.href.split('/').pop().split('?')[0]}`,
  42.               t=`https://public.api.bsky.app/xrpc/app.bsky.feed.getPostThread?uri=${encodeURIComponent(apiUri)}&depth=1`;
  43.         try{const e=await fetch(t);const i=e.ok?await e.json():null;if(i)postId=i.thread.post.uri.split('/').pop(),a=`https://bsky.app/profile/${s}/post/${postId}`;}catch(e){console.warn("API%20fetch%20for%20post%20ID%20failed:",e);}
  44.       }
  45.     }
  46.     let i=null;
  47.     if(a&&e.querySelector('video')){
  48.       const e=`at://${s}/app.bsky.feed.post/${a.split('/').pop()}`,
  49.             t=`https://public.api.bsky.app/xrpc/app.bsky.feed.getPostThread?uri=${encodeURIComponent(e)}&depth=1`;
  50.       try{const e=await fetch(t);i=e.ok?await e.json():null;if(i)console.log("API%20data%20for",a,":",i);}catch(e){console.warn("API%20fetch%20failed,%20skipping%20thread%20data:",e);}
  51.     }
  52.     const l=extractMedia(e);l.images.slice(0,MAX_IMAGES_PER_POST).forEach(e=>o+=`\n[img]${e}[/img]`);
  53.     if(i){
  54.       const videoUrl=await getEmbedUrl(i);if(videoUrl)o+=`\n[U][URL]${videoUrl}[/URL][/U]`;
  55.     }
  56.     const normalizeUrl=(url)=>(url.startsWith('/')?`https://bsky.app${url}`:url).replace(/\/+$/,"").toLowerCase();
  57.     const c=new Set(Array.from(e.querySelectorAll("a[href]")).map(e=>normalizeUrl(e.href)).filter(e=>e.startsWith("http")&&!e.includes("/post/")&&!e.includes("/profile/")&&!o.includes(e)));
  58.     c.forEach(e=>o+=`\n[U][URL]${e}[/URL][/U]`);
  59.     const d=e.querySelector('[role="link"][aria-label*="Post%20by"]');let quotedUrl=null;
  60.     if(d&&isActivePost){
  61.       const t=d.querySelector('div[data-word-wrap="1"]')?.innerHTML||"";
  62.       if(t){
  63.         const quotedText=cleanText(t),quotedHandle=d.querySelector(".css-146c3p1.r-dnmrzs.r-1udh08x.r-1udbk01.r-3s2u2q.r-1iln25a")?.textContent?.trim()||"Unknown",
  64.               quotedPostId=d.querySelector('a[href*="/post/"]')?.href.split('/').pop().split('?')[0];
  65.         if(quotedText){
  66.           o+=`\n\n[QUOTED%20POST]\n%F0%9F%87%BA%20${quotedHandle}\n${quotedText}`;
  67.           let quotedVideoUrl=null;
  68.           if(quotedPostId){
  69.             const quotedUri=`at://${quotedHandle}/app.bsky.feed.post/${quotedPostId}`,
  70.                   quotedApi=`https://public.api.bsky.app/xrpc/app.bsky.feed.getPostThread?uri=${encodeURIComponent(quotedUri)}&depth=0`;
  71.             try{
  72.               const res=await fetch(quotedApi);const quotedData=res.ok?await res.json():null;
  73.               if(quotedData?.thread?.post?.record?.embed?.$type==="app.bsky.embed.video"&&quotedData?.thread?.post?.author?.did&&quotedData?.thread?.post?.record?.embed?.video?.ref?.$link){
  74.                 quotedVideoUrl=constructVideoUrl(quotedData.thread.post.author.did,quotedData.thread.post.record.embed.video);
  75.                 console.log("Quoted video URL (API):",quotedVideoUrl);
  76.               }
  77.             }catch(e){console.warn("Quoted post API fetch failed:",e);}
  78.           }
  79.           if(!quotedVideoUrl&&d.querySelector('video[poster]')){
  80.             const poster=d.querySelector('video[poster]').getAttribute('poster');
  81.             if(poster.includes('video.bsky.app/watch/')){
  82.               const parts=poster.split('/');quotedVideoUrl=`https://video.bsky.app/watch/${parts[4]}/${parts[5]}/playlist.m3u8`;
  83.               console.log("Quoted video URL (poster fallback):",quotedVideoUrl);
  84.             }
  85.           }
  86.           if(!quotedVideoUrl&&d.querySelector('video[src^="blob:"]')){
  87.             quotedVideoUrl=d.querySelector('video[src^="blob:"]').src;
  88.             console.log("Quoted video URL (blob fallback):",quotedVideoUrl);
  89.           }
  90.           if(quotedVideoUrl)o+=`\n[U][URL]${quotedVideoUrl}[/URL][/U]`;
  91.         }
  92.         quotedUrl=quotedPostId?`https://bsky.app/profile/${quotedHandle}/post/${quotedPostId}`:"";
  93.         const r=extractMedia(d);r.images.slice(0,MAX_IMAGES_PER_POST).forEach(e=>o+=`\n[img]${e}[/img]`);
  94.       }
  95.     }
  96.     if(i?.thread?.post?.record?.embed?.record&&isActivePost){
  97.       const qText=cleanText(i.thread.post.record.embed.record.value?.text||"");
  98.       if(qText){
  99.         o+=`\n\n[QUOTED%20POST]\n%F0%9F%87%BA%20${i.thread.post.record.embed.record.uri.split('/')[2]}\n${qText}`;
  100.         if(i.thread.post.record.embed.record.value?.embed?.$type==="app.bsky.embed.video"){
  101.           const quotedVideoUrl=constructVideoUrl(i.thread.post.record.embed.record.uri.split('/')[2],i.thread.post.record.embed.record.value.embed.video);
  102.           o+=`\n[U][URL]${quotedVideoUrl}[/URL][/U]`;console.log("Quoted video URL from embed:",quotedVideoUrl);
  103.         }
  104.       }
  105.       quotedUrl=`https://bsky.app/profile/${i.thread.post.record.embed.record.uri.split('/')[2]}/post/${i.thread.post.record.embed.record.uri.split('/')[4]}`;
  106.     }
  107.     return{text:o,author:s?`%F0%9F%87%BA%20${s}`:"",url:a,threadData:i,quotedUrl}
  108.   }
  109.   async function extractPosts(e){
  110.     const allPosts=Array.from(document.querySelectorAll('[data-testid^="postThreadItem-by-"],%20[data-testid="postThreadItem"]'));
  111.     if(!allPosts.length)return void showToast("No%20posts%20found%20on%20this%20page.");
  112.     let activePost=allPosts.find(p=>p.querySelector('a[href*="/post/'+window.location.href.split('/').pop().split('?')[0]+'"]'))||allPosts[0];
  113.     const activeIndex=allPosts.indexOf(activePost);
  114.     const{text:activeText,author:activeAuthor,url:activeUrl,threadData:activeThreadData,quotedUrl:activeQuotedUrl}=await extractPost(activePost,true);
  115.     const r=activePost.querySelector(".css-146c3p1.r-dnmrzs.r-1udh08x.r-1udbk01.r-3s2u2q.r-1iln25a"),
  116.           n=r?.textContent?.trim()||document.querySelector('[data-testid="profileHandle"]')?.textContent?.trim()||"";
  117.     if(!n)return void showToast("Could%20not%20determine%20main%20username.");
  118.     let t=[activePost],parentUrl=null,parentText=null,parentAuthor=null,quotedPostUrl=activeQuotedUrl;
  119.     let parentFromDom=activeIndex>0?allPosts[activeIndex-1]:null;
  120.     if(!activeThreadData?.thread?.post?.reply?.parent&&parentFromDom){
  121.       const{text,author,url}=await extractPost(parentFromDom,false);
  122.       if(author===activeAuthor||parentFromDom.querySelector(".css-146c3p1.r-dnmrzs.r-1udh08x.r-1udbk01.r-3s2u2q.r-1iln25a")?.textContent?.trim()===n){
  123.         parentUrl=url;parentText=text;parentAuthor=author;
  124.       }
  125.     }
  126.     if(activeThreadData?.thread?.post?.reply?.parent){
  127.       const parentUri=activeThreadData.thread.post.reply.parent.uri;const parentAuthorUri=parentUri.split('/')[2];const parentId=parentUri.split('/')[4];
  128.       parentUrl=`https://bsky.app/profile/${parentAuthorUri}/post/${parentId}`;parentText=cleanText(activeThreadData.thread.post.reply.parent.value?.text||"");parentAuthor=`%F0%9F%87%BA%20${parentAuthorUri}`;
  129.       const parentQuotedPost=activeThreadData.thread.post.reply.parent.embed?.record;
  130.       if(parentQuotedPost){
  131.         const qText=cleanText(parentQuotedPost.value?.text||"");if(qText)parentText+=`\n\n[QUOTED%20POST]\n%F0%9F%87%BA%20${parentQuotedPost.uri.split('/')[2]}\n${qText}`;
  132.         quotedPostUrl=`https://bsky.app/profile/${parentQuotedPost.uri.split('/')[2]}/post/${parentQuotedPost.uri.split('/')[4]}`;
  133.       }
  134.       t=[{text:parentText,author:parentAuthor,url:parentUrl},...t];
  135.     }
  136.     t=[...t,...allPosts.slice(activeIndex+1,activeIndex+e)];if(t.length>e)t=t.slice(0,e);
  137.     let mainPostUrls=new Set([activeUrl]);if(parentAuthor===activeAuthor&&parentUrl)mainPostUrls.add(parentUrl);
  138.     let s=parentText?[{author:parentAuthor,text:parentText},{author:activeAuthor,text:activeText}]:[{author:activeAuthor,text:activeText}],a=0,i=parentText?2:1,l=[];
  139.     for(const[r,c]of t.entries()){
  140.       if(r===0)continue;
  141.       const{text:d,author:p,url:u,threadData}=await extractPost(c,false);
  142.       if(!d&&!u&&!extractMedia(c).images.length)continue;
  143.       if(u&&p===`%F0%9F%87%BA%20${n}`&&u!==activeUrl&&u!==parentUrl)mainPostUrls.add(u);
  144.       s.push({author:p,text:d});a+=(d.match(/\[img\]/g)||[]).length;i++;
  145.       if(a>=20||r===t.length-1||r===e-1){
  146.         const hasThreads=parentUrl&&parentUrl!==activeUrl||[...mainPostUrls].some(u=>u!==activeUrl)||quotedPostUrl&&quotedPostUrl!==activeUrl;
  147.         l.push(`${activeUrl||replaceDomain(window.location.href.split("?")[0])}${hasThreads?`\n[SPOILER="Threads Continued"]${parentUrl&&parentUrl!==activeUrl?`\n${parentUrl}`:""}${[...mainPostUrls].filter(u=>u!==activeUrl).length>0?`\n${[...mainPostUrls].filter(u=>u!==activeUrl).join("\n")}`:""}${quotedPostUrl&&quotedPostUrl!==activeUrl?`\n${quotedPostUrl}`:""}\n[/SPOILER]`:""}\n[SPOILER="full%20text%20&%20large%20images"]\n\n${s.map((e,t)=>`${t+1}/${s.length}\n${e.author}\n${e.text}`).join("\n\n")}\n\n[COLOR=rgb(184,49,47)][B][SIZE=5]To%20post%20tweets%20in%20this%20format,%20more%20info%20here:%20[URL]https://www.thecoli.com/threads/tips-and-tricks-for-posting-the-coli-megathread.984734/post-52211196[/URL][/SIZE][/B][/COLOR]\n[/SPOILER]`);
  148.         s=parentText?[{author:parentAuthor,text:parentText},{author:activeAuthor,text:activeText}]:[{author:activeAuthor,text:activeText}];a=0;
  149.       }
  150.     }
  151.     if(s.length&&!l.length){
  152.       const hasThreads=parentUrl&&parentUrl!==activeUrl||[...mainPostUrls].some(u=>u!==activeUrl)||quotedPostUrl&&quotedPostUrl!==activeUrl;
  153.       l.push(`${activeUrl||replaceDomain(window.location.href.split("?")[0])}${hasThreads?`\n[SPOILER="Threads Continued"]${parentUrl&&parentUrl!==activeUrl?`\n${parentUrl}`:""}${[...mainPostUrls].filter(u=>u!==activeUrl).length>0?`\n${[...mainPostUrls].filter(u=>u!==activeUrl).join("\n")}`:""}${quotedPostUrl&&quotedPostUrl!==activeUrl?`\n${quotedPostUrl}`:""}\n[/SPOILER]`:""}\n[SPOILER="full%20text%20&%20large%20images"]\n\n${s.map((e,t)=>`${t+1}/${s.length}\n${e.author}\n${e.text}`).join("\n\n")}\n\n[COLOR=rgb(184,49,47)][B][SIZE=5]To%20post%20tweets%20in%20this%20format,%20more%20info%20here:%20[URL]https://www.thecoli.com/threads/tips-and-tricks-for-posting-the-coli-megathread.984734/post-52211196[/URL][/SIZE][/B][/COLOR]\n[/SPOILER]`);
  154.     }
  155.     const c=l.join("\n\n[threads%20continued]\n\n"),d=document.createElement("textarea");d.value=c;document.body.appendChild(d);d.select();document.execCommand("copy");document.body.removeChild(d);showToast(`Copied:%20${i}%20posts`)
  156.   }
  157.   createPostCountPrompt(extractPosts)
  158. })();
Advertisement
Add Comment
Please, Sign In to add comment