javascript 如何使用node-imap读取和保存附件

0qx6xfy6  于 2023-09-29  发布在  Java
关注(0)|答案(5)|浏览(181)

我正在使用node-imap,我找不到一个简单的代码示例来说明如何使用fs将使用node-imap获取的电子邮件附件保存到磁盘。
我已经读过几遍文档了。在我看来,我应该用一个对消息的特定部分(附件)的引用来做另一个获取。我从一个基本的例子开始:

var Imap = require('imap'),
    inspect = require('util').inspect;

var imap = new Imap({
  user: '[email protected]',
  password: 'mygmailpassword',
  host: 'imap.gmail.com',
  port: 993,
  tls: true
});

function openInbox(cb) {
  imap.openBox('INBOX', true, cb);
}

imap.once('ready', function() {
  openInbox(function(err, box) {
    if (err) throw err;
    var f = imap.seq.fetch('1:3', {
      bodies: 'HEADER.FIELDS (FROM TO SUBJECT DATE)',
      struct: true
    });
    f.on('message', function(msg, seqno) {
      console.log('Message #%d', seqno);
      var prefix = '(#' + seqno + ') ';
      msg.on('body', function(stream, info) {
        var buffer = '';
        stream.on('data', function(chunk) {
          buffer += chunk.toString('utf8');
        });
        stream.once('end', function() {
          console.log(prefix + 'Parsed header: %s', inspect(Imap.parseHeader(buffer)));
        });
      });
      msg.once('attributes', function(attrs) {
        console.log(prefix + 'Attributes: %s', inspect(attrs, false, 8));

        //Here's were I imagine to need to do another fetch for the content of the message part...

      });
      msg.once('end', function() {
        console.log(prefix + 'Finished');
      });
    });
    f.once('error', function(err) {
      console.log('Fetch error: ' + err);
    });
    f.once('end', function() {
      console.log('Done fetching all messages!');
      imap.end();
    });
  });
});

imap.once('error', function(err) {
  console.log(err);
});

imap.once('end', function() {
  console.log('Connection ended');
});

imap.connect();

这个例子很有效。这是附件部分的输出:

[ { partID: '2',
     type: 'application',
     subtype: 'octet-stream',
     params: { name: 'my-file.txt' },
     id: null,
     description: null,
     encoding: 'BASE64',
     size: 44952,
     md5: null,
     disposition:
      { type: 'ATTACHMENT',
        params: { filename: 'my-file.txt' } },
     language: null } ],

如何使用node的fs模块读取该文件并将其保存到磁盘?

nkhmeac6

nkhmeac61#

感谢@arnt和mscdex的帮助。这里有一个完整的工作脚本,它将所有附件作为文件流到磁盘,同时base64动态解码它们。在内存使用方面相当可伸缩。

var inspect = require('util').inspect;
var fs      = require('fs');
var base64  = require('base64-stream');
var Imap    = require('imap');
var imap    = new Imap({
  user: '[email protected]',
  password: 'mygmailpassword',
  host: 'imap.gmail.com',
  port: 993,
  tls: true
  //,debug: function(msg){console.log('imap:', msg);}
});

function toUpper(thing) { return thing && thing.toUpperCase ? thing.toUpperCase() : thing;}

function findAttachmentParts(struct, attachments) {
  attachments = attachments ||  [];
  for (var i = 0, len = struct.length, r; i < len; ++i) {
    if (Array.isArray(struct[i])) {
      findAttachmentParts(struct[i], attachments);
    } else {
      if (struct[i].disposition && ['INLINE', 'ATTACHMENT'].indexOf(toUpper(struct[i].disposition.type)) > -1) {
        attachments.push(struct[i]);
      }
    }
  }
  return attachments;
}

function buildAttMessageFunction(attachment) {
  var filename = attachment.params.name;
  var encoding = attachment.encoding;

  return function (msg, seqno) {
    var prefix = '(#' + seqno + ') ';
    msg.on('body', function(stream, info) {
      //Create a write stream so that we can stream the attachment to file;
      console.log(prefix + 'Streaming this attachment to file', filename, info);
      var writeStream = fs.createWriteStream(filename);
      writeStream.on('finish', function() {
        console.log(prefix + 'Done writing to file %s', filename);
      });

      //stream.pipe(writeStream); this would write base64 data to the file.
      //so we decode during streaming using 
      if (toUpper(encoding) === 'BASE64') {
        //the stream is base64 encoded, so here the stream is decode on the fly and piped to the write stream (file)
        stream.pipe(base64.decode()).pipe(writeStream);
      } else  {
        //here we have none or some other decoding streamed directly to the file which renders it useless probably
        stream.pipe(writeStream);
      }
    });
    msg.once('end', function() {
      console.log(prefix + 'Finished attachment %s', filename);
    });
  };
}

imap.once('ready', function() {
  imap.openBox('INBOX', true, function(err, box) {
    if (err) throw err;
    var f = imap.seq.fetch('1:3', {
      bodies: ['HEADER.FIELDS (FROM TO SUBJECT DATE)'],
      struct: true
    });
    f.on('message', function (msg, seqno) {
      console.log('Message #%d', seqno);
      var prefix = '(#' + seqno + ') ';
      msg.on('body', function(stream, info) {
        var buffer = '';
        stream.on('data', function(chunk) {
          buffer += chunk.toString('utf8');
        });
        stream.once('end', function() {
          console.log(prefix + 'Parsed header: %s', Imap.parseHeader(buffer));
        });
      });
      msg.once('attributes', function(attrs) {
        var attachments = findAttachmentParts(attrs.struct);
        console.log(prefix + 'Has attachments: %d', attachments.length);
        for (var i = 0, len=attachments.length ; i < len; ++i) {
          var attachment = attachments[i];
          /*This is how each attachment looks like {
              partID: '2',
              type: 'application',
              subtype: 'octet-stream',
              params: { name: 'file-name.ext' },
              id: null,
              description: null,
              encoding: 'BASE64',
              size: 44952,
              md5: null,
              disposition: { type: 'ATTACHMENT', params: { filename: 'file-name.ext' } },
              language: null
            }
          */
          console.log(prefix + 'Fetching attachment %s', attachment.params.name);
          var f = imap.fetch(attrs.uid , { //do not use imap.seq.fetch here
            bodies: [attachment.partID],
            struct: true
          });
          //build function to process attachment message
          f.on('message', buildAttMessageFunction(attachment));
        }
      });
      msg.once('end', function() {
        console.log(prefix + 'Finished email');
      });
    });
    f.once('error', function(err) {
      console.log('Fetch error: ' + err);
    });
    f.once('end', function() {
      console.log('Done fetching all messages!');
      imap.end();
    });
  });
});

imap.once('error', function(err) {
  console.log(err);
});

imap.once('end', function() {
  console.log('Connection ended');
});

imap.connect();
puruo6ea

puruo6ea2#

你也可以用它为我工作。

const IMAP = require("imap");
const MailParser = require("mailparser").MailParser;
const moment = require('moment');
var fs = require('fs'), fileStream;
module.exports.imapEmailDownload = function () {
return new Promise(async (resolve, reject) => {
    try {
        const imapConfig = {
            user: '[email protected]',
            password: 'XXX@126',
            host: 'imap.gmail.com',
            port: '993',
            tls: true,
            tlsOptions: {
                secureProtocol: 'TLSv1_method'
            }
        }
        const imap = IMAP(imapConfig);

        imap.once("ready", execute);
        imap.once("error", function (err) {
            console.error("Connection error: " + err.stack);
        });

        imap.connect();

        function execute() {
            imap.openBox("INBOX", false, function (err, mailBox) {
                if (err) {
                    console.error(err);
                    return;
                }
                imap.search([["ON", moment().format('YYYY-MM-DD')]], function (err, results) {
                    if (!results || !results.length) { console.log("No unread mails"); imap.end(); return; }
                    /* mark as seen
                    imap.setFlags(results, ['\\Seen'], function(err) {
                        if (!err) {
                            console.log("marked as read");
                        } else {
                            console.log(JSON.stringify(err, null, 2));
                        }
                    });*/
                    var f = imap.fetch(results, { bodies: "" });
                    f.on("message", processMessage);
                    f.once("error", function (err) {
                        return Promise.reject(err);
                    });
                    f.once("end", function () {
                        imap.end();
                    });
                });
            });
        }

        function processMessage(msg, seqno) {
          
            var parser = new MailParser({ streamAttachments: true });
            parser.on("headers", function (headers) {
            });

            parser.on('data', data => {
                if (data.type === 'text') {
                    console.log(seqno);
                    console.log(data.text);  /* data.html*/
                }

            });
            let data = ""
            msg.on("body", function (stream) {
                stream.on("data", function (chunk) {
                    data = data + chunk.toString("utf8");
                    parser.write(chunk.toString("utf8"));
                });
                stream.on("end", (chunk) => {
                })
            });

            parser.on('attachment', async function (attachment, mail) {
                let filepath = './download/';
                let output = fs.createWriteStream(filepath + attachment.fileName);
                
                attachment.stream.pipe(output).on("end", function () {
                    console.log("All the data in the file has been read");
                }).on("close", function (err) {
                    console.log("Stream has been cloesd.");
                });

            });

            msg.once("end", function () {
                // console.log("Finished msg #" + seqno);
                parser.end();
            });
        }
        resolve();
    } catch (error) {
        console.log("error", error);
        reject(error);
    }
});};
njthzxwz

njthzxwz3#

作者:Christiaan Westerbeek
change:1. use =>,forEach; 2.第二次fetch不需要“struct”。
问题:
在某些情况下,附件的文件名SHOLUD为attachment.disposition.params“文件名”]。请参阅“RFC 2231 MIME参数值和编码字扩展”& here

const fs = require('fs')
const base64 = require('base64-stream')
const Imap = require('imap')

const imap = new Imap({
  user: '[email protected]',
  password: 'XXXXX',
  host: 'imap.126.com',
  port: 993,
  tls: true /*,
  debug: (msg) => {console.log('imap:', msg);} */
});

function toUpper(thing) { return thing && thing.toUpperCase ? thing.toUpperCase() : thing }

function findAttachmentParts(struct, attachments) {
  attachments = attachments ||  []
  struct.forEach((i) => {
    if (Array.isArray(i)) findAttachmentParts(i, attachments)
    else if (i.disposition && ['INLINE', 'ATTACHMENT'].indexOf(toUpper(i.disposition.type)) > -1) {
      attachments.push(i)
    }
  })
  return attachments
}

imap.once('ready', () => {
  // A4 EXAMINE "INBOX"
  imap.openBox('INBOX', true, (err, box) => { 
    if (err) throw err;
    // A5 FETCH 1:3 (UID FLAGS INTERNALDATE BODYSTRUCTURE BODY.PEEK[HEADER.FIELDS (SUBJECT DATE)])
    const f = imap.seq.fetch('1:3', {
      bodies: ['HEADER.FIELDS (SUBJECT)'],
      struct: true  // BODYSTRUCTURE
    }) 
    f.on('message', (msg, seqno) => {
      console.log('Message #%d', seqno)
      const prefix = `(#${seqno})`
      var header = null
      msg.on('body', (stream, info) => {
        var buffer = ''
        stream.on('data', (chunk) => { buffer += chunk.toString('utf8') });
        stream.once('end', () => { header = Imap.parseHeader(buffer) })
      });
      msg.once('attributes', (attrs) => {
        const attachments = findAttachmentParts(attrs.struct);
        console.log(`${prefix} uid=${attrs.uid} Has attachments: ${attachments.length}`);
        attachments.forEach((attachment) => {
        /* 
          RFC2184 MIME Parameter Value and Encoded Word Extensions
                  4.Parameter Value Character Set and Language Information
          RFC2231 Obsoletes: 2184
          {
            partID: "2",
            type: "image",
            subtype: "jpeg",
            params: {
    X         "name":"________20.jpg",
              "x-apple-part-url":"8C33222D-8ED9-4B10-B05D-0E028DEDA92A"
            },
            id: null,
            description: null,
            encoding: "base64",
            size: 351314,
            md5: null,
            disposition: {
              type: "inline",
              params: {
    V           "filename*":"GB2312''%B2%E2%CA%D4%B8%BD%BC%FE%D2%BB%5F.jpg"
              }
            },
            language: null
          }   */            
          console.log(`${prefix} Fetching attachment $(attachment.params.name)`)
          console.log(attachment.disposition.params["filename*"])
          const filename = attachment.params.name  // need decode disposition.params['filename*'] !!!
          const encoding = toUpper(attachment.encoding)
          // A6 UID FETCH {attrs.uid} (UID FLAGS INTERNALDATE BODY.PEEK[{attachment.partID}])
          const f = imap.fetch(attrs.uid, { bodies: [attachment.partID] })
          f.on('message', (msg, seqno) => {
            const prefix = `(#${seqno})`
            msg.on('body', (stream, info) => {
              const writeStream = fs.createWriteStream(filename);
              writeStream.on('finish', () => { console.log(`${prefix} Done writing to file ${filename}`) })
              if (encoding === 'BASE64') stream.pipe(base64.decode()).pipe(writeStream)
              else stream.pipe(writeStream)
            })
            msg.once('end', () => { console.log(`${prefix} Finished attachment file${filename}`) })
          })
          f.once('end', () => { console.log('WS: downloder finish') })
        })
      })
      msg.once('end', () => { console.log(`${prefix} Finished email`); })
    });
    f.once('error', (err) => { console.log(`Fetch error: ${err}`) })
    f.once('end', () => {
      console.log('Done fetching all messages!')
      imap.end()
    })
  })
})
imap.once('error', (err) => { console.log(err) })
imap.once('end', () => { console.log('Connection ended') })
imap.connect()
cgyqldqp

cgyqldqp4#

下面的解释是从电子邮件下载附件,也有一个标志(markAsRead)设置为true只读取未读邮件,并为下载所有附件设置为false。
仅获取未读/未查看的电子邮件:你必须在回调中 Package fetching调用以搜索一个,像这样:

imap.search(
      ['UNSEEN'],
      function(err, results) {
          // current code
      }
);

将电子邮件标记为已读:对我来说是一个棘手的问题,在第53行,打开收件箱的调用类似于:openBox('INBOX',true,function(err,box){第二个参数(true值)用于以只读模式打开收件箱。您需要将其更改为false,然后添加一个字段markSeen:第二个参数中的true:

var f = imap.seq.fetch('1:*', {
      bodies: ['HEADER.FIELDS (FROM TO SUBJECT DATE)'],
      struct: true,
      markSeen: true // <---- this is new
});

这是我现在使用的脚本,更改如下:
将邮件标记为已读:如果配置选项imapOptions.markAsRead被设置为true,它将把已处理的邮件标记为已读。文件格式:有一个配置选项(downloads.filenameFormat)可用于重命名文件。其实很简单如果您将其设置为$FILENAME或只是删除它,它将保留原始文件名。我包括它,因为人们发送的文件名称相同,但内容不同,我需要保留它们。日志:我使用simple-node-logger包添加了日志。脚本使用两个级别:debug,它显示了原始脚本和信息(更简单的日志)中的所有内容。如果您只需要它,也会使用错误级别。

const config = require('./config.json');
const markAsRead = (config.imapOptions && config.imapOptions.markAsRead) ? config.imapOptions.markAsRead : false;

const fs = require('fs');
const { Base64Decode } = require('base64-stream')

const Imap = require('imap');
const imap = new Imap(config.imap);

// Simple logger:
const logger = require('simple-node-logger').createSimpleLogger( config.logs?.simpleNodeLogger || { logFilePath:'mail-downloader.log', timestampFormat:'YYYY-MM-DD HH:mm:ss.SSS' } );
logger.setLevel(config.logs?.level || 'debug');

// var emailDate;
// var emailFrom;
function formatFilename(filename, emailFrom, emailDate) {
  // defaults to current filename:
  let name = filename;
  // if custom config is present:
  if (config.downloads) {
    // if format provided, use it to build filename:
    if (config.downloads.filenameFormat) {
      name = config.downloads.filenameFormat;
      // converts from field from "Full Name <f[email protected]>" into "fullname":
      name = name.replace('$FROM', emailFrom.replace(/.*</i, '').replace('>', '').replace(/@.*/i, ''));
      // parses text date and uses timestamp:
      name = name.replace('$DATE', new Date(emailDate).getTime());
      name = name.replace('$FILENAME', filename);
    }
    // if directory provided, use it:
    if (config.downloads.directory) name = `${config.downloads.directory}/${name}`;
  }
  // return formatted filename:
  return name;
}

function findAttachmentParts(struct, attachments) {
  attachments = attachments ||  [];
  for (var i = 0, len = struct.length, r; i < len; ++i) {
    if (Array.isArray(struct[i])) {
      findAttachmentParts(struct[i], attachments);
    } else {
      if (struct[i].disposition && ['inline', 'attachment'].indexOf(struct[i].disposition.type.toLowerCase()) > -1) {
        attachments.push(struct[i]);
      }
    }
  }
  return attachments;
}

function buildAttMessageFunction(attachment, emailFrom, emailDate) {
  const filename = attachment.params.name;
  const encoding = attachment.encoding;

  return function (msg, seqno) {
    var prefix = '(#' + seqno + ') ';
    msg.on('body', function(stream, info) {
      //Create a write stream so that we can stream the attachment to file;
      logger.debug(prefix + 'Streaming this attachment to file', filename, info);
      var writeStream = fs.createWriteStream(formatFilename(filename, emailFrom, emailDate));
      writeStream.on('finish', function() {
        logger.debug(prefix + 'Done writing to file %s', filename);
      });

      //so we decode during streaming using 
      if (encoding.toLowerCase() === 'base64') {
        //the stream is base64 encoded, so here the stream is decode on the fly and piped to the write stream (file)
        stream.pipe(new Base64Decode()).pipe(writeStream)
      } else  {
        //here we have none or some other decoding streamed directly to the file which renders it useless probably
        stream.pipe(writeStream);
      }
    });
    msg.once('end', function() {
      logger.debug(prefix + 'Finished attachment %s', filename);
      logger.info(`Attachment downloaded: ${filename}`)
    });
  };
}

imap.once('ready', function() {
  logger.info('Connected');
  imap.openBox('INBOX', !markAsRead, function(err, box) {

    if (err) throw err;
    imap.search(
      ['UNSEEN'],
      function(err, results) {
        if (err) throw err;

        if (!results.length) {
          // if now unread messages, log and end connection:
          logger.info('No new emails found');
          imap.end();

        } else {
          logger.info(`Found ${results.length} unread emails`)
          // if unread messages, fetch and process:
          var f = imap.fetch(results, {
            bodies: ['HEADER.FIELDS (FROM TO SUBJECT DATE)'],
            struct: true,
            markSeen: markAsRead
          });
  
          f.on('message', function (msg, seqno) {
            logger.debug('Message #%d', seqno);
            const prefix = '(#' + seqno + ') ';

            var emailDate;
            var emailFrom;

            msg.on(
              'body',
              function(stream, info) {
                var buffer = '';
                stream.on('data', function(chunk) {
                  buffer += chunk.toString('utf8');
                });
                stream.once('end', function() {
                  const parsedHeader = Imap.parseHeader(buffer);
                  logger.debug(prefix + 'Parsed header: %s', parsedHeader);
                  // set to global vars so they can be used later to format filename:
                  emailFrom = parsedHeader.from[0];
                  emailDate = parsedHeader.date[0];
                  logger.info(`Email from ${emailFrom} with date ${emailDate}`);
                });
              }
            );
      
            msg.once(
              'attributes',
              function(attrs) {
                const attachments = findAttachmentParts(attrs.struct);
                logger.debug(prefix + 'Has attachments: %d', attachments.length);
                logger.info(`Email with ${attachments.length} attachemnts`);
                for (var i = 0, len=attachments.length ; i < len; ++i) {
                  const attachment = attachments[i];
                  logger.debug(prefix + 'Fetching attachment %s', attachment.params.name);
                  var f = imap.fetch(attrs.uid , {
                    bodies: [attachment.partID],
                    struct: true
                  });
                  //build function to process attachment message
                  f.on('message', buildAttMessageFunction(attachment, emailFrom, emailDate));
                }
              }
            );
      
            msg.once(
              'end',
              function() {
                logger.debug(prefix + 'Finished email');
              }
            );
          });
      
          f.once('error', function(err) {
            logger.error('Fetch error: ' + err);
          });
      
          f.once('end', function() {
            logger.info('Done fetching all messages!');
            imap.end();
          });

        }

      }
    );

  });
});

imap.once('error', function(err) {
  logger.error(err);
});

imap.once('end', function() {
  logger.info('Connection ended');
});

imap.connect();

这是一个配置文件,其中包含新选项:

{
  "imap": {
    "user": "[email protected]",
    "password": "myPassword",
    "host": "myImapServer",
    "port": 993,
    "tls": true
  },
  "imapOptions": {
    "markAsRead": false
  },
  "downloads": {
    "directory": "./downloads",
    "filenameFormat": "$DATE_$FROM_$FILENAME"
  },
  "logs": {
    "level": "info",
    "simpleNodeLogger": {
      "logFilePath": "mail-downloader.log",
      "timestampFormat": "YYYY-MM-DD HH:mm:ss.SSS"
    }
  }
}

干杯!干杯!

kuuvgm7e

kuuvgm7e5#

由于所有内容都是更新的,而不是使用pipe方法,因此您可以直接使用此方法来写入文件。`

const Imap = require("imap")
const fs = require("fs")
const { simpleParser } = require("mailparser")
      const imapConfig = {
      user: "your mail",
      password: "your password",
      host: "imap.gmail.com",
      port: 993,
      tls: true,
      tlsOptions: { rejectUnauthorized: false },
    }
    
    const getEmails = () => {
      try {
        
        const imap = new Imap(imapConfig)
        imap.once("ready", () => {
          imap.openBox("INBOX", false, () => {
            //to fetch latest 25 emails
            const currentDate = new Date()
            const daysAgo = 25
            const sinceDate = new Date(currentDate)
            sinceDate.setDate(currentDate.getDate() - daysAgo)
            imap.search([["SINCE", sinceDate]], (err, result) => {
              if (err) {
                console.error("Error while searching for messages:", err)
                imap.end()
                return
              }
              if (result.length === 0) {
                console.log("No messages to fetch.")
                imap.end()
                return
              }
              const mails = imap.fetch(result, { bodies: "" })
              console.log("Fetching emails...")
              mails.on("message", (msg) => {
                let attachments = []
                msg.on("body", (stream) => {
                  simpleParser(stream, async (err, parsed) => {
                   
                    if (err) {
                      console.log("Error while parsing", err)
                    } else {
                      attachments = parsed.attachments || []
                      // console.log("Subject:", parsed.subject)
                      // console.log("Text body:", parsed.text)
                      // console.log("attachments:", attachments)
                      if (attachments.length) {
                        attachments.forEach((attachment, index) => {
                          if (attachment?.contentType === "application/pdf") {
                            console.log("pdf file found", attachment.content)
                            let fileName = attachment?.filename
                            fileName = fileName.replace(".docx", "")
                            console.log("fileName", fileName)
                            const fileStream = fs.createWriteStream(
                              `./attachments/attachment_${index}_${fileName}`,
                              { encoding: "binary" }
                            )
                            fileStream.write(attachment.content)
    
                            // Close the file stream
                            fileStream.end()
                            
                            fileStream.on("finish", () => {
                              console.log(` Done writing to file ${fileName}`)
                            })
                          }
                        })
                      }
                    }
                  })
                })
                msg.once("attributes", (attrs) => {
                  const { uid } = attrs
                  // imap.addFlags(uid, ["\\Seen"], () => {
                  //   console.log("Here Marked as read")
                  // })
                })
              })
              mails.once("error", (ex) => {
                return Promise.reject(ex)
              })
              mails.once("end", () => {
                console.log("Done fetching all messages!")
                imap.end()
              })
            })
          })
        })
        imap.once("error", (err) => {
          console.log("error:", err)
        })
    
        imap.once("end", () => {
          console.log("Connection ended")
        })
    
        imap.connect()
      } catch (error) {
        console.log("Error while getting the email:-", error)
      }
    }
    getEmails()

`

相关问题