// ==UserScript== // @name Save ChatGPT as PDF // @namespace http://tampermonkey.net/ // @version 1.10 // @description Turn your chats into neatly formatted PDF. // @author PDFCrowd (https://pdfcrowd.com/) // @match https://chatgpt.com/* // @icon64 https://github.com/pdfcrowd/save-chatgpt-as-pdf/raw/master/icons/icon64.png // @run-at document-end // @grant GM_xmlhttpRequest // @connect api.pdfcrowd.com // @license MIT // @downloadURL none // ==/UserScript== /* globals pdfcrowdChatGPT */ // do not modify or delete the following line, it serves as a placeholder for // the common.js contents which is copied here by "make build-userscript-single-file" // // common.js placeholder 'use strict'; const pdfcrowdChatGPT = {}; pdfcrowdChatGPT.pdfcrowdAPI = 'https://api.pdfcrowd.com/convert/24.04/'; pdfcrowdChatGPT.username = 'chat-gpt'; pdfcrowdChatGPT.apiKey = '29d211b1f6924c22b7a799b4e8fecb7e'; pdfcrowdChatGPT.init = function() { if(document.querySelectorAll('.pdfcrowd-convert').length > 0) { // avoid double init return; } // remote images live at least 1 minute const minImageDuration = 60000; const buttonIconFill = (typeof GM_xmlhttpRequest !== 'undefined') ? '#A72C16' : '#EA4C3A'; var rateUsLink = '#'; if (typeof GM_info !== 'undefined') { rateUsLink = 'https://greasyfork.org/en/scripts/484463-save-chatgpt-as-pdf/feedback#post-discussion'; } else if (navigator.userAgent.includes("Chrome")) { rateUsLink = 'https://chromewebstore.google.com/detail/save-chatgpt-as-pdf/ccjfggejcoobknjolglgmfhoeneafhhm/reviews'; } else if (navigator.userAgent.includes("Firefox")) { rateUsLink = 'https://addons.mozilla.org/en-US/firefox/addon/save-chatgpt-as-pdf/reviews/'; } const pdfcrowdBlockHtml = `

Error occurred ×

Save ChatGPT as PDF by PDFCrowd ×
Support
Feel free to contact us with any questions or for assistance. We're always happy to help!
Email us at support@pdfcrowd.com or use our contact form.
Please rate us if you like the extension. It helps a lot!
Tips
  • You can download a specific part of the chat by selecting it.
  • If images are missing in the PDF, reload the page and try downloading the PDF again.
Links
`; function findRow(element) { while(element) { if(element.classList && element.classList.contains('text-token-text-primary')) { return element; } element = element.parentElement; } return null; } function hasParent(element, parent) { while(element) { if(element === parent) { return true; } element = element.parentElement; } return false; } function prepareSelection(element) { const selection = window.getSelection(); if(!selection.isCollapsed) { const rangeCount = selection.rangeCount; if(rangeCount > 0) { const startElement = findRow( selection.getRangeAt(0).startContainer.parentElement); if(startElement && hasParent(startElement, element)) { // selection is in the main block const endElement = findRow( selection.getRangeAt( rangeCount-1).endContainer.parentElement); const newContainer = document.createElement('main'); newContainer.classList.add('h-full', 'w-full'); let currentElement = startElement; while(currentElement) { newContainer.appendChild( currentElement.cloneNode(true)); if(currentElement === endElement) { break; } currentElement = currentElement.nextElementSibling; } return newContainer; } } } return element.cloneNode(true); } function prepareContent(element) { element = prepareSelection(element); // fix nested buttons error element.querySelectorAll('button button').forEach(button => { button.parentNode.removeChild(button); }); // solve user icons // element.querySelectorAll('.gizmo-shadow-stroke,.gizmo-bot-avatar').forEach(icon => { // if(!icon.querySelector('.gizmo-shadow-stroke')) { // icon.classList.add('gizmo-shadow-stroke', 'chat-user-icon'); // let parent = icon.parentNode; // while(parent) { // const label = parent.querySelector('.font-semibold'); // if(label) { // label.insertBefore(icon, label.firstChild); // label.style.marginTop = '1.5rem'; // label.style.marginBottom = '.25rem'; // break; // } // parent = parent.parentNode; // } // } // }); // solve expired images element.querySelectorAll('.grid img').forEach(img => { img.setAttribute( 'alt', 'The image has expired. Refresh ChatGPT page and retry saving to PDF.'); }); element.classList.add('chat-gpt-custom'); return element.outerHTML; } function showHelp() { document.getElementById('pdfcrowd-extra-btns').classList.add( 'pdfcrowd-hidden'); document.getElementById('pdfcrowd-help-overlay').style.display = 'flex'; } function addPdfExtension(filename) { return filename.replace(/\.*$/, '') + '.pdf'; } function convert(event) { let trigger = event.target; document.getElementById('pdfcrowd-extra-btns').classList.add( 'pdfcrowd-hidden'); const btnConvert = document.getElementById('pdfcrowd-convert-main'); btnConvert.disabled = true; const spinner = document.getElementById('pdfcrowd-spinner'); spinner.classList.remove('pdfcrowd-hidden'); const btnElems = document.getElementsByClassName('pdfcrowd-btn-content'); for(let i = 0; i < btnElems.length; i++) { btnElems[i].classList.add('pdfcrowd-invisible'); } function cleanup() { btnConvert.disabled = false; spinner.classList.add('pdfcrowd-hidden'); for(let i = 0; i < btnElems.length; i++) { btnElems[i].classList.remove('pdfcrowd-invisible'); } } const main = document.getElementsByTagName('main')[0]; const content = prepareContent(main); let body; let title = ''; const h1 = main.querySelector('h1'); if(h1) { title = h1.textContent; body = content; } else { const chatTitle = document.querySelector(`nav a[href="${window.location.pathname}"]`); title = chatTitle ? chatTitle.textContent : document.getElementsByTagName('title')[0].textContent; body = `

${title}

` + content; } title = title.trim(); const data = { text: `${body}`, jpeg_quality: 70, image_dpi: 150, convert_images_to_jpeg: 'all', title: title, rendering_mode: 'viewport', smart_scaling_mode: 'viewport-fit' }; if(trigger.id) { localStorage.setItem('pdfcrowd-btn', trigger.id); } else { let lastBtn = localStorage.getItem('pdfcrowd-btn'); if(lastBtn) { lastBtn = document.getElementById(lastBtn); if(lastBtn) { trigger = lastBtn; } } } const convOptions = JSON.parse(trigger.dataset.convOptions || '{}'); for(let key in convOptions) { data[key] = convOptions[key]; } if(!('viewport_width' in convOptions)) { data.viewport_width = 800; } pdfcrowdChatGPT.doRequest(data, addPdfExtension(title), cleanup); } function addPdfcrowdBlock() { const container = document.createElement('div'); container.innerHTML = pdfcrowdBlockHtml; document.body.appendChild(container); let buttons = document.querySelectorAll('.pdfcrowd-convert'); buttons.forEach(element => { element.addEventListener('click', convert); }); document.getElementById('pdfcrowd-help').addEventListener( 'click', event => { showHelp(); }); document.getElementById('pdfcrowd-more').addEventListener('click', event => { event.stopPropagation(); const moreButtons = document.getElementById( 'pdfcrowd-extra-btns'); if(moreButtons.classList.contains('pdfcrowd-hidden')) { moreButtons.classList.remove('pdfcrowd-hidden'); } else { moreButtons.classList.add('pdfcrowd-hidden'); } }); document.addEventListener('click', event => { const moreButtons = document.getElementById('pdfcrowd-extra-btns'); if (!moreButtons.contains(event.target)) { moreButtons.classList.add('pdfcrowd-hidden'); } }); buttons = document.querySelectorAll('.pdfcrowd-close-btn'); buttons.forEach(element => { element.addEventListener('click', () => { element.closest('.pdfcrowd-overlay').style.display = 'none'; }); }); return container.getElementsByClassName('pdfcrowd-block')[0]; } const pdfcrowd_block = addPdfcrowdBlock(); function checkForContent() { if(document.querySelector('main div[role="presentation"]')) { pdfcrowd_block.classList.remove('pdfcrowd-hidden'); } else { pdfcrowd_block.classList.add('pdfcrowd-hidden'); } } setInterval(checkForContent, 1000); } pdfcrowdChatGPT.showError = function(status, text) { let html; if (status == 432) { html = [ "Fair Use Notice
", "Current usage is over the limit. Please wait a while before trying again.

", ]; } else { html = []; if (status) { html.push(`Code: ${status}`); html.push("Please try again later"); } else { html.push(text); } html.push(`If the problem persists, contact us at support@pdfcrowd.com `); } html = html.join('
'); document.getElementById('pdfcrowd-error-overlay').style.display = 'flex'; document.getElementById('pdfcrowd-error-message').innerHTML = html; }; pdfcrowdChatGPT.saveBlob = function(url, filename) { const a = document.createElement('a'); a.href = url; a.download = filename; a.click(); setTimeout(() => { window.URL.revokeObjectURL(url); }, 100); }; (function() { pdfcrowdChatGPT.doRequest = function(data, fileName, fnCleanup) { const formData = new FormData(); for(let key in data) { formData.append(key, data[key]); } GM_xmlhttpRequest({ url: pdfcrowdChatGPT.pdfcrowdAPI, method: 'POST', data: formData, responseType: 'blob', headers: { 'Authorization': 'Basic ' + btoa( pdfcrowdChatGPT.username + ':' + pdfcrowdChatGPT.apiKey), }, onload: response => { fnCleanup(); if(response.status == 200) { const url = window.URL.createObjectURL(response.response); pdfcrowdChatGPT.saveBlob(url, fileName); } else { pdfcrowdChatGPT.showError( response.status, response.responseText); } }, onerror: error => { console.error('conversion error:', error); fnCleanup(); pdfcrowdChatGPT.showError(500, error.responseText); } }); }; pdfcrowdChatGPT.init(); })();