// ==UserScript== // @name Save ChatGPT as PDF // @namespace http://tampermonkey.net/ // @version 1.0 // @description Turn your chats into neatly formatted PDF. // @author Pdfcrowd (https://pdfcrowd.com/) // @match https://chat.openai.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/latest/'; pdfcrowdChatGPT.username = 'chat-gpt'; pdfcrowdChatGPT.apiKey = '29d211b1f6924c22b7a799b4e8fecb7e'; pdfcrowdChatGPT.init = function() { // remote images live at least 1 minute const minImageDuration = 60000; const buttonIconFill = (typeof GM_xmlhttpRequest !== 'undefined') ? '#A72C16' : '#EA4C3A'; const pdfcrowdBlockHtml = `
A4 Portrait
A4 Landscape
Letter Portrait
Letter Landscape

`; const customCss = ` button { all: initial; } pre code.hljs { white-space: pre-wrap; } .icon-sm { stroke-width: 2; margin-left: .25rem; margin-top: .25rem; height: 1rem; width: 1rem; } form, button, .tabular-nums, .text-token-text-tertiary { display: none !important; } .text-white { --tw-text-opacity: 1; color: rgba(255,255,255,var(--tw-text-opacity)); } .font-semibold { font-weight: 600; } html { font-family: Noto Sans,sans-serif,Helvetica Neue,Arial,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji; } p { line-height: 1.5; } code:not(.hljs) { font-weight: 600; } code:not(.hljs)::before { content: '\\\`'; } code:not(.hljs)::after { content: '\\\`'; } .text-xs { font-size: .75rem; } .text-center { text-align: center; } .bg-gray-800 { background-color: #ccc; } .gizmo-shadow-stroke img { width: 24px; height: 24px; } .gizmo-shadow-stroke { margin-right: .5rem; display: inline-block; vertical-align: text-bottom; } .py-2 { padding-top: .5rem; padding-bottom: .5rem; } .px-4 { padding-left: 1rem; padding-right: 1rem; } .grid * { display: inline-block !important; } .grid img { margin-right: .5rem; max-width: 45vw; height: auto; color: unset !important; } `; const head = ''; function prepareContent(element) { element = element.cloneNode(true); // fix nested buttons error element.querySelectorAll('button button').forEach(button => { button.parentNode.removeChild(button); }); // solve user icons element.querySelectorAll('.gizmo-shadow-stroke').forEach(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 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); const title = document.getElementsByTagName('title')[0].text; const body = `

${title}

` + content; const data = { text: `${head}${body}`, disable_javascript: true, custom_css: customCss, 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, title + '.pdf', cleanup); } function showButton() { let buttons = document.querySelectorAll('.pdfcrowd-convert'); if(buttons.length > 0) { return; } const container = document.createElement('div'); container.innerHTML = pdfcrowdBlockHtml; document.body.appendChild(container); buttons = document.querySelectorAll('.pdfcrowd-convert'); buttons.forEach(element => { element.addEventListener('click', convert); }); 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'); } }); document.getElementById('pdfcrowd-close-btn').addEventListener( 'click', () => { document.getElementById( 'pdfcrowd-error-overlay').style.display = 'none'; }); } let buttonVisible = false; function checkForContent() { if(!buttonVisible) { const mainElement = document.querySelector( 'main > div:first-child'); if (mainElement && mainElement.textContent.trim().length > 0) { buttonVisible = true; showButton(); } else { // content not found, continue checking setTimeout(checkForContent, 1000); } } } setTimeout(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 = ['Error occurred']; if (status) { html.push(`Code: ${status}`); html.push("Please try again later"); html.push(`If the problem persists, contact us at support@pdfcrowd.com `); } else { html.push(text); } } 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(); })();