javascript 如何使用 node-imap 读取和保存附件
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/25247207/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to read and save attachments using node-imap
提问by Christiaan Westerbeek
I'm using node-imapand I can't find a straightforward code example of how to save attachments from emails fetched using node-imapto disk using fs.
我正在使用node-imap,但找不到一个简单的代码示例,说明如何使用 fs 将使用node-imap获取的电子邮件中的附件保存到磁盘。
I've read the documentation a couple of times. It appears to me I should do another fetch with a reference to the specific part of a message being the attachment. I started of with the basic example:
我已经阅读了几次文档。在我看来,我应该使用对作为附件的邮件的特定部分的引用进行另一次提取。我从基本示例开始:
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();
And this example works. This is the output with the attachment part:
这个例子有效。这是带有附件部分的输出:
[ { 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 } ],
How do I read that file and save it to disk using node's fs module?
如何使用节点的 fs 模块读取该文件并将其保存到磁盘?
回答by Christiaan Westerbeek
I figured it out thanks to help of @arnt and mscdex. Here's a complete and working script that streams all attachments as files to disk while base64 decoding them on the fly. Pretty scalable in terms of memory usage.
感谢 @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();
回答by user2276686
based Christiaan Westerbeek
基于 Christiaan Westerbeek
changed: 1. use =>, forEach; 2. the 2nd fetch don't need "struct".
更改: 1. 使用 =>, forEach; 2.第二次提取不需要“结构”。
Problem:
问题:
In some cases, the attachment's filename SHOLUD be attachment.disposition.params['filename*']. Please see "RFC2231 MIME Parameter Value and Encoded Word Extensions" & here.
在某些情况下,附件的文件名应该是attachment.disposition.params['filename*']。请参阅“RFC2231 MIME 参数值和编码字扩展”和此处。
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()