Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- javascript:(function(){
- const MAX_IMAGES_PER_POST = 10;
- function replaceDomain(e){ return e.replace(/^(https?:\/\/)([^\/]+)/,"$1bsky.app") }
- function cleanText(e){
- 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() : ""
- }
- function extractMedia(e){
- const images = Array.from(e.querySelectorAll('img[src*="cdn.bsky.app"]')).map(i=>i.src).filter(s=>!s.includes("/avatar_thumbnail/")),
- videoSources = new Set([...Array.from(e.querySelectorAll("video source[src]")).map(v=>v.src), ...Array.from(e.querySelectorAll("video[src]")).map(v=>v.src)]);
- return { images, videos: Array.from(videoSources) }
- }
- function showToast(e){
- 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),1000) },2000)
- }
- function createPostCountPrompt(cb){
- 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(d=>{ const b=document.createElement("button"); b.textContent=d; b.style.cssText="font-size:20px;padding:10px;cursor:pointer"; b.onclick=()=>n.value+=d; o.appendChild(b) });
- 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 num=parseInt(n.value,10); if(isNaN(num)||num<=0) showToast("Please enter a valid number."); else { document.body.removeChild(t); cb(num) } };
- r.append(n,p,o,s,a); t.appendChild(r); document.body.appendChild(t)
- }
- async function getEmbedUrl(threadData){
- let url = null;
- const post = threadData?.thread?.post;
- if(post?.embed?.$type==="app.bsky.embed.video#view" && post.embed.playlist){
- url = post.embed.playlist;
- console.log("Found playlist (main):", url);
- } else if(post?.record?.embed?.$type==="app.bsky.embed.video" && post.author?.did && post.record.embed.video?.ref?.$link){
- url = constructVideoUrl(post.author.did, post.record.embed.video);
- console.log("Constructed URL (main):", url);
- }
- return url;
- }
- function constructVideoUrl(did, videoEmbed){
- if(videoEmbed.mimeType === "video/webm") return `https://bsky.social/xrpc/com.atproto.sync.getBlob?did=${encodeURIComponent(did)}&cid=${encodeURIComponent(videoEmbed.ref.$link)}`;
- return `https://video.bsky.app/watch/${encodeURIComponent(did)}/${encodeURIComponent(videoEmbed.ref.$link)}/playlist.m3u8`;
- }
- function isElementVisible(el){
- if(!el || !(el instanceof Element)) return false;
- const style = getComputedStyle(el);
- if(style.display === 'none' || style.visibility === 'hidden' || parseFloat(style.opacity || "1") === 0) return false;
- const rect = el.getBoundingClientRect();
- if(rect.width === 0 || rect.height === 0) return false;
- const vertOverlap = Math.min(rect.bottom, window.innerHeight) - Math.max(rect.top, 0);
- const horizOverlap = Math.min(rect.right, window.innerWidth) - Math.max(rect.left, 0);
- return vertOverlap > 0 && horizOverlap > 0;
- }
- function visibilityScore(el){
- const rect = el.getBoundingClientRect();
- const w = Math.max(0, Math.min(rect.right, window.innerWidth) - Math.max(rect.left, 0));
- const h = Math.max(0, Math.min(rect.bottom, window.innerHeight) - Math.max(rect.top, 0));
- return w * h;
- }
- async function extractPost(el, isActivePost = false){
- if(!(el instanceof Element)) return console.error("Invalid post element:", el), { text:"", author:"", url:"", threadData:null };
- let mainHandle = el.getAttribute("data-testid")?.match(/postThreadItem-by-(.+)/)?.[1] || null;
- if(!mainHandle){
- const profileAnchor = el.querySelector('a[href^="/profile/"]');
- if(profileAnchor){
- try{
- const segs = profileAnchor.getAttribute('href').split('/').filter(Boolean);
- if(segs.length >= 2 && segs[0]==='profile') mainHandle = segs[1];
- }catch(e){}
- }
- }
- const authorNode = el.querySelector(".css-146c3p1.r-dnmrzs.r-1udh08x.r-1udbk01.r-3s2u2q.r-1iln25a"),
- authorText = authorNode?.textContent?.trim() || "Unknown",
- textNode = el.querySelector('div[data-word-wrap="1"]') || el.querySelector('[data-testid="postText"]');
- let text = textNode ? cleanText(textNode.innerHTML || textNode.textContent) : "",
- mainUrl = null;
- let postId = null;
- const postAnchors = Array.from(el.querySelectorAll('a[href*="/post/"]'));
- if (mainHandle && postAnchors.length) {
- const matchByHandle = postAnchors.find(a=>{
- try{
- const u = new URL(a.href, window.location.origin);
- const segs = u.pathname.split('/').filter(Boolean);
- const idx = segs.indexOf('post');
- if(idx !== -1 && segs[idx-1] === mainHandle) return true;
- }catch(e){}
- return false;
- });
- if(matchByHandle){
- try{
- const u = new URL(matchByHandle.href, window.location.origin);
- const segs = u.pathname.split('/').filter(Boolean);
- postId = segs[segs.indexOf('post') + 1];
- }catch(e){}
- }
- }
- if(!postId && postAnchors.length){
- for(const a of postAnchors){
- try{
- const u = new URL(a.href, window.location.origin);
- const segs = u.pathname.split('/').filter(Boolean);
- const idx = segs.indexOf('post');
- if(idx !== -1 && segs.length > idx+1){
- const quotedAncestor = a.closest('[role="link"][aria-label*="Post by"]');
- if(quotedAncestor && el.contains(quotedAncestor) && quotedAncestor !== el){
- continue;
- }
- postId = segs[idx+1];
- if(!mainHandle && segs[idx-1]) mainHandle = segs[idx-1];
- break;
- }
- }catch(e){}
- }
- }
- let threadData = null;
- if (!postId && isActivePost && mainHandle) {
- try {
- const currentCandidate = window.location.href.split('/').pop().split('?')[0];
- const apiUri = `at://${mainHandle}/app.bsky.feed.post/${currentCandidate}`;
- const apiUrl = `https://public.api.bsky.app/xrpc/app.bsky.feed.getPostThread?uri=${encodeURIComponent(apiUri)}&depth=0`;
- const resp = await fetch(apiUrl);
- if (resp.ok) {
- const json = await resp.json();
- const uri = json?.thread?.post?.uri;
- if (uri) {
- postId = uri.split('/').pop();
- threadData = json;
- console.log("Fetched postId via API fallback:", postId);
- }
- }
- } catch(err){
- console.warn("API fetch for post ID failed:", err);
- }
- }
- mainUrl = postId && mainHandle ? `https://bsky.app/profile/${mainHandle}/post/${postId}` : "";
- if (mainUrl && el.querySelector('video') && !threadData) {
- try {
- const apiUri = `at://${mainHandle}/app.bsky.feed.post/${postId || window.location.href.split('/').pop().split('?')[0]}`;
- const tUrl = `https://public.api.bsky.app/xrpc/app.bsky.feed.getPostThread?uri=${encodeURIComponent(apiUri)}&depth=1`;
- const res = await fetch(tUrl);
- threadData = res.ok ? await res.json() : threadData;
- if (threadData) console.log("API data for", mainUrl, ":", threadData);
- } catch (err) {
- console.warn("API fetch failed, skipping thread data:", err);
- }
- }
- const media = extractMedia(el);
- media.images.slice(0, MAX_IMAGES_PER_POST).forEach(i => text += `\n[img]${i}[/img]`);
- if (threadData) {
- const videoUrl = await getEmbedUrl(threadData);
- if (videoUrl) text += `\n[U][URL]${videoUrl}[/URL][/U]`;
- }
- const normalizeUrl = (url) => (url.startsWith('/') ? `https://bsky.app${url}` : url).replace(/\/+$/,"").toLowerCase();
- const externalLinks = new Set(Array.from(el.querySelectorAll("a[href]")).map(a=>normalizeUrl(a.href)).filter(u=>u.startsWith("http") && !u.includes("/post/") && !u.includes("/profile/") && !text.includes(u)));
- externalLinks.forEach(u => text += `\n[U][URL]${u}[/URL][/U]`);
- const quotedContainer = el.querySelector('[role="link"][aria-label*="Post by"]');
- let quotedUrl = null;
- if (quotedContainer && isActivePost) {
- const quotedTextHtml = quotedContainer.querySelector('div[data-word-wrap="1"]')?.innerHTML || "";
- if (quotedTextHtml) {
- const quotedText = cleanText(quotedTextHtml);
- const quotedHrefEl = quotedContainer.querySelector('a[href*="/post/"]');
- let quotedHandleFromHref = null, quotedPostIdFromHref = null;
- if (quotedHrefEl) {
- try {
- const u = new URL(quotedHrefEl.href, window.location.origin);
- const segs = u.pathname.split('/').filter(Boolean);
- const postIdx = segs.indexOf('post');
- if (postIdx !== -1 && segs.length > postIdx + 1) {
- quotedPostIdFromHref = segs[postIdx + 1];
- if (postIdx - 1 >= 0) quotedHandleFromHref = segs[postIdx - 1];
- }
- } catch(e){ /* ignore */ }
- }
- const quotedVisibleHandle = quotedContainer.querySelector(".css-146c3p1.r-dnmrzs.r-1udh08x.r-1udbk01.r-3s2u2q.r-1iln25a")?.textContent?.trim() || "Unknown";
- const finalQuotedHandle = quotedHandleFromHref || quotedVisibleHandle;
- text += `\n\n[QUOTED POST]\nšŗ ${finalQuotedHandle}\n${quotedText}`;
- let quotedVideoUrl = null;
- if (quotedPostIdFromHref) {
- quotedUrl = `https://bsky.app/profile/${finalQuotedHandle}/post/${quotedPostIdFromHref}`;
- } else {
- if (finalQuotedHandle && finalQuotedHandle !== "Unknown") {
- try {
- const qApi = `https://public.api.bsky.app/xrpc/app.bsky.feed.getPostThread?uri=${encodeURIComponent(`at://${finalQuotedHandle}/app.bsky.feed.post/${window.location.href.split('/').pop().split('?')[0]}`)}&depth=0`;
- const r = await fetch(qApi);
- const qJson = r.ok ? await r.json() : null;
- if (qJson?.thread?.post?.uri) {
- const parts = qJson.thread.post.uri.split('/');
- const handleFromUri = parts[2];
- const idFromUri = parts.pop();
- quotedUrl = `https://bsky.app/profile/${handleFromUri}/post/${idFromUri}`;
- if (qJson.thread.post.record?.embed?.$type === "app.bsky.embed.video" && qJson.thread.post.author?.did && qJson.thread.post.record.embed.video?.ref?.$link) {
- quotedVideoUrl = constructVideoUrl(qJson.thread.post.author.did, qJson.thread.post.record.embed.video);
- console.log("Quoted video URL (API):", quotedVideoUrl);
- }
- }
- } catch(e){
- console.warn("Quoted post API fetch failed:", e);
- }
- }
- }
- if (!quotedVideoUrl && quotedContainer.querySelector('video[poster]')) {
- try {
- const poster = quotedContainer.querySelector('video[poster]').getAttribute('poster') || "";
- if (poster.includes('video.bsky.app/watch/')) {
- const parts = poster.split('/');
- if (parts.length >= 6) {
- quotedVideoUrl = `https://video.bsky.app/watch/${parts[4]}/${parts[5]}/playlist.m3u8`;
- console.log("Quoted video URL (poster fallback):", quotedVideoUrl);
- }
- }
- } catch(e){/* ignore */}
- }
- if (!quotedVideoUrl && quotedContainer.querySelector('video[src^="blob:"]')) {
- quotedVideoUrl = quotedContainer.querySelector('video[src^="blob:"]').src;
- console.log("Quoted video URL (blob fallback):", quotedVideoUrl);
- }
- if (quotedVideoUrl) text += `\n[U][URL]${quotedVideoUrl}[/URL][/U]`;
- const quotedMedia = extractMedia(quotedContainer);
- quotedMedia.images.slice(0, MAX_IMAGES_PER_POST).forEach(i => text += `\n[img]${i}[/img]`);
- }
- }
- if (threadData?.thread?.post?.record?.embed?.record && isActivePost) {
- try {
- const qRecord = threadData.thread.post.record.embed.record;
- const qText = cleanText(qRecord.value?.text || "");
- if (qText) {
- const qUriParts = qRecord.uri.split('/');
- const qHandle = qUriParts[2];
- const qId = qUriParts[4];
- text += `\n\n[QUOTED POST]\nšŗ ${qHandle}\n${qText}`;
- if (qRecord.value?.embed?.$type === "app.bsky.embed.video") {
- const quotedVideoUrl = constructVideoUrl(qHandle, qRecord.value.embed.video);
- text += `\n[U][URL]${quotedVideoUrl}[/URL][/U]`;
- console.log("Quoted video URL from embed:", quotedVideoUrl);
- }
- quotedUrl = `https://bsky.app/profile/${qHandle}/post/${qId}`;
- }
- } catch(e){ console.warn("Error handling embedded quoted record:", e) }
- }
- return { text, author: mainHandle ? `šŗ ${mainHandle}` : "", url: mainUrl, threadData, quotedUrl }
- }
- async function extractPosts(count){
- const allPosts = Array.from(document.querySelectorAll('[data-testid^="postThreadItem-by-"], [data-testid="postThreadItem"]'));
- if (!allPosts.length) return void showToast("No posts found on this page.");
- let visiblePosts = allPosts.filter(isElementVisible);
- if (!visiblePosts.length) visiblePosts = allPosts;
- const currentId = window.location.href.split('/').pop().split('?')[0];
- let activePost = visiblePosts.find(p => p.querySelector('a[href*="/post/'+currentId+'"]'));
- if (!activePost) {
- let best = null, bestScore = -1;
- for (const p of visiblePosts) {
- const score = visibilityScore(p);
- if (score > bestScore) { bestScore = score; best = p; }
- }
- activePost = best || visiblePosts[0] || allPosts[0];
- }
- const visibleIndex = visiblePosts.indexOf(activePost);
- const allIndex = allPosts.indexOf(activePost);
- const { text: activeText, author: activeAuthor, url: activeUrl, threadData: activeThreadData, quotedUrl: activeQuotedUrl } = await extractPost(activePost, true);
- const r = activePost.querySelector(".css-146c3p1.r-dnmrzs.r-1udh08x.r-1udbk01.r-3s2u2q.r-1iln25a"),
- n = r?.textContent?.trim() || document.querySelector('[data-testid="profileHandle"]')?.textContent?.trim() || "";
- if (!n) return void showToast("Could not determine main username.");
- let parentUrl = null, parentText = null, parentAuthor = null, quotedPostUrl = activeQuotedUrl;
- let parentFromDomElement = null;
- if (visibleIndex > 0) parentFromDomElement = visiblePosts[visibleIndex - 1];
- if (!parentFromDomElement && allIndex > 0) parentFromDomElement = allPosts[allIndex - 1];
- if (!activeThreadData?.thread?.post?.reply?.parent && parentFromDomElement) {
- const parentExtract = await extractPost(parentFromDomElement, false);
- if (parentExtract.author === activeAuthor || parentFromDomElement.querySelector(".css-146c3p1.r-dnmrzs.r-1udh08x.r-1udbk01.r-3s2u2q.r-1iln25a")?.textContent?.trim() === n) {
- parentUrl = parentExtract.url; parentText = parentExtract.text; parentAuthor = parentExtract.author;
- }
- }
- if (activeThreadData?.thread?.post?.reply?.parent) {
- const parentUri = activeThreadData.thread.post.reply.parent.uri;
- const parentAuthorUri = parentUri.split('/')[2];
- const parentId = parentUri.split('/')[4];
- parentUrl = `https://bsky.app/profile/${parentAuthorUri}/post/${parentId}`;
- parentText = cleanText(activeThreadData.thread.post.reply.parent.value?.text || "");
- parentAuthor = `šŗ ${parentAuthorUri}`;
- const parentQuotedPost = activeThreadData.thread.post.reply.parent.embed?.record;
- if (parentQuotedPost) {
- const qText = cleanText(parentQuotedPost.value?.text || "");
- if (qText) parentText += `\n\n[QUOTED POST]\nšŗ ${parentQuotedPost.uri.split('/')[2]}\n${qText}`;
- quotedPostUrl = `https://bsky.app/profile/${parentQuotedPost.uri.split('/')[2]}/post/${parentQuotedPost.uri.split('/')[4]}`;
- }
- }
- const postsToProcessElements = [];
- const addedEls = new Set();
- function pushEl(el){
- if(!el || addedEls.has(el)) return false;
- postsToProcessElements.push(el);
- addedEls.add(el);
- return true;
- }
- if (parentFromDomElement && parentText) pushEl(parentFromDomElement);
- pushEl(activePost);
- let needed = Math.max(0, count - postsToProcessElements.length);
- let src = visiblePosts;
- let startIdx = visiblePosts.indexOf(activePost) >= 0 ? visiblePosts.indexOf(activePost) + 1 : -1;
- if (startIdx >= 0) {
- for (let i = startIdx; i < src.length && needed > 0; i++){
- if(pushEl(src[i])) needed--;
- }
- }
- if (needed > 0) {
- const srcAll = allPosts;
- let startAll = srcAll.indexOf(activePost) >= 0 ? srcAll.indexOf(activePost) + 1 : 0;
- for (let i = startAll; i < srcAll.length && needed > 0; i++){
- if(pushEl(srcAll[i])) needed--;
- }
- }
- if (needed > 0) {
- for (const p of allPosts) {
- if(needed <= 0) break;
- if(pushEl(p)) needed--;
- }
- }
- const s = parentText ? [{ author: parentAuthor, text: parentText }, { author: activeAuthor, text: activeText }] : [{ author: activeAuthor, text: activeText }];
- let aImgsCount = (parentText ? (parentText.match(/\[img\]/g)||[]).length : 0) + ((activeText.match(/\[img\]/g)||[]).length || 0);
- let generatedCount = postsToProcessElements.length;
- let mainPostUrls = new Set([activeUrl]);
- if (parentAuthor === activeAuthor && parentUrl) mainPostUrls.add(parentUrl);
- if (parentUrl && parentUrl !== activeUrl) mainPostUrls.add(parentUrl);
- const startProcessIndex = parentText ? 2 : 1;
- const outputs = [];
- for (let idx = startProcessIndex; idx < postsToProcessElements.length; idx++){
- const postEl = postsToProcessElements[idx];
- const { text: pText, author: pAuthor, url: pUrl, threadData: pThread } = await extractPost(postEl, false);
- if (!pText && !pUrl && !extractMedia(postEl).images.length) {
- generatedCount--;
- continue;
- }
- if (pUrl && pAuthor === `šŗ ${n}` && pUrl !== activeUrl && pUrl !== parentUrl) mainPostUrls.add(pUrl);
- s.push({ author: pAuthor, text: pText });
- aImgsCount += (pText.match(/\[img\]/g) || []).length;
- if (aImgsCount >= 20 || idx === postsToProcessElements.length - 1 || s.length >= count) {
- const hasThreads = (parentUrl && parentUrl !== activeUrl) || [...mainPostUrls].some(u=>u !== activeUrl) || (quotedPostUrl && quotedPostUrl !== activeUrl);
- outputs.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 text & large images"]\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 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]`);
- if (parentText) {
- s.length = 0;
- s.push({ author: parentAuthor, text: parentText }, { author: activeAuthor, text: activeText });
- } else {
- s.length = 0;
- s.push({ author: activeAuthor, text: activeText });
- }
- aImgsCount = (parentText ? (parentText.match(/\[img\]/g)||[]).length : 0) + ((activeText.match(/\[img\]/g)||[]).length || 0);
- }
- }
- if (s.length && !outputs.length) {
- const hasThreads = (parentUrl && parentUrl !== activeUrl) || [...mainPostUrls].some(u=>u !== activeUrl) || (quotedPostUrl && quotedPostUrl !== activeUrl);
- outputs.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 text & large images"]\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 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]`);
- }
- const finalOutput = outputs.join("\n\n[threads continued]\n\n");
- const ta = document.createElement("textarea");
- ta.value = finalOutput;
- document.body.appendChild(ta);
- ta.select();
- document.execCommand("copy");
- document.body.removeChild(ta);
- showToast(`Copied: ${Math.max(1, postsToProcessElements.length)} posts`);
- }
- createPostCountPrompt(extractPosts);
- })();
Advertisement
Add Comment
Please, Sign In to add comment