v1.2

📋 dev_copyBar

One bookmarklet. Every platform. All your conversation capture tools in one floating toolbar.

📋 Copy Bar

Drag this to your bookmarks bar to install
Or right-click → Bookmark This Link
Click again to toggle off

Watch the short walkthrough for installing dev_copyBar and using it from a supported page.

▶ Open YouTube tutorial
🟠
Claude
claude.ai
🟢
ChatGPT
chatgpt.com
Grok
grok.com
Grok on X
x.com sidebar
🦙
llama.cpp
local web UI
🧵
X Thread
full thread → markdown
📝
X Article
long-form → markdown
📖
Defuddle
any page → reader markdown

Auto-detect — recognizes which platform you're on and highlights the matching button

One-click capture — exports conversations to clean markdown, copied to clipboard or downloaded as .md

Toggle on/off — click the bookmarklet again to dismiss the toolbar

Works everywhere — all 8 platforms accessible from one floating panel, no extensions needed

javascript:(()=>{if(window.__copyBar&&__copyBar.remove)return __copyBar.remove(),void delete window.__copyBar;const e=document,t=location.hostname,r=location.href,n=e=>e.toString().padStart(2,"0"),a=()=>{const e=new Date;return`${e.getFullYear()}-${n(e.getMonth()+1)}-${n(e.getDate())} ${n(e.getHours())}:${n(e.getMinutes())}:${n(e.getSeconds())}`},o=()=>{const e=new Date;return`${e.getFullYear()}-${n(e.getMonth()+1)}-${n(e.getDate())}_${n(e.getHours())}-${n(e.getMinutes())}-${n(e.getSeconds())}`},l=t=>{const D=e,root=D.createElement("div");root.innerHTML=t;root.querySelectorAll("pre").forEach(pre=>{const code=pre.querySelector("code")||pre;let lang="";const langEl=pre.querySelector('[class*="language-"]'),cm=((langEl&&langEl.className)||code.className||"").match(/language-([\w#+.-]+)/i);if(cm)lang=cm[1];if(!lang){const clone=pre.cloneNode(true),cc=clone.querySelector("code");if(cc)cc.remove();const hdr=clone.textContent.replace(/\s+/g," ").replace(/copy code|copy/gi,"").trim().toLowerCase();if(/^[a-z0-9#+.\-]{1,15}$/.test(hdr))lang=hdr}const txt=(code.textContent||"").replace(/\n+$/,""),mx=(txt.match(/`+/g)||[]).reduce((m,x)=>x.length>m?x.length:m,0),F="`".repeat(mx<3?3:mx+1);pre.replaceWith(D.createTextNode("\n"+F+lang+"\n"+txt+"\n"+F+"\n"))});let r=root.innerHTML;r=r.replace(/<h1[^>]*>(.*?)<\/h1>/gim,"# $1\n"),r=r.replace(/<h2[^>]*>(.*?)<\/h2>/gim,"## $1\n"),r=r.replace(/<h3[^>]*>(.*?)<\/h3>/gim,"### $1\n"),r=r.replace(/<strong>(.*?)<\/strong>/gim,"**$1**"),r=r.replace(/<b>(.*?)<\/b>/gim,"**$1**"),r=r.replace(/<em>(.*?)<\/em>/gim,"*$1*"),r=r.replace(/<i>(.*?)<\/i>/gim,"*$1*"),r=r.replace(/<code[^>]*>(.*?)<\/code>/gim,"`$1`"),r=r.replace(/<a href="(.*?)"[^>]*>(.*?)<\/a>/gim,"[$2]($1)"),r=r.replace(/<ul>([\s\S]*?)<\/ul>/gim,(e,t)=>t.replace(/<li>([\s\S]*?)<\/li>/gim,"- $1\n")),r=r.replace(/<ol>([\s\S]*?)<\/ol>/gim,(e,t)=>{let r=1;return t.replace(/<li>([\s\S]*?)<\/li>/gim,()=>r+++". $1\n")}),r=r.replace(/<p[^>]*>(.*?)<\/p>/gim,"$1\n\n"),r=r.replace(/<br\s*\/?>/gim,"\n"),r=r.replace(/<[^>]+>/g,"");const ta=D.createElement("textarea");return ta.innerHTML=r,ta.value},i=(t,r,n,a)=>{navigator.clipboard.writeText(t).then(()=>{a(`✅ ${n} messages → clipboard`)}).catch(()=>{const l=new Blob([t],{type:"text/markdown"}),i=e.createElement("a");i.href=URL.createObjectURL(l),i.download=`${r.replace(/[^a-z0-9]/gi,"_").substring(0,50)}_${o()}.md`,i.style.display="none",e.body.appendChild(i),i.click(),e.body.removeChild(i),a(`⬇ ${n} messages → .md download (clipboard blocked)`)})},c=e=>`# ${e}\n*Exported: ${a()}*\n\n---\n\n`,s=t.includes("claude.ai")?"claude":t.includes("chatgpt.com")||t.includes("chat.openai.com")?"chatgpt":t.includes("grok.com")?"grok":(t.includes("x.com")||t.includes("twitter.com"))&&e.querySelector('button[aria-label="Copy text"]')?"x-grok":(t.includes("x.com")||t.includes("twitter.com"))&&e.querySelector('[data-testid="longformRichTextComponent"]')?"x-article":(t.includes("x.com")||t.includes("twitter.com"))&&e.querySelector('article[data-testid="tweet"]')?"x-thread":e.querySelector(".flex.h-full.flex-col.space-y-10")?"llama":"unknown",d={claude:t=>{const r=e.querySelector("title"),n=r?r.innerText.replace("| Claude","").trim():"Claude_Export";let a=c(n);const o=e.querySelectorAll('[data-testid="user-message"]'),s=e.querySelectorAll(".font-claude-response"),d=[];o.forEach(t=>{d.push({e:t,r:"User",o:Array.from(e.body.querySelectorAll("*")).indexOf(t)})}),s.forEach(t=>{d.push({e:t,r:"ChatGPT",o:Array.from(e.body.querySelectorAll("*")).indexOf(t)})}),d.sort((e,t)=>e.o-t.o),d.length?(d.forEach(e=>{a+=`### ${e.r}\n\n${l(e.e.innerHTML)}\n\n---\n\n`}),i(a,n,d.length,t)):t("⚠ no messages found")},chatgpt:t=>{const r=e.querySelector("title"),n=r?r.innerText.replace("ChatGPT","").trim():"ChatGPT_Export";let a=c(n);const o=e.querySelectorAll('[data-message-author-role="user"], [data-message-author-role="assistant"]');o.length?(o.forEach(e=>{let t="ChatGPT";"user"===e.getAttribute("data-message-author-role")&&(t="User");const r=e.querySelector(".markdown")||e.querySelector(".prose")||e.querySelector(".whitespace-pre-wrap")||e;a+=`### ${t}\n\n${l(r.innerHTML)}\n\n---\n\n`}),i(a,n,o.length,t)):t("⚠ no messages found")},grok:t=>{const r=e.querySelector("title"),n=r&&r.innerText.replace(/Grok|xAI|×/gi,"").trim()||"Grok_Chat";let a=c(n);const o=e.querySelectorAll("div.message-bubble");o.length?(o.forEach(e=>{let t="ChatGPT";(e.classList.contains("rounded-br-lg")||e.classList.contains("bg-surface-l1")||e.closest(".justify-end"))&&(t="User");const r=e.querySelector(".response-content-markdown")||e.querySelector(".prose")||e;a+=`### ${t}\n\n${l(r.innerHTML)}\n\n---\n\n`}),i(a,n,o.length,t)):t("⚠ no messages found")},"x-grok":t=>{const r=e.querySelector('button[aria-label="Copy text"]');if(!r)return void t("⚠ no Grok responses found");const n=function(e,t){for(let r=0;r<t;r++)e&&(e=e.parentElement);return e}(r,8).parentElement,a=Array.from(n.children);let o=c("Grok Chat (X)"),l=0;a.forEach(e=>{const t=e.innerText.trim();if(!t)return;const r=!!e.querySelector('button[aria-label="Copy text"]'),n=r?"ChatGPT":"User",a=r?t.replace(/^Thoughts\n+/i,"").replace(/\n+\d+\s+(?:posts?|web pages?|results?)(\n\d+\s+(?:posts?|web pages?|results?))*\s*$/i,"").replace(/\n([a-z0-9.-]+\.[a-z]{2,}(?:\s*\+\d+)?)\n/gi,"\n").replace(/\n(@\w+)\n/g," $1 ").replace(/ +/g," ").replace(/\n /g,"\n").trim():t;a&&(o+=`### ${n}\n\n${a}\n\n---\n\n`,l++)}),i(o,"Grok_X_Chat",l,t)},"x-thread":t=>{const r=e.querySelector("title"),n=r?r.innerText.replace(/\s*\/\s*X\s*$/i,"").replace(/\s+on\s+X\s*:/i,":").trim():"X_Thread";let a=c(n||"X_Thread");const l=e.querySelectorAll('article[data-testid="tweet"]'),i={};let s=0;l.forEach(e=>{const t=e.querySelector('a[href*="/status/"]'),r=t?t.getAttribute("href"):null;if(r&&i[r])return;r&&(i[r]=!0);const n=e.querySelector('[data-testid="User-Name"]'),o=n?n.querySelector("a[href]"):null,l=o?o.innerText.replace(/\n/g," ").trim():"",c=(n?Array.from(n.querySelectorAll("span")):[]).find(e=>e.innerText.trim().startsWith("@")),d=c?c.innerText.trim():"",p=e.querySelector("time"),u=p?p.innerText.trim():"",g=e.querySelector('[data-testid="tweetText"]'),m=g?g.innerText.trim():"",x=!!e.querySelector('[data-testid="videoPlayer"]'),y=!!e.querySelector('[data-testid="tweetPhoto"]'),f=m+(x?"\n\n[Video]":y?"\n\n[Image/GIF]":"");if(!f.trim())return;a+=`### ${l?`**${l}** \`${d}\``:`\`${d}\``}${u?" · "+u:""}\n\n${f}\n\n---\n\n`,s++});const d=`${(n||"X_Thread").replace(/[^a-z0-9]/gi,"_").replace(/_+/g,"_").substring(0,50)}_${o()}.md`,p=new Blob([a],{type:"text/markdown"}),u=e.createElement("a");u.href=URL.createObjectURL(p),u.download=d,u.click(),t(`⬇ ${s} tweets → ${d}`)},"x-article":t=>{const r=e.querySelector('[data-testid="longformRichTextComponent"]');if(!r)return void t("⚠ no article found");const n=e.querySelector('[data-testid="twitter-article-title"]'),l=e.querySelector('[data-testid="tweet"]'),i=l?l.querySelector('[data-testid="User-Name"]'):null,c=l?l.querySelector("time"):null,s=i?i.querySelector("a[href]"):null,d=s?s.innerText.replace(/\n/g," ").trim():"",p=(i?Array.from(i.querySelectorAll("span")):[]).find(e=>e.innerText.trim().startsWith("@")),u=p?p.innerText.trim():"",g=c?c.innerText.trim():"",m=n?n.innerText.trim():"X Article";const x=function(e){let t="";return Array.from(e.children).forEach(function e(r){const n=r.tagName,a=r.innerText.trim();(a||"IMG"===n)&&("H1"===n||"H2"===n?t+="\n\n## "+a.replace(/\n/g," ")+"\n\n":"H3"===n?t+="\n\n### "+a.replace(/\n/g," ")+"\n\n":"BLOCKQUOTE"===n?t+="\n\n> "+a.replace(/\n/g," ")+"\n\n":"LI"===n?t+="\n- "+a.replace(/\n/g," "):"UL"===n||"OL"===n?(Array.from(r.children).forEach(e),t+="\n"):"IMG"===n?t+="[Image] ":"BR"===n?t+="\n\n":r.children.length>0?Array.from(r.children).forEach(e):a&&(t+=a.replace(/\n\n+/g,"\0").replace(/\n/g," ").replace(/\u0000/g,"\n\n").replace(/ {2,}/g," ")+" "))}),t.replace(/ {2,}/g," ").replace(/\n{3,}/g,"\n\n").trim()}(r),y=`# ${m}\n\n**${d}** \`${u}\`${g?" · "+g:""}\n\n*Exported: ${a()}*\n\n---\n\n${x}`,f=`${m.replace(/[^a-z0-9]/gi,"_").replace(/_+/g,"_").substring(0,60)}_${o()}.md`,h=new Blob([y],{type:"text/markdown"}),b=e.createElement("a");b.href=URL.createObjectURL(h),b.download=f,b.click(),t(`⬇ article → ${f}`)},llama:t=>{const r=(e.title||"llama_export").replace(/\s*-\s*llama\.cpp\s*$/i,"").trim()||"llama_export";let n=c(r);const a=e.querySelector(".flex.h-full.flex-col.space-y-10"),o=a?Array.from(a.children):[];if(!o.length)return void t("⚠ no messages found");let s=0;o.forEach(e=>{const t=e.getAttribute("aria-label")||"",r=-1!==t.indexOf("User message"),a=-1!==t.indexOf("Assistant message");if(!r&&!a)return;const o=r?"User":"ChatGPT";let i="";if(r){const t=e.querySelector('[data-slot="card"]'),r=t?t.querySelector(".whitespace-pre-wrap"):null;i=r?r.innerText:t?t.innerText:e.innerText}else{const t=e.children[0];i=t?l(t.innerHTML):""}n+=`### ${o}\n\n${i}\n\n---\n\n`,s++}),i(n,r,s,t)},defuddle:t=>{t("⏳ fetching via defuddle.md…");const n="https://defuddle.md/"+r.replace(/^https?:\/\//,"");fetch(n).then(e=>e.text()).then(r=>{const n=(e.title||"page").replace(/[^a-z0-9]+/gi,"_").toLowerCase().replace(/^_|_$/g,"")+".md",a=e.createElement("a");a.href=URL.createObjectURL(new Blob([r],{type:"text/markdown"})),a.download=n,a.click(),t(`⬇ defuddle → ${n}`)}).catch(e=>t("⚠ defuddle failed: "+e.message))}},p=e.createElement("div");p.id="__copyBar",p.style.cssText="all:initial;position:fixed;z-index:2147483647;inset:auto 12px 12px auto;background:#0b1220;color:#e6f0ff;border:1px solid #1a2a40;box-shadow:0 8px 40px rgba(0,0,0,.55),0 0 0 1px rgba(255,255,255,.04);border-radius:14px;font:13px/1.2 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;user-select:none;max-width:380px;overflow:hidden;";const u=[{key:"claude",label:"Claude",group:"ai",icon:"🟠"},{key:"chatgpt",label:"ChatGPT",group:"ai",icon:"🟢"},{key:"grok",label:"Grok",group:"ai",icon:"⚡"},{key:"x-grok",label:"Grok on X",group:"ai",icon:"⚡"},{key:"llama",label:"llama.cpp",group:"ai",icon:"🦙"},{key:"x-thread",label:"X Thread",group:"x",icon:"🧵"},{key:"x-article",label:"X Article",group:"x",icon:"📝"},{key:"defuddle",label:"Defuddle",group:"util",icon:"📖"}],g=s,m=u.find(e=>e.key===g);p.innerHTML=`\n<div style="display:flex;gap:8px;align-items:center;padding:10px 12px 8px 14px;border-bottom:1px solid #1a2a40">\n <strong style="font:700 13px/1 system-ui;letter-spacing:.3px">📋 Copy Bar</strong>\n <span style="opacity:.5;font-size:11px">${m?"on "+m.label:"—"}</span>\n <button data-x style="margin-left:auto;background:#1b2538;border:1px solid #2a3852;color:#cfe5ff;padding:4px 10px;border-radius:8px;cursor:pointer;font:12px/1 system-ui">×</button>\n</div>\n<div style="padding:10px 12px 6px">\n <div style="opacity:.4;font:600 9px/1 system-ui;text-transform:uppercase;letter-spacing:1px;margin-bottom:6px">AI Chats</div>\n <div id="__cb_ai" style="display:flex;flex-wrap:wrap;gap:5px;margin-bottom:10px"></div>\n <div style="opacity:.4;font:600 9px/1 system-ui;text-transform:uppercase;letter-spacing:1px;margin-bottom:6px">X / Twitter</div>\n <div id="__cb_x" style="display:flex;flex-wrap:wrap;gap:5px;margin-bottom:10px"></div>\n <div style="opacity:.4;font:600 9px/1 system-ui;text-transform:uppercase;letter-spacing:1px;margin-bottom:6px">Utilities</div>\n <div id="__cb_util" style="display:flex;flex-wrap:wrap;gap:5px;margin-bottom:6px"></div>\n</div>\n<div id="__cblog" style="max-height:28vh;overflow:auto;border-top:1px solid #1a2a40;padding:8px 12px;font:11px/1.45 ui-monospace,Menlo,Consolas,monospace;color:#8fa8c8"></div>`;const x=p.querySelector("#__cblog"),y=e=>{x.innerHTML=`<div style="margin-bottom:3px">${e}</div>`+x.innerHTML};p.querySelector("[data-x]").onclick=()=>{p.remove(),delete window.__copyBar};u.forEach(t=>{const r=p.querySelector(`#__cb_${t.group}`),n=e.createElement("button"),a=t.key===g;n.style.cssText="all:initial;display:inline-flex;align-items:center;gap:4px;padding:6px 10px;border-radius:9px;cursor:pointer;font:12px/1 system-ui;transition:background .15s,border-color .15s;"+(a?"background:#1a3a5c;border:1px solid #2a5a8c;color:#7ec8ff;":"background:#132035;border:1px solid #1e3050;color:#8fa8c8;"),n.innerHTML=`${t.icon} ${t.label}`,n.onmouseenter=()=>{a||(n.style.background="#1a3050"),n.style.borderColor="#2a5a8c"},n.onmouseleave=()=>{a||(n.style.background="#132035",n.style.borderColor="#1e3050")},n.onclick=()=>{if(d[t.key]){y(`⏳ running ${t.label}…`);try{d[t.key](y)}catch(e){y(`⚠ ${t.label} error: ${e.message}`)}}else y(`⚠ ${t.label}: no extractor`)},r.appendChild(n)}),e.body.appendChild(p),window.__copyBar=p,y("unknown"!==g?`🎯 detected: ${m?m.label:g}`:"👀 no AI chat detected — try Defuddle for any page")})();

No bookmarks bar on iPhone or iPad? Run the exact same Copy Bar from an Apple Shortcut. The floating toolbar appears on the page, you tap a source, and the conversation lands on your clipboard — same universal User / ChatGPT tokens as the desktop bookmarklet.

  1. Open Shortcuts, tap +, and name the new shortcut dev_copyBar.
  2. Add the Run JavaScript on Web Page action (search for it in the action list).
  3. Delete the placeholder completion(x) text and paste the raw script below in its place.
  4. Open the shortcut settings → turn on Show in Share Sheet → enable Safari web pages / URLs. Tap Done.
  5. In Safari, open a supported chat → tap Share → tap dev_copyBarAllow when prompted.
  6. The 📋 Copy Bar appears. Tap your platform’s source button — that tap lets iOS write to the clipboard. You’ll see “✅ N messages → clipboard”.
⚠️Paste the script raw — not the javascript: bookmarklet and not URL-encoded. Plain JavaScript only, or you’ll get SyntaxError: unexpected token ')'.
The script already ends with its own completion(...) line — don’t add a second one, or nothing will show up.
(()=>{if(window.__copyBar&&__copyBar.remove)return __copyBar.remove(),void delete window.__copyBar;const e=document,t=location.hostname,r=location.href,n=e=>e.toString().padStart(2,"0"),a=()=>{const e=new Date;return`${e.getFullYear()}-${n(e.getMonth()+1)}-${n(e.getDate())} ${n(e.getHours())}:${n(e.getMinutes())}:${n(e.getSeconds())}`},o=()=>{const e=new Date;return`${e.getFullYear()}-${n(e.getMonth()+1)}-${n(e.getDate())}_${n(e.getHours())}-${n(e.getMinutes())}-${n(e.getSeconds())}`},l=t=>{const D=e,root=D.createElement("div");root.innerHTML=t;root.querySelectorAll("pre").forEach(pre=>{const code=pre.querySelector("code")||pre;let lang="";const langEl=pre.querySelector('[class*="language-"]'),cm=((langEl&&langEl.className)||code.className||"").match(/language-([\w#+.-]+)/i);if(cm)lang=cm[1];if(!lang){const clone=pre.cloneNode(true),cc=clone.querySelector("code");if(cc)cc.remove();const hdr=clone.textContent.replace(/\s+/g," ").replace(/copy code|copy/gi,"").trim().toLowerCase();if(/^[a-z0-9#+.\-]{1,15}$/.test(hdr))lang=hdr}const txt=(code.textContent||"").replace(/\n+$/,""),mx=(txt.match(/`+/g)||[]).reduce((m,x)=>x.length>m?x.length:m,0),F="`".repeat(mx<3?3:mx+1);pre.replaceWith(D.createTextNode("\n"+F+lang+"\n"+txt+"\n"+F+"\n"))});let r=root.innerHTML;r=r.replace(/<h1[^>]*>(.*?)<\/h1>/gim,"# $1\n"),r=r.replace(/<h2[^>]*>(.*?)<\/h2>/gim,"## $1\n"),r=r.replace(/<h3[^>]*>(.*?)<\/h3>/gim,"### $1\n"),r=r.replace(/<strong>(.*?)<\/strong>/gim,"**$1**"),r=r.replace(/<b>(.*?)<\/b>/gim,"**$1**"),r=r.replace(/<em>(.*?)<\/em>/gim,"*$1*"),r=r.replace(/<i>(.*?)<\/i>/gim,"*$1*"),r=r.replace(/<code[^>]*>(.*?)<\/code>/gim,"`$1`"),r=r.replace(/<a href="(.*?)"[^>]*>(.*?)<\/a>/gim,"[$2]($1)"),r=r.replace(/<ul>([\s\S]*?)<\/ul>/gim,(e,t)=>t.replace(/<li>([\s\S]*?)<\/li>/gim,"- $1\n")),r=r.replace(/<ol>([\s\S]*?)<\/ol>/gim,(e,t)=>{let r=1;return t.replace(/<li>([\s\S]*?)<\/li>/gim,()=>r+++". $1\n")}),r=r.replace(/<p[^>]*>(.*?)<\/p>/gim,"$1\n\n"),r=r.replace(/<br\s*\/?>/gim,"\n"),r=r.replace(/<[^>]+>/g,"");const ta=D.createElement("textarea");return ta.innerHTML=r,ta.value},i=(t,r,n,a)=>{navigator.clipboard.writeText(t).then(()=>{a(`✅ ${n} messages → clipboard`)}).catch(()=>{const l=new Blob([t],{type:"text/markdown"}),i=e.createElement("a");i.href=URL.createObjectURL(l),i.download=`${r.replace(/[^a-z0-9]/gi,"_").substring(0,50)}_${o()}.md`,i.style.display="none",e.body.appendChild(i),i.click(),e.body.removeChild(i),a(`⬇ ${n} messages → .md download (clipboard blocked)`)})},c=e=>`# ${e}\n*Exported: ${a()}*\n\n---\n\n`,s=t.includes("claude.ai")?"claude":t.includes("chatgpt.com")||t.includes("chat.openai.com")?"chatgpt":t.includes("grok.com")?"grok":(t.includes("x.com")||t.includes("twitter.com"))&&e.querySelector('button[aria-label="Copy text"]')?"x-grok":(t.includes("x.com")||t.includes("twitter.com"))&&e.querySelector('[data-testid="longformRichTextComponent"]')?"x-article":(t.includes("x.com")||t.includes("twitter.com"))&&e.querySelector('article[data-testid="tweet"]')?"x-thread":e.querySelector(".flex.h-full.flex-col.space-y-10")?"llama":"unknown",d={claude:t=>{const r=e.querySelector("title"),n=r?r.innerText.replace("| Claude","").trim():"Claude_Export";let a=c(n);const o=e.querySelectorAll('[data-testid="user-message"]'),s=e.querySelectorAll(".font-claude-response"),d=[];o.forEach(t=>{d.push({e:t,r:"User",o:Array.from(e.body.querySelectorAll("*")).indexOf(t)})}),s.forEach(t=>{d.push({e:t,r:"ChatGPT",o:Array.from(e.body.querySelectorAll("*")).indexOf(t)})}),d.sort((e,t)=>e.o-t.o),d.length?(d.forEach(e=>{a+=`### ${e.r}\n\n${l(e.e.innerHTML)}\n\n---\n\n`}),i(a,n,d.length,t)):t("⚠ no messages found")},chatgpt:t=>{const r=e.querySelector("title"),n=r?r.innerText.replace("ChatGPT","").trim():"ChatGPT_Export";let a=c(n);const o=e.querySelectorAll('[data-message-author-role="user"], [data-message-author-role="assistant"]');o.length?(o.forEach(e=>{let t="ChatGPT";"user"===e.getAttribute("data-message-author-role")&&(t="User");const r=e.querySelector(".markdown")||e.querySelector(".prose")||e.querySelector(".whitespace-pre-wrap")||e;a+=`### ${t}\n\n${l(r.innerHTML)}\n\n---\n\n`}),i(a,n,o.length,t)):t("⚠ no messages found")},grok:t=>{const r=e.querySelector("title"),n=r&&r.innerText.replace(/Grok|xAI|×/gi,"").trim()||"Grok_Chat";let a=c(n);const o=e.querySelectorAll("div.message-bubble");o.length?(o.forEach(e=>{let t="ChatGPT";(e.classList.contains("rounded-br-lg")||e.classList.contains("bg-surface-l1")||e.closest(".justify-end"))&&(t="User");const r=e.querySelector(".response-content-markdown")||e.querySelector(".prose")||e;a+=`### ${t}\n\n${l(r.innerHTML)}\n\n---\n\n`}),i(a,n,o.length,t)):t("⚠ no messages found")},"x-grok":t=>{const r=e.querySelector('button[aria-label="Copy text"]');if(!r)return void t("⚠ no Grok responses found");const n=function(e,t){for(let r=0;r<t;r++)e&&(e=e.parentElement);return e}(r,8).parentElement,a=Array.from(n.children);let o=c("Grok Chat (X)"),l=0;a.forEach(e=>{const t=e.innerText.trim();if(!t)return;const r=!!e.querySelector('button[aria-label="Copy text"]'),n=r?"ChatGPT":"User",a=r?t.replace(/^Thoughts\n+/i,"").replace(/\n+\d+\s+(?:posts?|web pages?|results?)(\n\d+\s+(?:posts?|web pages?|results?))*\s*$/i,"").replace(/\n([a-z0-9.-]+\.[a-z]{2,}(?:\s*\+\d+)?)\n/gi,"\n").replace(/\n(@\w+)\n/g," $1 ").replace(/ +/g," ").replace(/\n /g,"\n").trim():t;a&&(o+=`### ${n}\n\n${a}\n\n---\n\n`,l++)}),i(o,"Grok_X_Chat",l,t)},"x-thread":t=>{const r=e.querySelector("title"),n=r?r.innerText.replace(/\s*\/\s*X\s*$/i,"").replace(/\s+on\s+X\s*:/i,":").trim():"X_Thread";let a=c(n||"X_Thread");const l=e.querySelectorAll('article[data-testid="tweet"]'),i={};let s=0;l.forEach(e=>{const t=e.querySelector('a[href*="/status/"]'),r=t?t.getAttribute("href"):null;if(r&&i[r])return;r&&(i[r]=!0);const n=e.querySelector('[data-testid="User-Name"]'),o=n?n.querySelector("a[href]"):null,l=o?o.innerText.replace(/\n/g," ").trim():"",c=(n?Array.from(n.querySelectorAll("span")):[]).find(e=>e.innerText.trim().startsWith("@")),d=c?c.innerText.trim():"",p=e.querySelector("time"),u=p?p.innerText.trim():"",g=e.querySelector('[data-testid="tweetText"]'),m=g?g.innerText.trim():"",x=!!e.querySelector('[data-testid="videoPlayer"]'),y=!!e.querySelector('[data-testid="tweetPhoto"]'),f=m+(x?"\n\n[Video]":y?"\n\n[Image/GIF]":"");if(!f.trim())return;a+=`### ${l?`**${l}** \`${d}\``:`\`${d}\``}${u?" · "+u:""}\n\n${f}\n\n---\n\n`,s++});const d=`${(n||"X_Thread").replace(/[^a-z0-9]/gi,"_").replace(/_+/g,"_").substring(0,50)}_${o()}.md`,p=new Blob([a],{type:"text/markdown"}),u=e.createElement("a");u.href=URL.createObjectURL(p),u.download=d,u.click(),t(`⬇ ${s} tweets → ${d}`)},"x-article":t=>{const r=e.querySelector('[data-testid="longformRichTextComponent"]');if(!r)return void t("⚠ no article found");const n=e.querySelector('[data-testid="twitter-article-title"]'),l=e.querySelector('[data-testid="tweet"]'),i=l?l.querySelector('[data-testid="User-Name"]'):null,c=l?l.querySelector("time"):null,s=i?i.querySelector("a[href]"):null,d=s?s.innerText.replace(/\n/g," ").trim():"",p=(i?Array.from(i.querySelectorAll("span")):[]).find(e=>e.innerText.trim().startsWith("@")),u=p?p.innerText.trim():"",g=c?c.innerText.trim():"",m=n?n.innerText.trim():"X Article";const x=function(e){let t="";return Array.from(e.children).forEach(function e(r){const n=r.tagName,a=r.innerText.trim();(a||"IMG"===n)&&("H1"===n||"H2"===n?t+="\n\n## "+a.replace(/\n/g," ")+"\n\n":"H3"===n?t+="\n\n### "+a.replace(/\n/g," ")+"\n\n":"BLOCKQUOTE"===n?t+="\n\n> "+a.replace(/\n/g," ")+"\n\n":"LI"===n?t+="\n- "+a.replace(/\n/g," "):"UL"===n||"OL"===n?(Array.from(r.children).forEach(e),t+="\n"):"IMG"===n?t+="[Image] ":"BR"===n?t+="\n\n":r.children.length>0?Array.from(r.children).forEach(e):a&&(t+=a.replace(/\n\n+/g,"\0").replace(/\n/g," ").replace(/\u0000/g,"\n\n").replace(/ {2,}/g," ")+" "))}),t.replace(/ {2,}/g," ").replace(/\n{3,}/g,"\n\n").trim()}(r),y=`# ${m}\n\n**${d}** \`${u}\`${g?" · "+g:""}\n\n*Exported: ${a()}*\n\n---\n\n${x}`,f=`${m.replace(/[^a-z0-9]/gi,"_").replace(/_+/g,"_").substring(0,60)}_${o()}.md`,h=new Blob([y],{type:"text/markdown"}),b=e.createElement("a");b.href=URL.createObjectURL(h),b.download=f,b.click(),t(`⬇ article → ${f}`)},llama:t=>{const r=(e.title||"llama_export").replace(/\s*-\s*llama\.cpp\s*$/i,"").trim()||"llama_export";let n=c(r);const a=e.querySelector(".flex.h-full.flex-col.space-y-10"),o=a?Array.from(a.children):[];if(!o.length)return void t("⚠ no messages found");let s=0;o.forEach(e=>{const t=e.getAttribute("aria-label")||"",r=-1!==t.indexOf("User message"),a=-1!==t.indexOf("Assistant message");if(!r&&!a)return;const o=r?"User":"ChatGPT";let i="";if(r){const t=e.querySelector('[data-slot="card"]'),r=t?t.querySelector(".whitespace-pre-wrap"):null;i=r?r.innerText:t?t.innerText:e.innerText}else{const t=e.children[0];i=t?l(t.innerHTML):""}n+=`### ${o}\n\n${i}\n\n---\n\n`,s++}),i(n,r,s,t)},defuddle:t=>{t("⏳ fetching via defuddle.md…");const n="https://defuddle.md/"+r.replace(/^https?:\/\//,"");fetch(n).then(e=>e.text()).then(r=>{const n=(e.title||"page").replace(/[^a-z0-9]+/gi,"_").toLowerCase().replace(/^_|_$/g,"")+".md",a=e.createElement("a");a.href=URL.createObjectURL(new Blob([r],{type:"text/markdown"})),a.download=n,a.click(),t(`⬇ defuddle → ${n}`)}).catch(e=>t("⚠ defuddle failed: "+e.message))}},p=e.createElement("div");p.id="__copyBar",p.style.cssText="all:initial;position:fixed;z-index:2147483647;inset:auto 12px 12px auto;background:#0b1220;color:#e6f0ff;border:1px solid #1a2a40;box-shadow:0 8px 40px rgba(0,0,0,.55),0 0 0 1px rgba(255,255,255,.04);border-radius:14px;font:13px/1.2 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;user-select:none;max-width:380px;overflow:hidden;";const u=[{key:"claude",label:"Claude",group:"ai",icon:"🟠"},{key:"chatgpt",label:"ChatGPT",group:"ai",icon:"🟢"},{key:"grok",label:"Grok",group:"ai",icon:"⚡"},{key:"x-grok",label:"Grok on X",group:"ai",icon:"⚡"},{key:"llama",label:"llama.cpp",group:"ai",icon:"🦙"},{key:"x-thread",label:"X Thread",group:"x",icon:"🧵"},{key:"x-article",label:"X Article",group:"x",icon:"📝"},{key:"defuddle",label:"Defuddle",group:"util",icon:"📖"}],g=s,m=u.find(e=>e.key===g);p.innerHTML=`\n<div style="display:flex;gap:8px;align-items:center;padding:10px 12px 8px 14px;border-bottom:1px solid #1a2a40">\n <strong style="font:700 13px/1 system-ui;letter-spacing:.3px">📋 Copy Bar</strong>\n <span style="opacity:.5;font-size:11px">${m?"on "+m.label:"—"}</span>\n <button data-x style="margin-left:auto;background:#1b2538;border:1px solid #2a3852;color:#cfe5ff;padding:4px 10px;border-radius:8px;cursor:pointer;font:12px/1 system-ui">×</button>\n</div>\n<div style="padding:10px 12px 6px">\n <div style="opacity:.4;font:600 9px/1 system-ui;text-transform:uppercase;letter-spacing:1px;margin-bottom:6px">AI Chats</div>\n <div id="__cb_ai" style="display:flex;flex-wrap:wrap;gap:5px;margin-bottom:10px"></div>\n <div style="opacity:.4;font:600 9px/1 system-ui;text-transform:uppercase;letter-spacing:1px;margin-bottom:6px">X / Twitter</div>\n <div id="__cb_x" style="display:flex;flex-wrap:wrap;gap:5px;margin-bottom:10px"></div>\n <div style="opacity:.4;font:600 9px/1 system-ui;text-transform:uppercase;letter-spacing:1px;margin-bottom:6px">Utilities</div>\n <div id="__cb_util" style="display:flex;flex-wrap:wrap;gap:5px;margin-bottom:6px"></div>\n</div>\n<div id="__cblog" style="max-height:28vh;overflow:auto;border-top:1px solid #1a2a40;padding:8px 12px;font:11px/1.45 ui-monospace,Menlo,Consolas,monospace;color:#8fa8c8"></div>`;const x=p.querySelector("#__cblog"),y=e=>{x.innerHTML=`<div style="margin-bottom:3px">${e}</div>`+x.innerHTML};p.querySelector("[data-x]").onclick=()=>{p.remove(),delete window.__copyBar};u.forEach(t=>{const r=p.querySelector(`#__cb_${t.group}`),n=e.createElement("button"),a=t.key===g;n.style.cssText="all:initial;display:inline-flex;align-items:center;gap:4px;padding:6px 10px;border-radius:9px;cursor:pointer;font:12px/1 system-ui;transition:background .15s,border-color .15s;"+(a?"background:#1a3a5c;border:1px solid #2a5a8c;color:#7ec8ff;":"background:#132035;border:1px solid #1e3050;color:#8fa8c8;"),n.innerHTML=`${t.icon} ${t.label}`,n.onmouseenter=()=>{a||(n.style.background="#1a3050"),n.style.borderColor="#2a5a8c"},n.onmouseleave=()=>{a||(n.style.background="#132035",n.style.borderColor="#1e3050")},n.onclick=()=>{if(d[t.key]){y(`⏳ running ${t.label}…`);try{d[t.key](y)}catch(e){y(`⚠ ${t.label} error: ${e.message}`)}}else y(`⚠ ${t.label}: no extractor`)},r.appendChild(n)}),e.body.appendChild(p),window.__copyBar=p,y("unknown"!==g?`🎯 detected: ${m?m.label:g}`:"👀 no AI chat detected — try Defuddle for any page")})(); completion("📋 Copy Bar loaded");

💡 Tip: long-press the shortcut → Add to Home Screen, or leave it in the Share Sheet for the fastest in-Safari flow. Running it again toggles the bar off — run once more to bring it back.

dev_copyBar captures conversations as raw markdown with a universal speaker token. For full post-processing — custom persona labels, whitespace cleanup, line compression — pass the output through RaccoonSanitizer.

📋 dev_copyBar
📎 clipboard
🦝 RaccoonSanitizer
clean .md
🦝

RaccoonSanitizer

Transform raw AI chat exports into clean, Obsidian-ready markdown. Custom speaker labels, line compression, UI artifact removal, and stats — all in one paste-and-go tool.