liady/ChatGPT-pdf

For anyone else, still have requirement for export ChatGPT conversations into file

sawyerbutton opened this issue · 0 comments

I've extracted the content related to obtaining the canvas and downloading it from the source code, creating a runnable Chrome DevTools code snippet.

  1. It supports directly downloading the long image of the conversation.
  2. I didn't include any preloading code for PDFJS, so the functionality of downloading as a PDF needs to be implemented separately. However, it's not complicated (you can ask ChatGPT to help you write it).
    Simply copy the code into Chrome DevTools' code snippet section, run it, and you'll get the result in 3–5 seconds.

PS:

  1. At first, I tried to implement the logic of obtaining the canvas myself, but after failing for a whole night, I decided to come here for answers.
  2. The extension itself has some minor issues, but I don't want to fix these logical problems. I just want the fastest executable solution.
  3. Code snippets have always been my choice. I don't want to turn this feature into an extension because if ChatGPT's page structure changes, the current code for obtaining the canvas will inevitably need to be adjusted, making it a nightmare if it were an extension.
  4. The above content was translated by ChatGPT. Enjoy the ALL-GPT-ERA!
class Elements {
  constructor() {
    this.init();
  }
  init() {
    // this.threadWrapper = document.querySelector(".cdfdFe");
    this.spacer = document.querySelector(
      ".w-full.h-32.md\\:h-48.flex-shrink-0"
    );
    this.thread = document.querySelector(
      "[class*='react-scroll-to-bottom']>[class*='react-scroll-to-bottom']>div"
    );
    this.positionForm = document.querySelector("form").parentNode;
    // this.styledThread = document.querySelector("main");
    // this.threadContent = document.querySelector(".gAnhyd");
    this.scroller = Array.from(
      document.querySelectorAll('[class*="react-scroll-to"]')
    ).filter((el) => el.classList.contains("h-full"))[0];
    this.hiddens = Array.from(document.querySelectorAll(".overflow-hidden"));
    this.images = Array.from(document.querySelectorAll("img[srcset]"));
  }
  fixLocation() {
    this.hiddens.forEach((el) => {
      el.classList.remove("overflow-hidden");
    });
    this.spacer.style.display = "none";
    this.thread.style.maxWidth = "960px";
    this.thread.style.marginInline = "auto";
    this.positionForm.style.display = "none";
    this.scroller.classList.remove("h-full");
    this.scroller.style.minHeight = "100vh";
    this.images.forEach((img) => {
      const srcset = img.getAttribute("srcset");
      img.setAttribute("srcset_old", srcset);
      img.setAttribute("srcset", "");
    });
    //Fix to the text shifting down when generating the canvas
    document.body.style.lineHeight = "0.5";
  }
  restoreLocation() {
    this.hiddens.forEach((el) => {
      el.classList.add("overflow-hidden");
    });
    this.spacer.style.display = null;
    this.thread.style.maxWidth = null;
    this.thread.style.marginInline = null;
    this.positionForm.style.display = null;
    this.scroller.classList.add("h-full");
    this.scroller.style.minHeight = null;
    this.images.forEach((img) => {
      const srcset = img.getAttribute("srcset_old");
      img.setAttribute("srcset", srcset);
      img.setAttribute("srcset_old", "");
    });
    document.body.style.lineHeight = null;
  }
}

const Format = {
  PNG: "png",
  PDF: "pdf",
};

function downloadThread({ as = Format.PNG } = {}) {
  const elements = new Elements();
  elements.fixLocation();
  const pixelRatio = window.devicePixelRatio;
  // since I only need to export to image format, following two lines code mean less
  const minRatio = as === Format.PDF ? 2 : 2.5;
  window.devicePixelRatio = Math.max(pixelRatio, minRatio);

  html2canvas(elements.thread, {
    letterRendering: true,
  }).then(async function (canvas) {
    elements.restoreLocation();
    window.devicePixelRatio = pixelRatio;
    const imgData = canvas.toDataURL("image/png");
    requestAnimationFrame(() => {
      if (as === Format.PDF) {
        return handlePdf(imgData, canvas, pixelRatio);
      } else {
        handleImg(imgData);
      }
    });
  });
}

function handleImg(imgData) {
  const binaryData = atob(imgData.split("base64,")[1]);
  const data = [];
  for (let i = 0; i < binaryData.length; i++) {
    data.push(binaryData.charCodeAt(i));
  }
  const blob = new Blob([new Uint8Array(data)], { type: "image/png" });
  const url = URL.createObjectURL(blob);

  window.open(url, "_blank");

  const a = document.createElement("a");
  a.href = url;
  a.download = "chat-gpt-image.png";
  a.click();
}
  // since I only need to export to image format, following two lines code mean less
  // if you wanna to export to pdf file, import pdfjs lib before everything, otherwise you will get an error about 'operty 'jsPDF' of 'window.jspdf' as it is undefined.
function handlePdf(imgData, canvas, pixelRatio) {
  const { jsPDF } = window.jspdf;
  const orientation = canvas.width > canvas.height ? "l" : "p";
  var pdf = new jsPDF(orientation, "pt", [
    canvas.width / pixelRatio,
    canvas.height / pixelRatio,
  ]);
  var pdfWidth = pdf.internal.pageSize.getWidth();
  var pdfHeight = pdf.internal.pageSize.getHeight();
  pdf.addImage(imgData, "PNG", 0, 0, pdfWidth, pdfHeight);
  pdf.save("chat-gpt.pdf");
}

downloadThread();