使用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
相关产品推荐
相关产品推荐

