You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Api2PDF在Google Apps Script中处理PDF拆分时遭遇405/422错误的排查求助

使用Api2PDF在Google Apps Script中处理PDF拆分时遭遇405/422错误的排查求助

大家好,我目前在开发一个Google Apps Script工具,用来自动处理符合特定条件的邮件:筛选出主题包含@Domain、一周内未处理的邮件,将附件转换为PDF(后续会扩展支持非PDF附件转PDF,当前代码仅处理PDF附件),并且如果PDF页数超过5页的话,用Api2PDF API拆分成多个5页以内的PDF文件,最后把处理后的附件转发到指定邮箱。

但在调用Api2PDF进行PDF拆分的过程中,我一直遇到HTTP错误,先是405 Method Not Allowed,调整请求方式后又出现422 Unprocessable Entity错误,试了很多参数和请求格式的变化都没解决问题。最新的错误是422,我已经确认Api2PDF的密钥是有效的,但就是无法成功调用拆分接口。

我怀疑可能的问题点包括:

  • 上传到Google Drive的PDF文件链接是私有的,Api2PDF无法访问;
  • 页码参数的索引问题(我代码里用的是0-based,但Api2PDF接口可能要求1-based);
  • 请求 payload 的参数格式不符合接口要求。

以下是我当前的完整代码,麻烦各位帮忙看看哪里可能出了问题,尤其是splitPdfUsingApi2Pdf函数里的Api2PDF调用部分:

// Global constants
const ERRORNOTIFICATIONEMAIL = 'helpdesk@';
const FIXEDDESTINATIONEMAIL = 'smtp@';
const EMAIL_DOMAIN = 'Domain';
const MY_LABEL = 'Processed';
const ERROR_LABEL = 'Error Processing';
const ENABLE_PDF_SPLITTING = true;
const API2PDF_KEY = ''; // Your confirmed valid Api2Pdf API key
const PAGE_LIMIT = 5;

/**
 * Main function to forward emails with processed attachments.
 */
function forwardMail() {
  const threadsWithAttachments = [];
  const oneWeekAgo = new Date();
  oneWeekAgo.setDate(oneWeekAgo.getDate() - 7);
  const formattedDate = Utilities.formatDate(oneWeekAgo, Session.getScriptTimeZone(), 'yyyy/MM/dd');

  try {
    const searchQuery = `subject:"@${EMAIL_DOMAIN}" -label:"Processed" -label:"Error Processing" after:${formattedDate}`;
    Logger.log(`Search query: ${searchQuery}`);
    const threads = GmailApp.search(searchQuery);
    Logger.log(`Found ${threads.length} threads matching query.`);

    for (const thread of threads) {
      const messages = thread.getMessages();
      let hasError = false;

      for (const message of messages) {
        const subject = message.getSubject();
        const senderEmail = message.getFrom();
        const threadId = thread.getId();
        const labels = thread.getLabels();
        const isProcessed = labels.some(label => label.getName() === MY_LABEL || label.getName() === ERROR_LABEL);

        Logger.log(`Checking thread: Subject="${subject}", Sender="${senderEmail}", ThreadID="${threadId}", Processed=${isProcessed}`);

        if (!isProcessed) {
          Logger.log(`Processing thread: Subject="${subject}", Sender="${senderEmail}", ThreadID="${threadId}`);
          try {
            const attachments = message.getAttachments();
            Logger.log(`Attachments found: ${attachments.length}`);
            attachments.forEach(att => Logger.log(`Attachment: ${att.getName()}, Type: ${att.getContentType()}`));

            const splitAttachments = attachments
              .map(attachment => transformAndSplitAttachment(attachment, thread))
              .flat()
              .filter(att => att !== null);

            Logger.log(`Processed split attachments: ${splitAttachments.length}`);
            if (splitAttachments.length > 0) {
              splitAttachments.forEach(splitAttachment => {
                const newSubject = transformSubject(subject);
                Logger.log(`Sending email with attachment: ${splitAttachment.getName()}`);
                sendEmailWithAttachments(newSubject, [splitAttachment], subject, senderEmail, threadId);
              });
              threadsWithAttachments.push(thread);
            } else {
              Logger.log('No valid split attachments to forward.');
            }
          } catch (error) {
            Logger.log(`Error processing thread: ${error.message}`);
            sendErrorNotification('Error processing thread', error.message);
            hasError = true;
          }
        }
      }

      if (hasError) {
        addErrorLabel(thread);
      }
    }

    if (threadsWithAttachments.length > 0) {
      addProcessedLabel(threadsWithAttachments);
      Logger.log(`Processed label added to ${threadsWithAttachments.length} threads.`);
    } else {
      Logger.log('No threads found with attachments.');
    }
  } catch (error) {
    Logger.log(`Error in forwardMail: ${error.message}`);
    sendErrorNotification('Error in forwardMail', error.message);
  }
}

/**
 * Transforms and splits attachment if necessary.
 */
function transformAndSplitAttachment(attachment, thread) {
  try {
    const originalBlob = attachment.copyBlob();
    const sanitizedFileName = attachment.getName().replace(/[^a-zA-Z0-9]/g, '_');

    if (originalBlob.getContentType() === MimeType.PDF) {
      Logger.log(`Processing PDF directly: ${sanitizedFileName}`);
      const pageCount = getPageCountUsingGoogleDrive(originalBlob);

      if (ENABLE_PDF_SPLITTING && pageCount > PAGE_LIMIT) {
        const splitPdfs = splitPdfUsingApi2Pdf(originalBlob, sanitizedFileName, pageCount);
        Logger.log(`Split PDFs created: ${splitPdfs.length}`);
        return splitPdfs;
      }

      Logger.log(`PDF does not require splitting. Pages: ${pageCount}`);
      return [originalBlob];
    } else {
      throw new Error('Attachment is not a valid PDF.');
    }
  } catch (error) {
    Logger.log(`Error transforming and splitting attachment: ${error.message}`);
    sendErrorNotification('Error transforming attachment', error.message);
    addErrorLabel(thread);
    return null;
  }
}

/**
 * Determines the number of pages in a PDF using Google Drive.
 */
function getPageCountUsingGoogleDrive(pdfBlob) {
  try {
    const tempFile = DriveApp.createFile(pdfBlob);
    const pdfContent = tempFile.getBlob().getDataAsString();
    const pages = (pdfContent.match(/\/Contents/g) || []).length;

    Logger.log(`Detected ${pages} pages in the PDF.`);
    tempFile.setTrashed(true); // Cleanup
    return pages;
  } catch (error) {
    Logger.log(`Error getting page count: ${error.message}`);
    throw new Error('Failed to determine page count.');
  }
}

/**
 * Splits a PDF into smaller parts using Api2Pdf.
 */
function splitPdfUsingApi2Pdf(pdfBlob, baseFileName, pageCount) {
  const blobs = [];
  try {
    // Upload PDF to Google Drive to get a shareable URL
    const pdfFile = DriveApp.createFile(pdfBlob);
    const pdfUrl = pdfFile.getUrl(); // This should return a proper URL

    for (let startPage = 0; startPage < pageCount; startPage += PAGE_LIMIT) {
      const endPage = Math.min(startPage + PAGE_LIMIT - 1, pageCount - 1); // Updated to handle zero-based index

      const payload = {
        url: pdfUrl, // Use the shareable URL
        start: startPage,
        end: endPage,
        inline: true, // Ensure the PDF is processed inline
        fileName: `${baseFileName}_Pages_${startPage + 1}_to_${endPage + 1}.pdf`, // Friendly file name
      };

      const options = {
        method: 'POST',
        contentType: 'application/json',
        muteHttpExceptions: true,
        payload: JSON.stringify(payload),
      };

      // Endpoint with API key as a query parameter
      const endpoint = `https://v2.api2pdf.com/pdfsharp/extract-pages?apikey=${API2PDF_KEY}`;
      Logger.log(`Sending request to: ${endpoint}`);
      const response = UrlFetchApp.fetch(endpoint, options);
      const jsonResponse = JSON.parse(response.getContentText() || '{}');

      Logger.log(`Api2Pdf Response Code: ${response.getResponseCode()}`);
      Logger.log(`Api2Pdf Response Body: ${JSON.stringify(jsonResponse, null, 2)}`);

      if (jsonResponse.Success && jsonResponse.FileUrl) {
        const blob = UrlFetchApp.fetch(jsonResponse.FileUrl).getBlob();
        blob.setName(`${baseFileName}_Pages_${startPage + 1}_to_${endPage + 1}.pdf`);
        blobs.push(blob);
      } else {
        throw new Error(`Api2Pdf error: ${jsonResponse.Error || 'Unknown error'}`);
      }
    }
  } catch (error) {
    Logger.log(`Error splitting PDF using Api2Pdf: ${error.message}`);
    sendErrorNotification('Error splitting PDF using Api2Pdf', error.message);
    return [];
  }
  return blobs;
}

/**
 * Sends error notifications.
 */
function sendErrorNotification(subject, body) {
  GmailApp.sendEmail(ERRORNOTIFICATIONEMAIL, `ERROR: ${subject}`, body);
}

/**
 * Adds a "Processed" label to threads.
 */
function addProcessedLabel(threads) {
  const label = GmailApp.createLabel(MY_LABEL);
  threads.forEach(thread => thread.addLabel(label));
}

/**
 * Adds an "Error Processing" label to threads.
 */
function addErrorLabel(thread) {
  const label = GmailApp.createLabel(ERROR_LABEL);
  thread.addLabel(label);
}

/**
 * Sends an email with attachments.
 */
function sendEmailWithAttachments(subject, attachments, originalSubject, senderEmail, threadId) {
  GmailApp.sendEmail(FIXEDDESTINATIONEMAIL, subject, `Processed email from ${senderEmail} (Thread ID: ${threadId})\n\nOriginal Subject: ${originalSubject}`, {
    attachments: attachments,
  });
}

// 补充原代码缺失的transformSubject函数
function transformSubject(subject) {
  return subject.replace(`@${EMAIL_DOMAIN}`, '').trim();
}

另外我注意到原代码中transformSubject函数是缺失的,所以自己补充了一个简单的实现,不确定是否会影响流程,但核心问题还是Api2PDF的调用错误。期待大家的指点,谢谢!

备注:内容来源于stack exchange,提问作者Kyle Arndt

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.14 15:13:00