SHOW:
|
|
- or go back to the newest paste.
| 1 | javascript:(function(){
| |
| 2 | const MAX_IMAGES_PER_POST = 10; | |
| 3 | ||
| 4 | function replaceDomain(e){ return e.replace(/^(https?:\/\/)([^\/]+)/,"$1bsky.app") }
| |
| 5 | ||
| 6 | function cleanText(e){
| |
| 7 | 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]`))
| |
| 8 | .replace(/<br\s*\/?>/g,"\n") | |
| 9 | .replace(/<[^>]+>/g,"") | |
| 10 | .trim() : "" | |
| 11 | } | |
| 12 | ||
| 13 | function extractMedia(e){
| |
| 14 | const images = Array.from(e.querySelectorAll('img[src*="cdn.bsky.app"]')).map(i=>i.src).filter(s=>!s.includes("/avatar_thumbnail/")),
| |
| 15 | videoSources = new Set([...Array.from(e.querySelectorAll("video source[src]")).map(v=>v.src), ...Array.from(e.querySelectorAll("video[src]")).map(v=>v.src)]);
| |
| 16 | return { images, videos: Array.from(videoSources) }
| |
| 17 | } | |
| 18 | ||
| 19 | function showToast(e){
| |
| 20 | const t=document.createElement("div");
| |
| 21 | 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)"; | |
| 22 | t.innerText=e; document.body.appendChild(t); | |
| 23 | setTimeout(()=>{ t.style.opacity="0"; setTimeout(()=>document.body.removeChild(t),1000) },2000)
| |
| 24 | } | |
| 25 | ||
| 26 | function createPostCountPrompt(cb){
| |
| 27 | const t=document.createElement("div");
| |
| 28 | 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"; | |
| 29 | const r=document.createElement("div");
| |
| 30 | r.style.cssText="background:white;padding:20px;border-radius:8px;box-shadow:0 0 10px rgba(0,0,0,0.3);text-align:center"; | |
| 31 | 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";
| |
| 32 | const o=document.createElement("div"); o.style.cssText="display:grid;grid-template-columns:repeat(3,1fr);gap:10px";
| |
| 33 | "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) });
| |
| 34 | const p=document.createElement("div"); p.style.cssText="display:flex;gap:10px;margin-bottom:20px";
| |
| 35 | [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) });
| |
| 36 | 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="";
| |
| 37 | 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";
| |
| 38 | 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) } };
| |
| 39 | r.append(n,p,o,s,a); t.appendChild(r); document.body.appendChild(t) | |
| 40 | } | |
| 41 | ||
| 42 | async function getEmbedUrl(threadData){
| |
| 43 | let url = null; | |
| 44 | const post = threadData?.thread?.post; | |
| 45 | if(post?.embed?.$type==="app.bsky.embed.video#view" && post.embed.playlist){
| |
| 46 | url = post.embed.playlist; | |
| 47 | console.log("Found playlist (main):", url);
| |
| 48 | } else if(post?.record?.embed?.$type==="app.bsky.embed.video" && post.author?.did && post.record.embed.video?.ref?.$link){
| |
| 49 | url = constructVideoUrl(post.author.did, post.record.embed.video); | |
| 50 | console.log("Constructed URL (main):", url);
| |
| 51 | } | |
| 52 | return url; | |
| 53 | } | |
| 54 | ||
| 55 | function constructVideoUrl(did, videoEmbed){
| |
| 56 | if(videoEmbed.mimeType === "video/webm") return `https://bsky.social/xrpc/com.atproto.sync.getBlob?did=${encodeURIComponent(did)}&cid=${encodeURIComponent(videoEmbed.ref.$link)}`;
| |
| 57 | return `https://video.bsky.app/watch/${encodeURIComponent(did)}/${encodeURIComponent(videoEmbed.ref.$link)}/playlist.m3u8`;
| |
| 58 | } | |
| 59 | ||
| 60 | ||
| 61 | function isElementVisible(el){
| |
| 62 | if(!el || !(el instanceof Element)) return false; | |
| 63 | const style = getComputedStyle(el); | |
| 64 | if(style.display === 'none' || style.visibility === 'hidden' || parseFloat(style.opacity || "1") === 0) return false; | |
| 65 | const rect = el.getBoundingClientRect(); | |
| 66 | if(rect.width === 0 || rect.height === 0) return false; | |
| 67 | const vertOverlap = Math.min(rect.bottom, window.innerHeight) - Math.max(rect.top, 0); | |
| 68 | const horizOverlap = Math.min(rect.right, window.innerWidth) - Math.max(rect.left, 0); | |
| 69 | return vertOverlap > 0 && horizOverlap > 0; | |
| 70 | } | |
| 71 | ||
| 72 | function visibilityScore(el){
| |
| 73 | const rect = el.getBoundingClientRect(); | |
| 74 | const w = Math.max(0, Math.min(rect.right, window.innerWidth) - Math.max(rect.left, 0)); | |
| 75 | const h = Math.max(0, Math.min(rect.bottom, window.innerHeight) - Math.max(rect.top, 0)); | |
| 76 | return w * h; | |
| 77 | } | |
| 78 | ||
| 79 | async function extractPost(el, isActivePost = false){
| |
| 80 | if(!(el instanceof Element)) return console.error("Invalid post element:", el), { text:"", author:"", url:"", threadData:null };
| |
| 81 | ||
| 82 | ||
| 83 | let mainHandle = el.getAttribute("data-testid")?.match(/postThreadItem-by-(.+)/)?.[1] || null;
| |
| 84 | if(!mainHandle){
| |
| 85 | const profileAnchor = el.querySelector('a[href^="/profile/"]');
| |
| 86 | if(profileAnchor){
| |
| 87 | - | mainHandle = el.getAttribute("data-testid")?.match(/postThreadItem-by-(.+)/)?.[1] || null,
|
| 87 | + | try{
|
| 88 | const segs = profileAnchor.getAttribute('href').split('/').filter(Boolean);
| |
| 89 | if(segs.length >= 2 && segs[0]==='profile') mainHandle = segs[1]; | |
| 90 | }catch(e){}
| |
| 91 | } | |
| 92 | - | const hrefEl = el.querySelector('a[href*="/post/"]');
|
| 92 | + | |
| 93 | - | if (hrefEl) {
|
| 93 | + | |
| 94 | - | const href = hrefEl.href; |
| 94 | + | |
| 95 | authorText = authorNode?.textContent?.trim() || "Unknown", | |
| 96 | - | const parts = new URL(href); |
| 96 | + | |
| 97 | - | const segs = parts.pathname.split('/').filter(Boolean);
|
| 97 | + | |
| 98 | - | const postIndex = segs.indexOf('post');
|
| 98 | + | |
| 99 | - | if (postIndex !== -1 && segs.length > postIndex + 1) {
|
| 99 | + | |
| 100 | - | postId = segs[postIndex + 1]; |
| 100 | + | |
| 101 | - | if(!mainHandle && segs[postIndex - 1]) mainHandle = segs[postIndex - 1]; |
| 101 | + | |
| 102 | let postId = null; | |
| 103 | - | } catch(err){ /* ignore */ }
|
| 103 | + | const postAnchors = Array.from(el.querySelectorAll('a[href*="/post/"]'));
|
| 104 | ||
| 105 | if (mainHandle && postAnchors.length) {
| |
| 106 | ||
| 107 | const matchByHandle = postAnchors.find(a=>{
| |
| 108 | try{
| |
| 109 | const u = new URL(a.href, window.location.origin); | |
| 110 | const segs = u.pathname.split('/').filter(Boolean);
| |
| 111 | const idx = segs.indexOf('post');
| |
| 112 | if(idx !== -1 && segs[idx-1] === mainHandle) return true; | |
| 113 | }catch(e){}
| |
| 114 | return false; | |
| 115 | }); | |
| 116 | if(matchByHandle){
| |
| 117 | try{
| |
| 118 | const u = new URL(matchByHandle.href, window.location.origin); | |
| 119 | const segs = u.pathname.split('/').filter(Boolean);
| |
| 120 | postId = segs[segs.indexOf('post') + 1];
| |
| 121 | }catch(e){}
| |
| 122 | } | |
| 123 | } | |
| 124 | ||
| 125 | ||
| 126 | if(!postId && postAnchors.length){
| |
| 127 | ||
| 128 | for(const a of postAnchors){
| |
| 129 | try{
| |
| 130 | const u = new URL(a.href, window.location.origin); | |
| 131 | const segs = u.pathname.split('/').filter(Boolean);
| |
| 132 | const idx = segs.indexOf('post');
| |
| 133 | if(idx !== -1 && segs.length > idx+1){
| |
| 134 | ||
| 135 | const quotedAncestor = a.closest('[role="link"][aria-label*="Post by"]');
| |
| 136 | if(quotedAncestor && el.contains(quotedAncestor) && quotedAncestor !== el){
| |
| 137 | ||
| 138 | continue; | |
| 139 | } | |
| 140 | postId = segs[idx+1]; | |
| 141 | if(!mainHandle && segs[idx-1]) mainHandle = segs[idx-1]; | |
| 142 | break; | |
| 143 | } | |
| 144 | }catch(e){}
| |
| 145 | } | |
| 146 | } | |
| 147 | ||
| 148 | ||
| 149 | let threadData = null; | |
| 150 | if (!postId && isActivePost && mainHandle) {
| |
| 151 | try {
| |
| 152 | const currentCandidate = window.location.href.split('/').pop().split('?')[0];
| |
| 153 | const apiUri = `at://${mainHandle}/app.bsky.feed.post/${currentCandidate}`;
| |
| 154 | const apiUrl = `https://public.api.bsky.app/xrpc/app.bsky.feed.getPostThread?uri=${encodeURIComponent(apiUri)}&depth=0`;
| |
| 155 | const resp = await fetch(apiUrl); | |
| 156 | if (resp.ok) {
| |
| 157 | const json = await resp.json(); | |
| 158 | const uri = json?.thread?.post?.uri; | |
| 159 | if (uri) {
| |
| 160 | postId = uri.split('/').pop();
| |
| 161 | threadData = json; | |
| 162 | console.log("Fetched postId via API fallback:", postId);
| |
| 163 | } | |
| 164 | } | |
| 165 | - | const u = new URL(quotedHrefEl.href); |
| 165 | + | |
| 166 | console.warn("API fetch for post ID failed:", err);
| |
| 167 | } | |
| 168 | } | |
| 169 | ||
| 170 | mainUrl = postId && mainHandle ? `https://bsky.app/profile/${mainHandle}/post/${postId}` : "";
| |
| 171 | ||
| 172 | ||
| 173 | if (mainUrl && el.querySelector('video') && !threadData) {
| |
| 174 | try {
| |
| 175 | const apiUri = `at://${mainHandle}/app.bsky.feed.post/${postId || window.location.href.split('/').pop().split('?')[0]}`;
| |
| 176 | const tUrl = `https://public.api.bsky.app/xrpc/app.bsky.feed.getPostThread?uri=${encodeURIComponent(apiUri)}&depth=1`;
| |
| 177 | const res = await fetch(tUrl); | |
| 178 | threadData = res.ok ? await res.json() : threadData; | |
| 179 | if (threadData) console.log("API data for", mainUrl, ":", threadData);
| |
| 180 | } catch (err) {
| |
| 181 | console.warn("API fetch failed, skipping thread data:", err);
| |
| 182 | } | |
| 183 | } | |
| 184 | ||
| 185 | ||
| 186 | const media = extractMedia(el); | |
| 187 | media.images.slice(0, MAX_IMAGES_PER_POST).forEach(i => text += `\n[img]${i}[/img]`);
| |
| 188 | if (threadData) {
| |
| 189 | const videoUrl = await getEmbedUrl(threadData); | |
| 190 | if (videoUrl) text += `\n[U][URL]${videoUrl}[/URL][/U]`;
| |
| 191 | } | |
| 192 | ||
| 193 | const normalizeUrl = (url) => (url.startsWith('/') ? `https://bsky.app${url}` : url).replace(/\/+$/,"").toLowerCase();
| |
| 194 | ||
| 195 | 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)));
| |
| 196 | externalLinks.forEach(u => text += `\n[U][URL]${u}[/URL][/U]`);
| |
| 197 | ||
| 198 | ||
| 199 | const quotedContainer = el.querySelector('[role="link"][aria-label*="Post by"]');
| |
| 200 | let quotedUrl = null; | |
| 201 | if (quotedContainer && isActivePost) {
| |
| 202 | const quotedTextHtml = quotedContainer.querySelector('div[data-word-wrap="1"]')?.innerHTML || "";
| |
| 203 | if (quotedTextHtml) {
| |
| 204 | const quotedText = cleanText(quotedTextHtml); | |
| 205 | const quotedHrefEl = quotedContainer.querySelector('a[href*="/post/"]');
| |
| 206 | let quotedHandleFromHref = null, quotedPostIdFromHref = null; | |
| 207 | if (quotedHrefEl) {
| |
| 208 | try {
| |
| 209 | const u = new URL(quotedHrefEl.href, window.location.origin); | |
| 210 | const segs = u.pathname.split('/').filter(Boolean);
| |
| 211 | const postIdx = segs.indexOf('post');
| |
| 212 | if (postIdx !== -1 && segs.length > postIdx + 1) {
| |
| 213 | quotedPostIdFromHref = segs[postIdx + 1]; | |
| 214 | if (postIdx - 1 >= 0) quotedHandleFromHref = segs[postIdx - 1]; | |
| 215 | } | |
| 216 | } catch(e){ /* ignore */ }
| |
| 217 | } | |
| 218 | const quotedVisibleHandle = quotedContainer.querySelector(".css-146c3p1.r-dnmrzs.r-1udh08x.r-1udbk01.r-3s2u2q.r-1iln25a")?.textContent?.trim() || "Unknown";
| |
| 219 | const finalQuotedHandle = quotedHandleFromHref || quotedVisibleHandle; | |
| 220 | ||
| 221 | text += `\n\n[QUOTED POST]\nπΊ ${finalQuotedHandle}\n${quotedText}`;
| |
| 222 | ||
| 223 | let quotedVideoUrl = null; | |
| 224 | if (quotedPostIdFromHref) {
| |
| 225 | quotedUrl = `https://bsky.app/profile/${finalQuotedHandle}/post/${quotedPostIdFromHref}`;
| |
| 226 | } else {
| |
| 227 | if (finalQuotedHandle && finalQuotedHandle !== "Unknown") {
| |
| 228 | try {
| |
| 229 | 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`;
| |
| 230 | const r = await fetch(qApi); | |
| 231 | const qJson = r.ok ? await r.json() : null; | |
| 232 | if (qJson?.thread?.post?.uri) {
| |
| 233 | const parts = qJson.thread.post.uri.split('/');
| |
| 234 | const handleFromUri = parts[2]; | |
| 235 | const idFromUri = parts.pop(); | |
| 236 | quotedUrl = `https://bsky.app/profile/${handleFromUri}/post/${idFromUri}`;
| |
| 237 | if (qJson.thread.post.record?.embed?.$type === "app.bsky.embed.video" && qJson.thread.post.author?.did && qJson.thread.post.record.embed.video?.ref?.$link) {
| |
| 238 | quotedVideoUrl = constructVideoUrl(qJson.thread.post.author.did, qJson.thread.post.record.embed.video); | |
| 239 | console.log("Quoted video URL (API):", quotedVideoUrl);
| |
| 240 | } | |
| 241 | } | |
| 242 | } catch(e){
| |
| 243 | console.warn("Quoted post API fetch failed:", e);
| |
| 244 | } | |
| 245 | } | |
| 246 | } | |
| 247 | ||
| 248 | if (!quotedVideoUrl && quotedContainer.querySelector('video[poster]')) {
| |
| 249 | try {
| |
| 250 | const poster = quotedContainer.querySelector('video[poster]').getAttribute('poster') || "";
| |
| 251 | if (poster.includes('video.bsky.app/watch/')) {
| |
| 252 | const parts = poster.split('/');
| |
| 253 | if (parts.length >= 6) {
| |
| 254 | quotedVideoUrl = `https://video.bsky.app/watch/${parts[4]}/${parts[5]}/playlist.m3u8`;
| |
| 255 | console.log("Quoted video URL (poster fallback):", quotedVideoUrl);
| |
| 256 | } | |
| 257 | } | |
| 258 | } catch(e){/* ignore */}
| |
| 259 | } | |
| 260 | ||
| 261 | if (!quotedVideoUrl && quotedContainer.querySelector('video[src^="blob:"]')) {
| |
| 262 | quotedVideoUrl = quotedContainer.querySelector('video[src^="blob:"]').src;
| |
| 263 | console.log("Quoted video URL (blob fallback):", quotedVideoUrl);
| |
| 264 | } | |
| 265 | ||
| 266 | if (quotedVideoUrl) text += `\n[U][URL]${quotedVideoUrl}[/URL][/U]`;
| |
| 267 | const quotedMedia = extractMedia(quotedContainer); | |
| 268 | quotedMedia.images.slice(0, MAX_IMAGES_PER_POST).forEach(i => text += `\n[img]${i}[/img]`);
| |
| 269 | } | |
| 270 | } | |
| 271 | ||
| 272 | ||
| 273 | if (threadData?.thread?.post?.record?.embed?.record && isActivePost) {
| |
| 274 | try {
| |
| 275 | const qRecord = threadData.thread.post.record.embed.record; | |
| 276 | const qText = cleanText(qRecord.value?.text || ""); | |
| 277 | if (qText) {
| |
| 278 | const qUriParts = qRecord.uri.split('/');
| |
| 279 | const qHandle = qUriParts[2]; | |
| 280 | const qId = qUriParts[4]; | |
| 281 | text += `\n\n[QUOTED POST]\nπΊ ${qHandle}\n${qText}`;
| |
| 282 | if (qRecord.value?.embed?.$type === "app.bsky.embed.video") {
| |
| 283 | const quotedVideoUrl = constructVideoUrl(qHandle, qRecord.value.embed.video); | |
| 284 | text += `\n[U][URL]${quotedVideoUrl}[/URL][/U]`;
| |
| 285 | console.log("Quoted video URL from embed:", quotedVideoUrl);
| |
| 286 | } | |
| 287 | quotedUrl = `https://bsky.app/profile/${qHandle}/post/${qId}`;
| |
| 288 | } | |
| 289 | } catch(e){ console.warn("Error handling embedded quoted record:", e) }
| |
| 290 | } | |
| 291 | ||
| 292 | return { text, author: mainHandle ? `πΊ ${mainHandle}` : "", url: mainUrl, threadData, quotedUrl }
| |
| 293 | } | |
| 294 | ||
| 295 | async function extractPosts(count){
| |
| 296 | const allPosts = Array.from(document.querySelectorAll('[data-testid^="postThreadItem-by-"], [data-testid="postThreadItem"]'));
| |
| 297 | if (!allPosts.length) return void showToast("No posts found on this page.");
| |
| 298 | ||
| 299 | let visiblePosts = allPosts.filter(isElementVisible); | |
| 300 | if (!visiblePosts.length) visiblePosts = allPosts; | |
| 301 | ||
| 302 | const currentId = window.location.href.split('/').pop().split('?')[0];
| |
| 303 | let activePost = visiblePosts.find(p => p.querySelector('a[href*="/post/'+currentId+'"]'));
| |
| 304 | if (!activePost) {
| |
| 305 | let best = null, bestScore = -1; | |
| 306 | for (const p of visiblePosts) {
| |
| 307 | const score = visibilityScore(p); | |
| 308 | if (score > bestScore) { bestScore = score; best = p; }
| |
| 309 | } | |
| 310 | activePost = best || visiblePosts[0] || allPosts[0]; | |
| 311 | } | |
| 312 | ||
| 313 | const visibleIndex = visiblePosts.indexOf(activePost); | |
| 314 | const allIndex = allPosts.indexOf(activePost); | |
| 315 | ||
| 316 | const { text: activeText, author: activeAuthor, url: activeUrl, threadData: activeThreadData, quotedUrl: activeQuotedUrl } = await extractPost(activePost, true);
| |
| 317 | ||
| 318 | const r = activePost.querySelector(".css-146c3p1.r-dnmrzs.r-1udh08x.r-1udbk01.r-3s2u2q.r-1iln25a"),
| |
| 319 | n = r?.textContent?.trim() || document.querySelector('[data-testid="profileHandle"]')?.textContent?.trim() || "";
| |
| 320 | if (!n) return void showToast("Could not determine main username.");
| |
| 321 | - | if (parentFromDomElement && parentText) pushEl(parentFromDomElement); |
| 321 | + | |
| 322 | let parentUrl = null, parentText = null, parentAuthor = null, quotedPostUrl = activeQuotedUrl; | |
| 323 | let parentFromDomElement = null; | |
| 324 | ||
| 325 | if (visibleIndex > 0) parentFromDomElement = visiblePosts[visibleIndex - 1]; | |
| 326 | if (!parentFromDomElement && allIndex > 0) parentFromDomElement = allPosts[allIndex - 1]; | |
| 327 | ||
| 328 | if (!activeThreadData?.thread?.post?.reply?.parent && parentFromDomElement) {
| |
| 329 | const parentExtract = await extractPost(parentFromDomElement, false); | |
| 330 | if (parentExtract.author === activeAuthor || parentFromDomElement.querySelector(".css-146c3p1.r-dnmrzs.r-1udh08x.r-1udbk01.r-3s2u2q.r-1iln25a")?.textContent?.trim() === n) {
| |
| 331 | parentUrl = parentExtract.url; parentText = parentExtract.text; parentAuthor = parentExtract.author; | |
| 332 | } | |
| 333 | } | |
| 334 | ||
| 335 | if (activeThreadData?.thread?.post?.reply?.parent) {
| |
| 336 | const parentUri = activeThreadData.thread.post.reply.parent.uri; | |
| 337 | const parentAuthorUri = parentUri.split('/')[2];
| |
| 338 | const parentId = parentUri.split('/')[4];
| |
| 339 | parentUrl = `https://bsky.app/profile/${parentAuthorUri}/post/${parentId}`;
| |
| 340 | parentText = cleanText(activeThreadData.thread.post.reply.parent.value?.text || ""); | |
| 341 | parentAuthor = `πΊ ${parentAuthorUri}`;
| |
| 342 | const parentQuotedPost = activeThreadData.thread.post.reply.parent.embed?.record; | |
| 343 | if (parentQuotedPost) {
| |
| 344 | const qText = cleanText(parentQuotedPost.value?.text || ""); | |
| 345 | if (qText) parentText += `\n\n[QUOTED POST]\nπΊ ${parentQuotedPost.uri.split('/')[2]}\n${qText}`;
| |
| 346 | quotedPostUrl = `https://bsky.app/profile/${parentQuotedPost.uri.split('/')[2]}/post/${parentQuotedPost.uri.split('/')[4]}`;
| |
| 347 | } | |
| 348 | } | |
| 349 | ||
| 350 | ||
| 351 | const postsToProcessElements = []; | |
| 352 | const addedEls = new Set(); | |
| 353 | ||
| 354 | function pushEl(el){
| |
| 355 | if(!el || addedEls.has(el)) return false; | |
| 356 | - | let generatedCount = postsToProcessElements.length; |
| 356 | + | |
| 357 | addedEls.add(el); | |
| 358 | return true; | |
| 359 | } | |
| 360 | ||
| 361 | ||
| 362 | if (parentFromDomElement && parentText) pushEl(parentFromDomElement); | |
| 363 | pushEl(activePost); | |
| 364 | ||
| 365 | let needed = Math.max(0, count - postsToProcessElements.length); | |
| 366 | ||
| 367 | ||
| 368 | let src = visiblePosts; | |
| 369 | let startIdx = visiblePosts.indexOf(activePost) >= 0 ? visiblePosts.indexOf(activePost) + 1 : -1; | |
| 370 | if (startIdx >= 0) {
| |
| 371 | for (let i = startIdx; i < src.length && needed > 0; i++){
| |
| 372 | - | |
| 372 | + | |
| 373 | } | |
| 374 | } | |
| 375 | ||
| 376 | ||
| 377 | if (needed > 0) {
| |
| 378 | const srcAll = allPosts; | |
| 379 | let startAll = srcAll.indexOf(activePost) >= 0 ? srcAll.indexOf(activePost) + 1 : 0; | |
| 380 | for (let i = startAll; i < srcAll.length && needed > 0; i++){
| |
| 381 | if(pushEl(srcAll[i])) needed--; | |
| 382 | } | |
| 383 | } | |
| 384 | ||
| 385 | - | |
| 385 | + | |
| 386 | for (const p of allPosts) {
| |
| 387 | if(needed <= 0) break; | |
| 388 | if(pushEl(p)) needed--; | |
| 389 | } | |
| 390 | } | |
| 391 | ||
| 392 | const s = parentText ? [{ author: parentAuthor, text: parentText }, { author: activeAuthor, text: activeText }] : [{ author: activeAuthor, text: activeText }];
| |
| 393 | let aImgsCount = (parentText ? (parentText.match(/\[img\]/g)||[]).length : 0) + ((activeText.match(/\[img\]/g)||[]).length || 0); | |
| 394 | let generatedCount = postsToProcessElements.length; | |
| 395 | let mainPostUrls = new Set([activeUrl]); | |
| 396 | if (parentAuthor === activeAuthor && parentUrl) mainPostUrls.add(parentUrl); | |
| 397 | if (parentUrl && parentUrl !== activeUrl) mainPostUrls.add(parentUrl); | |
| 398 | ||
| 399 | const startProcessIndex = parentText ? 2 : 1; | |
| 400 | const outputs = []; | |
| 401 | ||
| 402 | for (let idx = startProcessIndex; idx < postsToProcessElements.length; idx++){
| |
| 403 | const postEl = postsToProcessElements[idx]; | |
| 404 | const { text: pText, author: pAuthor, url: pUrl, threadData: pThread } = await extractPost(postEl, false);
| |
| 405 | if (!pText && !pUrl && !extractMedia(postEl).images.length) {
| |
| 406 | generatedCount--; | |
| 407 | continue; | |
| 408 | } | |
| 409 | ||
| 410 | if (pUrl && pAuthor === `πΊ ${n}` && pUrl !== activeUrl && pUrl !== parentUrl) mainPostUrls.add(pUrl);
| |
| 411 | s.push({ author: pAuthor, text: pText });
| |
| 412 | aImgsCount += (pText.match(/\[img\]/g) || []).length; | |
| 413 | ||
| 414 | if (aImgsCount >= 20 || idx === postsToProcessElements.length - 1 || s.length >= count) {
| |
| 415 | const hasThreads = (parentUrl && parentUrl !== activeUrl) || [...mainPostUrls].some(u=>u !== activeUrl) || (quotedPostUrl && quotedPostUrl !== activeUrl); | |
| 416 | 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]`);
| |
| 417 | if (parentText) {
| |
| 418 | s.length = 0; | |
| 419 | s.push({ author: parentAuthor, text: parentText }, { author: activeAuthor, text: activeText });
| |
| 420 | } else {
| |
| 421 | s.length = 0; | |
| 422 | s.push({ author: activeAuthor, text: activeText });
| |
| 423 | } | |
| 424 | aImgsCount = (parentText ? (parentText.match(/\[img\]/g)||[]).length : 0) + ((activeText.match(/\[img\]/g)||[]).length || 0); | |
| 425 | } | |
| 426 | } | |
| 427 | ||
| 428 | if (s.length && !outputs.length) {
| |
| 429 | const hasThreads = (parentUrl && parentUrl !== activeUrl) || [...mainPostUrls].some(u=>u !== activeUrl) || (quotedPostUrl && quotedPostUrl !== activeUrl); | |
| 430 | 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]`);
| |
| 431 | } | |
| 432 | ||
| 433 | const finalOutput = outputs.join("\n\n[threads continued]\n\n");
| |
| 434 | const ta = document.createElement("textarea");
| |
| 435 | ta.value = finalOutput; | |
| 436 | document.body.appendChild(ta); | |
| 437 | ta.select(); | |
| 438 | document.execCommand("copy");
| |
| 439 | document.body.removeChild(ta); | |
| 440 | showToast(`Copied: ${Math.max(1, postsToProcessElements.length)} posts`);
| |
| 441 | } | |
| 442 | ||
| 443 | createPostCountPrompt(extractPosts); | |
| 444 | })(); | |
| 445 |