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 | })(); |