HTML5にてMSGファイルをドラッグ&ドロップで内容を取り込む
自作のアプリにて、顧客から飛んできたメールをタスク登録したり、メッセージの内容をサーバに登録したり、添付ファイルをBoxにアップロードしたり、これらの機能を装備する必要が出てきたので、作ってみました。
使用しているメーラーは、Outlook2019でメッセージを掴んで離すとmsg形式のファイルが生成されます。これを解析して内容を取り出し、対象顧客のタスクとして登録しつつ、添付ファイルはBoxの所定の場所にアップロードして、そのファイルパスも顧客管理のシステムに登録するのが目的です。今回もElectronをアプリの土台として利用しています。
図:こんな感じのMSGファイルビューアを作ります
リンク
今回使用するモジュール等
フォークしたもので、Node.jsで使うタイプもあったのですが、今回はこちらのサンプルを利用し、メールデータをドラッグ・アンド・ドロップで取得。ファイルについてはBox Driveを使って保存し、ファイルパスを取得し登録する一歩手前の感じまで作ります。
JSだけで出来るので、データの解析はelectronではレンダラプロセスのみで作業を行います(実際のデータの登録等はNode.js側で作業を行います)。なので、通常のJSやGoogle Apps ScriptのHTML Service側でも使えますね。
ソースコード
<html> <head> <title>MSGファイルのインポート</title> <link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons1.css"> <style type="text/css"> .info-box { color: #F5F5F5; padding: 2em; } .error-msg { background-color: #D80A0A; } .wizard-msg { background-color: #673AB7; } .field-block { padding: 1em 2em; } .field-block .field-label { font-weight: bold; } /* スクロールするDIV */ #bodyman { overflow:auto; border:1px solid #aaaaaa; width:100%; height:300px; } #dropzone { background-color: #cfc; border: solid 3px #9c9; color: #9c9; min-height: 50px; padding: 20px; text-shadow: 1px 1px 0 #fff; height:70% } #dropzone.dropover { background-color: #cff; color: #9cc; } #files:empty::before { color: #ccc; content: "(Files data will be shown here.)"; } #files img { border: solid 1px #ccc; cursor: pointer; height: auto; margin: 0 10px; max-height: 128px; max-width: 128px; width: auto; } .header{ background:#000; box-shadow:0 2px 8px rgba(30,30,80,.3); left:0; line-height:1; position:fixed; top:0; width:100%; z-index:24; padding:5px; } </style> <script> var $ = jQuery = require("jquery") </script> </head> <body> <div class="msg-example"> <div class="header" style="display: none;"> <button onClick='sendmsgdata()' id="sendarray" class="action" title='取り込みデータを登録します'>データ登録</button> <button onClick='clearmsg()' id="msgreset" class="action" title='取り込みメッセージをクリアします'>クリア</button> </div> <br><hr> <div class="info-box wizard-msg" id="dropman"> <div id="dropzone" effectAllowed="move">.msgファイルをここに入れる</div> </div> <div class="msg-info" style="display: none;"> <div class="field-block"> <div class="field-label">ファイル名:</div> <div class="msg-file-name"></div> </div> <div class="field-block"> <div class="field-label">送信元:</div> <div class="msg-from"></div> </div> <div class="field-block"> <div class="field-label">宛先:</div> <div class="msg-to"></div> </div> <div class="field-block" style="display: none;" id="ccman"> <div class="field-label">Cc送信先:</div> <div class="msg-cc"></div> </div> <div class="field-block"> <div class="field-label">受信日付:</div> <div class="msg-date"></div> </div> <div class="field-block"> <div class="field-label">メールタイトル:</div> <div class="msg-subject"></div> </div> <div class="field-block"> <div class="field-label">本文:</div> <div class="msg-body" id="bodyman"></div> </div> <div class="field-block" style="display: none;" id="attachman"> <div class="field-label">添付ファイル:</div> <div class="msg-attachment"></div> </div> </div> </div> <div class="incorrect-type info-box error-msg" style="display: none;"> ドラッグされたファイルはmsg形式のファイルではないようです。 </div> <script src="js/DataStream.js"></script> <script src="js/msg.reader.js"></script> <script> //メッセージ情報を格納する連想配列 var message = {}; //取り込みメッセージをクリアする function clearmsg(){ //配列初期化 message = {}; //メッセージエリアとヘッダーを非表示 $('.msg-info').hide(); $('.header').hide(); $('#ccman').hide(); $('#attachman').hide(); //ドロップエリアを表示 $('.info-box').show(); $('.incorrect-type').hide(); } //FileAPIがサポートされているかどうかの判定 function isSupportedFileAPI() { return window.File && window.FileReader && window.FileList && window.Blob; } function formatEmail(data) { //HTML表示用に特殊文字でくくって表示 return data.name ? data.name + " <" + data.email + ">" : data.email; } //メールヘッダーを取得する function parseHeaders(headers) { var parsedHeaders = {}; if (!headers) { return parsedHeaders; } var headerRegEx = /(.*)\: (.*)/g; while (m = headerRegEx.exec(headers)) { parsedHeaders[m[1]] = m[2]; } return parsedHeaders; } //メールの日付を取得する function getMsgDate(rawHeaders) { var headers = parseHeaders(rawHeaders); if (!headers['Date']){ return '-'; } return new Date(headers['Date']); } //msgファイルを取得したら動くメインルーチン $(function () { //D&Dエリアを取得する var elDrop = document.getElementById('dropzone'); //D&Dイベントをつける elDrop.addEventListener('dragover', function(event) { event.preventDefault(); event.dataTransfer.dropEffect = 'copy'; showDropping(); }); elDrop.addEventListener('dragleave', function(event) { hideDropping(); }); function showDropping() { elDrop.classList.add('dropover'); } function hideDropping() { elDrop.classList.remove('dropover'); } //ヘッダ情報を配列に変換する関数 function header2array(email){ var emailArray = email.split("\n"); var header = ''; var inHeader = 1; for (var loopKey in emailArray) { var line = emailArray[loopKey].replace(/\r/g, ";;"); if (line == "") { inHeader = 0; } if (line == "") { inHeader = 0; } if (inHeader == 1) { header += line + "\r\n"; } } //カンマ区切りを配列にする var array = header.split(';;'); return array; }; var Base64 = { encode: function(str) { return window.btoa(unescape(encodeURIComponent(str))); }, decode: function(str) { return decodeURIComponent(escape(window.atob(str))); } }; //ファイルがドロップされたらスタート elDrop.addEventListener('drop', function(event) { event.preventDefault(); hideDropping(); //配列を初期化 message = {}; //ドロップされたファイルを受け取る var files = event.dataTransfer.files; //1個目のファイル情報を取得 var selectedFile = files[0]; //ドロップされたファイルの拡張子が.msgかどうか判定 try{ if (selectedFile.name.indexOf('.msg') == -1) { $('.msg-info').hide(); $('.incorrect-type').show(); return; } }catch(e){ $('.msg-info').hide(); $('.incorrect-type').show(); } //ファイル名を取得して表示 $('.msg-example .msg-file-name').html(selectedFile.name); $('.incorrect-type').hide(); message.filename = selectedFile.name; //ファイルの読み込み var fileReader = new FileReader(); fileReader.onload = function (evt) { //バッファにmsgデータを読み込み var buffer = evt.target.result; var msgReader = new MSGReader(buffer); var fileData = msgReader.getFileData(); //ファイルデータのエラー判定 if (!fileData.error) { //メールヘッダからToとCCを取り出す var tomail = ""; var ccmail = ""; var array = header2array(fileData.headers); for(var i = 0;i<array.length;i++){ //配列を取り出す var str = array[i].replace(/\r?\n/g, ''); var patternto = "To:" var patterncc = "Cc:" //toアドレスを調べる if(str.indexOf(patternto) > -1){ // 部分一致のときの処理 tomail = array[i].replace("To: ",""); //改行コードがある場合削除する tomail = tomail.replace("\n","") } //ccアドレスを調べる if(str.indexOf(patterncc) > -1){ // 部分一致のときの処理 ccmail = array[i].replace("Cc: ",""); //改行コードがある場合削除する ccmail = ccmail.replace("\n","") } } //ToアドレスをBase64デコード //複数アドレス対応の為にカンマで配列に区切る var toarray = tomail.split(','); //アドレスリスト用配列 var alist = []; for(var i = 0;i<toarray.length;i++){ //Base64文字列があるかどうか var ret = toarray[i].indexOf("=?UTF-8?B?") //Base64文字列が含まれているのでスペースで配列に分割 if(ret == -1){ //入っていない場合はそのまま利用する alist.push(toarray[i]); }else{ //余計な文字を除去する var sub = toarray[i].replace(" =?UTF-8?B?",""); var sub = sub.replace("?=",""); //スペース区切りで配列に分割する var subarray = sub.split(' '); //subarray[0]をbase64デコード var decoded = Base64.decode(subarray[0]); //メルアドを組み直す var mail = decoded + " " + subarray[1]; //配列にpushする alist.push(mail); } } //Ccアドレスをbase64デコード //複数アドレス対応の為にカンマで配列に区切る var ccarray = ccmail.split(","); //アドレスリスト用の配列 var clist = []; if(ccarray != ""){ for(var i = 0;i<ccarray.length;i++){ //Base64文字列があるかどうか var ret = ccarray[i].indexOf("=?UTF-8?B?") //Base64文字列が含まれているのでスペースで配列に分割 if(ret == -1){ //入っていない場合はそのまま利用する clist.push(ccarray[i]); }else{ //余計な文字を除去する var sub = ccarray[i].replace(" =?UTF-8?B?",""); sub = sub.replace("=?UTF-8?B?","") sub = sub.replace("?=",""); //スペース区切りで配列に分割する var subarray = sub.split(' '); //subarray[0]をbase64デコード var decoded = Base64.decode(subarray[0]); //メルアドを組み直す var mail = decoded + " " + subarray[1]; //配列にpushする clist.push(mail); } } } //メールの送信元アドレスを取得して表示 $('.msg-example .msg-from').html(formatEmail({name: fileData.senderName, email: fileData.senderEmail})); message.from = formatEmail({name: fileData.senderName, email: fileData.senderEmail}); //メールの送信先アドレスを取得して表示 $('.msg-example .msg-to').html(jQuery.map(alist, function (to, i) { //表示用に文字の置き換えをする var ret = to.replace("<","<") ret = ret.replace(">",">") //メアドを返す return ret; }).join('<br/>')); message.sendto = alist; //メールのCC送信先アドレスを取得して表示 if(clist.length == 0){ $('#ccman').hide(); }else{ //CCリストを構築する $('.msg-example .msg-cc').html(jQuery.map(clist, function (cc, i) { //表示用に文字の置き換えをする var ret = cc.replace("<","<") ret = ret.replace(">",">") //メアドを返す return ret; }).join('<br/>')); $('#ccman').show(); } message.sendcc = clist; //メールの受信日付を取得して表示 $('.msg-example .msg-date').html(getMsgDate(fileData.headers)); message.date = getMsgDate(fileData.headers); //メールのタイトルを取得して表示 $('.msg-example .msg-subject').html(fileData.subject); message.subject = fileData.subject; //メッセージ本文を取得して表示 //本文を一時的に取得 var tempbody = fileData.body; //改行コードを置換する var result = tempbody.replace(/\r?\n/g, '<br>'); message.body = result; //本文を反映する $('.msg-example .msg-body').html(result); //メールの添付ファイルを取得(全ファイルを取得してリンクを表示) var attachman = []; if(fileData.attachments.length != 0){ $('.msg-example .msg-attachment').html(jQuery.map(fileData.attachments, function (attachment, i) { var file = msgReader.getAttachment(i); attachman.push([file.content,attachment.fileName,attachment.mimeType]); var fileUrl = URL.createObjectURL(new File([file.content], attachment.fileName, {type: attachment.mimeType ? attachment.mimeType : "application/octet-stream"})); return attachment.fileName + ' [' + attachment.contentLength + 'bytes]' + (attachment.pidContentId ? '; ID = ' + attachment.pidContentId : '') + '; <a href="' + fileUrl + '" target="_blank">ダウンロード</a>'; }).join('<br/>')); $('#attachman').show(); }else{ $('#attachman').hide(); } message.attach = attachman; //メッセージ情報エリアを表示する $('.msg-info').show(); $('.header').show(); //ドロップエリアを非表示 $('.info-box').hide(); // Use msgReader.getAttachment to access attachment content ... // msgReader.getAttachment(0) or msgReader.getAttachment(fileData.attachments[0]) } else { //msgファイルじゃなかった場合の処置 $('.msg-info').hide(); $('.incorrect-type').show(); } }; fileReader.readAsArrayBuffer(selectedFile); }); }); </script> </body> </html>
- オリジナルのmsg.readerのサンプルはファイル選択方式でしたが、今回はドラッグ&ドロップ方式に変えてあります。
- オリジナルのmsg.readerは送信元はきちんと取れますが、ToとCcがごっちゃになって返ってくるので、今回その部分はメールヘッダから自前で撮ってきて構築しています。(header2array関数にてメールヘッダを各行を配列にしてあります)
- MSGファイル読み込み完了後に、ヘッダ部分に作業をする為の固定のヘッダとボタンを用意してあります。
- ヘッダの行末の改行コードを;;に置き換えて、splitにて配列に変換しています。
- メールヘッダのメアドには時々日本語でのユーザ名がBase64エンコードされて入ってるので、これに対応するため、デコードしたり余計な文字列を除去するコードを用意してあります。
- メアドに日本語が入ってる場合、半角スペースで名前とメアドが区切られているので、これをsplitで配列化。加工後に再度結合させる処置をしています。
- ToとCcは綺麗に整形されて、登録用の配列でるalistとclist配列に格納しています。
- オリジナルには無いToとCcのリスト表示ようにHTMLを改造しています。
- オリジナルは本文が改行コードになってるので、<BR>タグに置換させています。
- 最終的にはmessage連想配列に各データを格納してあるので、これをメインプロセス側などに送って、別のシステムへと登録を想定しています。
- クリアボタンで元のドラッグアンドドロップ画面に戻るようにしてあります。
- 本文はスクロールするDiv内に表示するように改造してあります。
- CC送信先と添付ファイルは、無い場合には非表示にしています。
関連リンク
- HTML5でファイルをドラッグして読み込むやつ
- ykarpovich/msg.reader
- extract-msg 0.23.3 - Python
- Parsing .msg files in JavaScript
- Chrome Extension which can Open and view MSG and EML files
- FreiraumIO/msgreader
- 【JavaScript入門】文字列を改行する方法(改行コード/「\n」/置換)
- tonyfederer/OutlookFileDrag
- HTML 5 drag and Drop email from outlook
- メール解析プログラムをJavaScriptで書いてみた、の巻
- javascriptでBase64