node-imap(译)

node.js版的 IMAP 客户端模块。

描述

node-imap 是一个 node.js版本的 IMAP 客户端模块。

它会自动解码 邮件 内容/附件 或解析邮件地址吗?答案是否定的, 此模块不执行任这些魔法代码。(node-imap会原封不动地保留所有邮件头的值)。

node-imap 的0.7.x 升级到 v0.8.x 可以看这里

准备

  • node.js-- v0.8.0 或者更高版本
    • 注意: node v 0.8.x 的用户也是支持的,因为readable-stream 模块不需要更新就能用(对比 node v0.10 streams2 的实现)。
  • 一台可连接的IMAP server --tested width gmail

安装

npm install  

示例

获取邮箱中前三封邮件的 ‘日期’,‘发件人’,‘收件人’,‘主题’,消息头,和消息体。

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

var imap = new Imap({  
  user: 'mygmailname@gmail.com',
  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));
      });
      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();  

又一个demo: 检索‘发件人’ header 并且将最新的邮件的消息体整体转成二进制。

// 继续使用上面一个例子中的变量和函数 ...

openInbox(function(err, box) {  
  if (err) throw err;
  var f = imap.seq.fetch(box.messages.total + ':*', { bodies: ['HEADER.FIELDS (FROM)','TEXT'] });
  f.on('message', function(msg, seqno) {
    console.log('Message #%d', seqno);
    var prefix = '(#' + seqno + ') ';
    msg.on('body', function(stream, info) {
      if (info.which === 'TEXT')
        console.log(prefix + 'Body [%s] found, %d total bytes', inspect(info.which), info.size);
      var buffer = '', count = 0;
      stream.on('data', function(chunk) {
        count += chunk.length;
        buffer += chunk.toString('utf8');
        if (info.which === 'TEXT')
          console.log(prefix + 'Body [%s] (%d/%d)', inspect(info.which), count, info.size);
      });
      stream.once('end', function() {
        if (info.which !== 'TEXT')
          console.log(prefix + 'Parsed header: %s', inspect(Imap.parseHeader(buffer)));
        else
          console.log(prefix + 'Body [%s] Finished', inspect(info.which));
      });
    });
    msg.once('attributes', function(attrs) {
      console.log(prefix + 'Attributes: %s', inspect(attrs, false, 8));
    });
    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();
  });
});

又一个demo: 保存2010年5月20日以后的未读邮件到文件:

//继续使用上面一个例子中的变量和函数 ...

var fs = require('fs'), fileStream;

openInbox(function(err, box) {  
  if (err) throw err;
  imap.search([ 'UNSEEN', ['SINCE', 'May 20, 2010'] ], function(err, results) {
    if (err) throw err;
    var f = imap.fetch(results, { bodies: '' });
    f.on('message', function(msg, seqno) {
      console.log('Message #%d', seqno);
      var prefix = '(#' + seqno + ') ';
      msg.on('body', function(stream, info) {
        console.log(prefix + 'Body');
        stream.pipe(fs.createWriteStream('msg-' + seqno + '-body.txt'));
      });
      msg.once('attributes', function(attrs) {
        console.log(prefix + 'Attributes: %s', inspect(attrs, false, 8));
      });
      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();
    });
  });
});

API

数据类型

  • MessageSource 可以是单条的邮件id,邮件id 范围(例如‘2504:2507’或者‘*’或者‘2504:*’),邮件id数组,邮件id范围的数组。
  • Box 是一个对象,表示当前打开的邮箱,它有以下属性。

    • name -string- 邮箱的名称。
    • readOnly -boolean- 如果此邮箱以只读模式打开,则为true(只适用于openBox()调用)
    • newKeywords -boolean- 如果新的关键字可以添加到邮箱中的邮件则为true。
    • uidvalidity - integer - 32位整数,可用于确定自上次打开此邮箱以来此邮箱中的UID是否已更改。
    • uidnext - integer - 这个uid 将被分配给到达此邮箱的下一个消息。
    • flags - array - 一个列表,此列表包含系统定义的适用于此邮箱的标志。这个列表中的flag如果不在permFlags中,可能只为当前会话存储。额外的服务器特定的flags可能也是适用的。
    • persistentUIDs: - boolean - 这个邮箱是否有持续的UID。在现代邮箱中这个值基本上总是true,只有一些老的邮箱因为技术上的不支持导致其值为false.
    • message - object - 包含邮箱中各类邮件的计数。
      • total - integer - 邮件总数。
      • new - integer - 邮箱中拥有‘当前’(这个IMAP 会话首次看到这些消息)这个标记的邮件数量。
      • unseen - integer - (只有 status()的回调函数中能用)邮件总数。邮箱中不带'已读'标记的所有邮件的数量。
  • ImapMessage 一个代表着邮件消息的对象,包含如下信息:

    • Events:
      • body (<可读数据流>stream, <对象> 信息) 为每个请求体发送的事件。例如info属性就有下面这些。
        • which - string - body 的说明符。
        • size - integer - body 的大小小(bytes)。
      • attributes( < object > 属性集) - 消息的所有属性都收集到了的时候则会发送这个对象。例如有以下属性:
        • uid - integer - 32位的id号,此邮件在邮箱中的唯一 标识。
        • flags - array - 此消息当前的标签集。
        • date- Date- 此消息的内部服务器的日期。
        • struct - array - 此消息的主体结构(只在使用fetch() 获取时才会设置)可查看下面这个属性这种方式的解释。
        • size - integer - RFC822 消息的大小(只在使用fetch() 获取时才会设置)。
      • end() - 当所有属性和消息体被解析后触发。
  • ImapFetch 一个表示fetch()请求的对象。它包含:

    • Events
      • message( msg, seqno) 为每一个从fetch 请求拿到的消息结果触发。
      • error(err) - 有错误时触发。
      • end() - 所有消息解析后触发。

一条由多部分组成的消息的结构可能会是下面这样:

[ { type: 'mixed',
    params: { boundary: '000e0cd294e80dc84c0475bf339d' },
    disposition: null,
    language: null,
    location: null
  },
  [ { type: 'alternative',
      params: { boundary: '000e0cd294e80dc83c0475bf339b' },
      disposition: null,
      language: null
    },
    [ { partID: '1.1',
        type: 'text',
        subtype: 'plain',
        params: { charset: 'ISO-8859-1' },
        id: null,
        description: null,
        encoding: '7BIT',
        size: 935,
        lines: 46,
        md5: null,
        disposition: null,
        language: null
      }
    ],
    [ { partID: '1.2',
        type: 'text',
        subtype: 'html',
        params: { charset: 'ISO-8859-1' },
        id: null,
        description: null,
        encoding: 'QUOTED-PRINTABLE',
        size: 1962,
        lines: 33,
        md5: null,
        disposition: null,
        language: null
      }
    ]
  ],
  [ { partID: '2',
      type: 'application',
      subtype: 'octet-stream',
      params: { name: 'somefile' },
      id: null,
      description: null,
      encoding: 'BASE64',
      size: 98,
      lines: null,
      md5: null,
      disposition:
       { type: 'attachment',
         params: { filename: 'somefile' }
       },
      language: null,
      location: null
    }
  ]
]

上述结构描述了一则带有一个附件和两种形式(纯文本和HTML)的邮件的消息体。每个消息的每一部分都是由一个"partID"来区分的,在你想获取这一部分内容的时候它就有用了。(查看 fetch())。

只拥有一个部分的消息,其结构看起来会是这样:

[ { partID: '1',
    type: 'text',
    subtype: 'plain',
    params: { charset: 'ISO-8859-1' },
    id: null,
    description: null,
    encoding: '7BIT',
    size: 935,
    lines: 46,
    md5: null,
    disposition: null,
    language: null
  }
]

因此检查一个消息是不是由多个部件构成的最简单的办法 是检查其结构的 length 是不是大于1。

最后,下面这些是系统标识 RFC3501标准 的 flag ,可能 有所添加/移除:

  • \Seen - 已读消息。
  • \Answered - 已回复消息。
  • \Flagged - 已标记为紧急/重要的消息。
  • \ Deleted - 已经标记为删除的消息。
  • \ Draft - 未完成的消息(标记为草稿)。

尽管 IMAP服务器要以限制为那些给定的消息永久地打上哪些flag ,但是仍然要注意: 如果有疑问,首先检查邮箱中的permFlags标记 。另外,用户自定义的flag 可以由服务器提供。如果可行的话,这些邮件都会在邮箱的 permFlasgs列表中展示出来 。

require('imap') 返回一个对象: Connection0

Connection 事件

  • ready() - 当与邮件服务成功连接并且权限验证通过时会执行。
  • alert( message) 当邮件服务有问题(例如'服务器正在维护')需要alert时触发。
  • mail ( numNewMsgs) 当新邮件到达当前打开的邮箱时触发。
  • expunge seqno) - 当外部删除了一则消息时触发。seqno 就是删除消息的序列号(而不是唯一的UID),如果你是缓存序列号,所有高于这个值 的序列号都 要减1 ,这是为了保持与服务器同步,并且保持正确的连续性。
  • uidvalidity(< integer >uidvalidity) 如果在当前会话期间,当前打开的邮箱的UID 有效值发生变化触发。
  • update(< integer >seqno, < object >info) - 当消息的元数据在外部发生改变时触发(例如 flags)。
  • error (< Error >err) - 当发生错误时触发。 ‘source’属性将被用于展示错误源自哪里。
  • close(< boolean >hadError) - 连接已经完全关闭时触发。
  • end() - 连接终止时触发。

Connection 属性

  • state - string - 连接的当前状态(例如 'disconnected', 'connected', 'authenticated')。
  • delimiter - string - (顶级)邮箱层次分隔符。如果服务器不支持邮箱等级,只有平坦的列表,这个值将是falsey。
  • namespaces- object - 包含每个命名空间类型的信息(如果服务器支持的)。有以下属性:
    • personal - array - 属于登录用户的邮箱。
    • other - array - 属于登录用户拥有的其他用户的邮箱。
    • shared - array - 任何登录进来的用户都有权限访问的邮箱。 在个人的命名空间列表中,至少应该有一个(尽管IMAP规范允许有多个,这个做法不常见)带有空白命名空间前缀的入口。每个属性的数组包含下列对象格式(示例值):
{ prefix: '', // 一个字符串。包含用于访问这个命名空间下邮箱的前缀。
  delimiter: '/', // 一个字符串。包含这个名称空间的层次结构分隔符,或boolean false。
                 // 无层级的扁平命名空间。
  extensions: [ // 命名空间支持的扩展名组成的数组,或者没有指定的 null
    { name: 'X-FOO-BAR', // 表示扩展名的字符串。
      params: [ 'BAZ' ] // 字符串数组,这些字符串中包含扩展参数 或者没有指定用null
    }
  ]
}

Connection 静态方法

  • parseHeader (< string >rawHeader[, < boolean >disableAutoDecode]) - object - 解析原始header并返回一个对象,key就是headers中字段名,value就是字段的值。disableAutoDecode设置为true来禁用自动解码中可能存在的MIME encoded-words头字段值。

Connection 实例方法

注意:Message UID 的范围不能保证是连续的。

  • (constructor)([< object >config]) - Connection - 用下列配置对象创建并返回一个连接实例。可用的配置项属性有:
    • user - string - 纯文本认证的用户名。
    • password - string - 纯文本认证的密码。 xoauth2 - string - 服务器的 base64转码的OAuth2 token SASL验证机制 (Andris Reinman的 xoauth2模块可以帮助生成这个字符串)
    • host - string - IMAP 服务器的主机名或者IP地址。默认为"localhost"
    • port - integer - IMAP 服务器的端口,默认为143.
    • tls - boolean - 是否执行隐式的TLS连接。默认为false。
    • tlsOptions - object -传给tls.connect()函数的选项。默认为none。
    • autotls - string - Set to 'always' to always attempt connection upgrades via STARTTLS, 'required' only if upgrading is required, or 'never' to never attempt upgrading. Default: 'never'
    • connTimeout - integer - 等待连接的毫秒数. Default: 10000
    • authTimeout - integer - 等待连接后多少毫秒认证通过. Default: 5000
    • socketTimeout - integer - IMAP server 的socket连接超时毫秒数. 如果没设置, socket 将没有超时时间. Default: 0
    • keepalive - mixed - 配置keepalive机制. 默认设置为true 允许keepalive,或者设置为一个对象来配置keepalive.
      • interval - integer - 用毫秒来表示的无操作时发送检查(NOOP)的时间间隔。 at which NOOPs are sent and the interval at which idleInterval is checked. 默认: 10000
      • idleInterval - integer - 用毫秒数表示的时间段,在此 期间IDLE指令(如果服务器支持)被发送出去的。默认值 300000 (5 mins)。
      • forceNoop - boolean - 在服务器端设置为true强制使用NOOP 仍然支持IDLE. 默认: false。
    • debug - function - 如果设置了, 该函数会被调用,传入一个String参数包含了一些调试信息。默认: (no debug output)。
  • connect() - (void) - 试图连接并验证IMAP 服务器。
  • end() - (void) - 队列中所有的请求都发出后关闭连接。
  • destroy() - (void) - 立即销毁连接着的服务。
  • openBox(< string >mailboxName[, < boolean >openReadOnly=false[, < object >modifiers]], < function >callback) - (void) 打开一个服务器上存在的特定的邮箱。mailboxName 应该包含必要的 前缀/路径. modifiers 用于IMAP扩展。callback 有 2 个参数 < Error >err, < Box >mailbox.
  • closeBox([< boolean >autoExpunge=true, ]< function >callback) - (void) - 关闭当前打开的邮箱. 如果 autoExpunge 为 true, 邮箱 打开方式不是只读的情况下,当前打开的邮箱中标记为deleted 的邮件会被删除. 如果 autoExpunge 为 false, 无论你关闭还是打开另一个邮箱,标记为Deleted的邮件不会从当前邮箱中移除掉. 回调函数有 1 个参数: < Error >err.
  • addBox(< string >mailboxName, < function >callback) - (void) - 在服务器上创建一个邮箱. mailboxName s应该包含必要的 前缀/路径. 回调函数有 1 个参数: < Error >err.
  • delBox(< string >mailboxName, < function >callback) - (void) - 删除打开一个服务器上存在的特定的邮箱. mailboxName s应该包含必要的 前缀/路径. 回调函数有 1 个参数: < Error >err.
  • renameBox(< string >oldMailboxName, < string >newMailboxName, < function >callback) - (void) - 重命名一个服务器上存在的特定的邮箱. oldMailboxName 和newMailboxName 都应该包含必要的 前缀/路径. 回调函数有 2 个参数: < Error >err, < Box >mailbox。注意: 重命名'INBOX' 邮件箱反而会导致所有'INBOX' 中的邮箱移动到新的 mailbox.
  • subscribeBox(< string >mailboxName, < function >callback) - (void) - 订阅一个服务器上存在的特定的邮箱. mailboxName s应该包含必要的 前缀/路径. 回调函数有 1 个参数: < Error >err.
  • unsubscribeBox(< string >mailboxName, < function >callback) - (void) - 取消订阅一个服务器上存在的特定的邮箱. mailboxName s应该包含必要的 前缀/路径. 回调函数有 1 个参数: < Error >err.
  • status(< string >mailboxName, < function >callback) - (void) - 获取邮箱信息,不是当前打开的邮箱.回调函数有2个参数: < Error >err, < Box >mailbox. 注意:不能保证这在服务器上是一个快速的操作。而且, 不要在当前打开的邮箱上调用此方法。
  • getBoxes (< string >mailboxName, < function >callback) - (void) - 获取邮箱的完整列表。如果nfPrefix没有指定,将使用主要的个人命名空间。回调函数有2个参数: < Error >err, < object >boxes. boxes 有如下结构 (with example values):
{ INBOX: // mailbox name
   { attribs: [], // mailbox attributes. An attribute of 'NOSELECT' indicates the mailbox cannot
                  // be opened
     delimiter: '/', // hierarchy delimiter for accessing this mailbox's direct children.
     children: null, // an object containing another structure similar in format to this top level,
                    // otherwise null if no children
     parent: null // pointer to parent mailbox, null if at the top level
   },
  Work:
   { attribs: [],
     delimiter: '/',
     children: null,
     parent: null
   },
  '[Gmail]':
   { attribs: [ '\\NOSELECT' ],
     delimiter: '/',
     children:
      { 'All Mail':
         { attribs: [ '\\All' ],
           delimiter: '/',
           children: null,
           parent: [Circular]
         },
        Drafts:
         { attribs: [ '\\Drafts' ],
           delimiter: '/',
           children: null,
           parent: [Circular]
         },
        Important:
         { attribs: [ '\\Important' ],
           delimiter: '/',
           children: null,
           parent: [Circular]
         },
        'Sent Mail':
         { attribs: [ '\\Sent' ],
           delimiter: '/',
           children: null,
           parent: [Circular]
         },
        Spam:
         { attribs: [ '\\Junk' ],
           delimiter: '/',
           children: null,
           parent: [Circular]
         },
        Starred:
         { attribs: [ '\\Flagged' ],
           delimiter: '/',
           children: null,
           parent: [Circular]
         },
        Trash:
         { attribs: [ '\\Trash' ],
           delimiter: '/',
           children: null,
           parent: [Circular]
         }
      },
     parent: null
   }
}
  • getSubscribedBoxes([< string >nsPrefix, ]< function >callback) - (void) - 获得订阅邮箱的完整列表。如果没有指定nsPrefix,将使用主要的个人名称空间。callback有两个参数:< Error >err, < object >boxes。boxes 拥有和上面getBoxes相同的格式。
  • expunge([< MessageSource >uids, ]< function >callback) - (void) - 将当前打开的邮箱中标记为Deleted的所有邮件永久性移除。如果服务器支持'UIDPLUS'的能力,uids可以用于只移除那些不仅有Deleted标记,而且uid在uids列表中的邮件。回调函数有一个参数: < Error >err. 注意:至少在Gmail邮箱上,在当前打开的除垃圾箱以外的邮箱上执行这个操作会将这些邮件存档标记为已经删除 (通过移动到“所有邮件”邮箱)。
  • append(< mixed >msgData, [< object >options, ]< function >callback) - (void) - 附加信息到选定的邮箱。msgData 是一个字符串或者Buffer,包含着一条 RFC-822 兼容的MIME消息。有效的的options属性有:

    • mailbox - string - 邮箱的名称附加信息。默认值:当前打开的邮箱。
    • flags - mixed - 一个简单的标志(例如 'Seen')或者数组 (例如 ['Seen', 'Flagged'])附加到消息上。默认: (无flags)
    • date - Date -用于消息到达日期/时间。默认: (当前的 date/time)

    回调函数有 1个参数: < Error >err. 未完待续

licat

继续阅读此作者的更多文章