前回、前々回とGmail APIを扱ってきましたが、今回は前々回の記事で紹介したコードを改修して、添付ファイル&64ビット版Officeに対応したコードを紹介します。
※ 下記コードは日本語本文には対応していますが、件名に日本語を入れると文字化けします。
Option Explicit '-------------------------------------------------- '※ 要変更 '-------------------------------------------------- Private Const email As String = "(Gメールアドレス(アカウントID))" Private Const passwd As String = "(Google アカウントのパスワード)" Private Const client_id As String = "(クライアント ID)" Private Const client_secret As String = "(クライアント シークレット)" '-------------------------------------------------- Private Const response_type As String = "code" Private Const redirect_uri As String = "urn:ietf:wg:oauth:2.0:oob" Private Const scope As String = "https://www.googleapis.com/auth/gmail.compose" Private Const grant_type As String = "authorization_code" Public Sub Sample() SendGmail email, _ "(Toアドレス)", _ "(Ccアドレス)", _ "(Bccアドレス)", _ "Test", _ "■ 本文テスト:" & vbCrLf & vbCrLf & _ "あいうえお" & vbCrLf & _ "かきくけこ" & vbCrLf & _ "さしすせそ" & vbCrLf & _ "たちつてと" & vbCrLf & _ "なにぬねの" & vbCrLf & _ "はひふへほ" & vbCrLf & _ "まみむめも", _ "C:\Test\サンプル.pdf" End Sub Private Sub SendGmail(ByVal MailFrom As String, _ ByVal MailTo As String, _ ByVal MailCc As String, _ ByVal MailBcc As String, _ ByVal MailSubject As String, _ ByVal MailBody As String, _ Optional ByVal AttachmentFilePath As String = "") 'Gmail API v1を使ってメール送信 Dim access_token As String Dim mail_dat As String Dim raw_dat As String Dim dat As Variant access_token = GetAccessToken If Len(Trim(access_token)) > 0 Then mail_dat = CreateMailData(MailFrom, _ MailTo, _ MailCc, _ MailBcc, _ MailSubject, _ MailBody, _ AttachmentFilePath) raw_dat = EncodeBase64Str(mail_dat) dat = "{""raw"": """ & DelBreak(raw_dat) & """}" With CreateObject("WinHttp.WinHttpRequest.5.1") .Open "POST", "https://www.googleapis.com/gmail/v1/users/me/messages/send" .SetRequestHeader "Content-Type", "application/json; charset=UTF-8" .SetRequestHeader "Authorization", "Bearer " & access_token .Send dat Select Case .Status Case 200 MsgBox "メールを送信しました。", vbInformation + vbSystemModal Case Else MsgBox "エラーが発生しました。" & vbCrLf & vbCrLf & .responseText, vbCritical + vbSystemModal End Select End With End If End Sub Private Sub WaitIE(ByRef IEObj As Object) '表示待ち Const READYSTATE_COMPLETE = 4 While IEObj.Busy Or IEObj.readyState <> READYSTATE_COMPLETE DoEvents Wend End Sub Private Function GetAuthorizationCode() As String 'Authorization code取得 Dim ie As Object Dim url As String Dim ancAcaa As Object Dim iptEmail As Object Dim iptPasswd As Object Dim iptSignIn As Object Dim iptCode As Object Dim btnApprove As Object Dim auth_code As String '初期化 Set ancAcaa = Nothing Set iptEmail = Nothing Set iptPasswd = Nothing Set iptSignIn = Nothing Set iptCode = Nothing Set btnApprove = Nothing auth_code = "" url = "https://accounts.google.com/o/oauth2/auth?" & _ "client_id=" & client_id & "&" & _ "response_type=" & response_type & "&" & _ "redirect_uri=" & redirect_uri & "&" & _ "scope=" & EncodeURL(scope) Set ie = CreateObject("InternetExplorer.Application") With ie .Visible = True .Navigate url WaitIE ie '複数アカウント存在時の処理 If InStr(LCase(.document.Location.href), "https://accounts.google.com/servicelogin") Then With .document On Error Resume Next Set ancAcaa = .getElementById("account-chooser-add-account") On Error GoTo 0 If Not ancAcaa Is Nothing Then ancAcaa.Click End With WaitIE ie End If '未ログイン時のログイン処理 If InStr(LCase(.document.Location.href), "https://accounts.google.com/servicelogin") Then With .document On Error Resume Next Set iptEmail = .getElementById("Email") Set iptPasswd = .getElementById("Passwd") Set iptSignIn = .getElementById("signIn") On Error GoTo 0 If Not iptEmail Is Nothing Then iptEmail.Value = email If Not iptPasswd Is Nothing Then iptPasswd.Value = passwd If Not iptSignIn Is Nothing Then iptSignIn.Click End With WaitIE ie End If '承認処理 If InStr(LCase(.document.Location.href), "https://accounts.google.com/o/oauth2/auth") Then With .document On Error Resume Next Set btnApprove = .getElementById("submit_approve_access") On Error GoTo 0 If Not btnApprove Is Nothing Then While btnApprove.disabled <> False DoEvents Wend btnApprove.Click End If End With WaitIE ie End If 'Authorization code取得処理 If InStr(LCase(.document.Location.href), "https://accounts.google.com/o/oauth2/approval") Then With .document On Error Resume Next Set iptCode = .getElementById("code") On Error GoTo 0 If Not iptCode Is Nothing Then auth_code = iptCode.Value End With .Navigate "https://accounts.google.com/o/logout" 'ログアウト WaitIE ie End If .Quit End With GetAuthorizationCode = auth_code End Function Private Function GetAccessToken() As String 'Access token取得 Dim auth_code As String Dim access_token As String Dim json As String Dim dat As Variant Dim d As Object Dim elm As Object access_token = "" '初期化 auth_code = GetAuthorizationCode If Len(Trim(auth_code)) > 0 Then dat = "code=" & auth_code & "&" & _ "client_id=" & client_id & "&" & _ "client_secret=" & client_secret & "&" & _ "redirect_uri=" & redirect_uri & "&" & _ "grant_type=" & grant_type With CreateObject("WinHttp.WinHttpRequest.5.1") .Open "POST", "https://accounts.google.com/o/oauth2/token" .SetRequestHeader "Content-Type", "application/x-www-form-urlencoded;charset=UTF-8" .Send dat If .Status = 200 Then json = .responseText If Len(Trim(json)) > 0 Then json = "(" & .responseText & ")" Set d = CreateObject("htmlfile") Set elm = d.createElement("span") elm.setAttribute "id", "result" d.body.appendChild elm d.parentWindow.execScript "document.getElementById('result').innerText=eval(" & json & ").access_token;" access_token = elm.innerText End If End If End With End If GetAccessToken = access_token End Function Private Function CreateMailData(ByVal MailFrom As String, _ ByVal MailTo As String, _ ByVal MailCc As String, _ ByVal MailBcc As String, _ ByVal MailSubject As String, _ ByVal MailBody As String, _ Optional ByVal AttachmentFilePath As String = "") As String 'メールデータ作成 Dim mail_dat As String Dim rnd_str As String Dim boundary As String Dim enc_atch As String Dim fso As Object mail_dat = "": rnd_str = "": boundary = "": enc_atch = "" '初期化 rnd_str = MakeRndStr(20) boundary = "----_" & rnd_str & "_MULTIPART_MIXED_" mail_dat = "Content-Type: multipart/mixed;boundary=""" & boundary & """" & vbCrLf mail_dat = mail_dat & "Content-Transfer-Encoding: base64" & vbCrLf 'mail_dat = mail_dat & "Content-Transfer-Encoding: UTF-8" & vbCrLf '日本語件名の場合なぜか「UTF-8」でエラーにならない? mail_dat = mail_dat & "From: " & MailFrom & vbCrLf mail_dat = mail_dat & "To: " & MailTo & vbCrLf If Len(Trim(MailCc)) > 0 Then mail_dat = mail_dat & "Cc: " & MailCc & vbCrLf If Len(Trim(MailBcc)) > 0 Then mail_dat = mail_dat & "Bcc: " & MailBcc & vbCrLf 'mail_dat = mail_dat & "Subject: =?UTF-8?B?" & EncodeBase64Str(MailSubject) & "?=" & vbCrLf mail_dat = mail_dat & "Subject: " & MailSubject & vbCrLf mail_dat = mail_dat & "MIME-Version: 1.0" & vbCrLf mail_dat = mail_dat & "Importance: normal" & vbCrLf mail_dat = mail_dat & "Priority: normal" & vbCrLf & vbCrLf mail_dat = mail_dat & "--" & boundary & vbCrLf mail_dat = mail_dat & "Content-Type: text/plain;charset=""UTF-8""" & vbCrLf mail_dat = mail_dat & "Content-Transfer-Encoding: base64" & vbCrLf & vbCrLf mail_dat = mail_dat & EncodeBase64Str(MailBody) & vbCrLf & vbCrLf If Len(Trim(AttachmentFilePath)) > 0 Then Set fso = CreateObject("Scripting.FileSystemObject") If fso.FileExists(AttachmentFilePath) Then enc_atch = EncodeBase64(AttachmentFilePath) enc_atch = DelBreak(enc_atch) enc_atch = SplitStr(enc_atch, 76) mail_dat = mail_dat & "--" & boundary & vbCrLf mail_dat = mail_dat & "Content-Type: application/octet-stream;name=""" & fso.GetFileName(AttachmentFilePath) & """" & vbCrLf mail_dat = mail_dat & "Content-Transfer-Encoding: base64" & vbCrLf mail_dat = mail_dat & "Content-Disposition: attachment;filename=""" & fso.GetFileName(AttachmentFilePath) & """" & vbCrLf & vbCrLf mail_dat = mail_dat & enc_atch & vbCrLf & vbCrLf End If End If mail_dat = mail_dat & "--" & boundary & "--" & vbCrLf CreateMailData = mail_dat End Function Private Function EncodeURL(ByVal str As String) As String 'URLエンコード Dim d As Object Dim elm As Object str = Replace(str, "\", "\\") str = Replace(str, "'", "\'") Set d = CreateObject("htmlfile") Set elm = d.createElement("span") elm.setAttribute "id", "result" d.body.appendChild elm d.parentWindow.execScript "document.getElementById('result').innerText = encodeURIComponent('" & str & "');", "JScript" EncodeURL = elm.innerText End Function Private Function EncodeBase64(ByVal file_path As String) As String 'ファイルをBase64エンコード Dim elm As Object Dim ret As String Const adTypeBinary = 1 Const adReadAll = -1 ret = "" '初期化 On Error Resume Next Set elm = CreateObject("MSXML2.DOMDocument").createElement("base64") With CreateObject("ADODB.Stream") .Type = adTypeBinary .Open .LoadFromFile file_path elm.DataType = "bin.base64" elm.nodeTypedValue = .Read(adReadAll) ret = elm.Text .Close End With On Error GoTo 0 EncodeBase64 = ret End Function Private Function EncodeBase64Str(ByVal str As String) As String '文字列をBase64エンコード Dim ret As String Dim d() As Byte Const adTypeBinary = 1 Const adTypeText = 2 ret = "" '初期化 On Error Resume Next With CreateObject("ADODB.Stream") .Open .Type = adTypeText .Charset = "UTF-8" .WriteText str .Position = 0 .Type = adTypeBinary .Position = 3 d = .Read() .Close End With With CreateObject("MSXML2.DOMDocument").createElement("base64") .DataType = "bin.base64" .nodeTypedValue = d ret = .Text End With On Error GoTo 0 EncodeBase64Str = ret End Function Private Function DelBreak(ByVal str As String) As String '改行削除 Dim ret As String ret = "" '初期化 ret = Replace(str, vbNewLine, "") ret = Replace(ret, vbCr, "") ret = Replace(ret, vbLf, "") DelBreak = ret End Function Private Function SplitStr(ByVal str As String, ByVal num As Long) As String '文字列を指定した文字数で分割・結合 Dim ret As String Dim i As Long ret = "" '初期化 For i = 1 To Len(str) Step num If i = 1 Then ret = Mid(str, i, num) Else ret = ret & vbCrLf & Mid(str, i, num) End If Next SplitStr = ret End Function Private Function MakeRndStr(ByVal num As Long) As String '0-9,A-Zまでのランダムな文字列を生成 Dim ret As String Dim n As Long ret = "" '初期化 Do n = RndScope(48, 90) Select Case n Case 58 To 64 Case Else ret = ret & ChrW(n) End Select Loop Until Len(ret) = num MakeRndStr = ret End Function Private Function RndScope(ByVal num_min As Long, num_max As Long) As Long '指定した範囲の乱数を生成 Dim ret As Long Randomize ret = Int(Rnd() * (num_max - num_min + 1) + num_min) RndScope = ret End Function
上記コード、上にも書いた通り、日本語の件名には対応していません。
「=?UTF-8?B?(略)?=」のようにして、問題無く送信できる場合もあればエラーが発生する場合もあり、同じ件名でもエラーが起きたり起きなかったり、「Content-Transfer-Encoding: UTF-8」なんていう謎のヘッダーを入れたらエラーが起きなくなったりと、とにかく挙動がおかしかったので、結局最後は“件名には日本語を使わない”、というところに落ち着きました。
API側が返すエラーも「Invalid value for ByteString」だけで、正直どこにどんな問題があるのか分かりませんでした・・・。
■ 関連Webページ
・Gmail APIを使ってメール送信するVBAマクロ
//www.ka-net.org/blog/?p=4524
・Gmail APIを使ってメール送信するVBAマクロ(2)
//www.ka-net.org/blog/?p=4538
■ あとがき
最初はメールデータの作成にCDOを使おうとしましたが、CDOで作成したデータだとメールアドレスの「<>」でエラーになったりと、問題がかなり多かったので、結局全て自分で書くことになりました・・・。
厳密にRFC 822に従っているわけではありませんが、これ以上やっていられないので、とりあえずはこれで良しとします。
お世話になります。
有用な公開していただきありがとうございます。
このマクロを使わせていただこうと思っています。
テストをしてみましたところ、メールを送信後、
googleのアカウントからログアウトしてしまいます。
引き続きメール送信や、GoogleDriveの利用をしたいので、
ログアウトしないようにしたいのですが、
どこを直せば良いかお教えください。
お手数をおかけいたしますが、よろしくお願いします。
お世話になります。
先程、質問いたしましたログアウトについては、164行目をコメント化することで、
ログアウトしないようになりました。有難うございました。
PCを64bitマシン(win-7)に変えて、試験しましたところ、
「ActivwXコンポーネントはオブジェクトを作成できません」とエラーメッセージで止まってしまいます。
IEを事前に立ち上げて、Googleアカウントでログインしておいても同様です。
有効な対処法がありましたらお教えください。
よろしくお願いします。
> しろやん様
はじめまして、当ブログの管理人です。
ご質問いただきました件につきまして、
> 「ActivwXコンポーネントはオブジェクトを作成できません」とエラーメッセージで止まってしまいます。
メッセージから察すると、恐らくはどこかのCreateObject文で動作が止まってしまっているのではないかと思います。
コードが64ビット環境に対応していない可能性もありますので、まずはステップ実行して、どこで引っかかっているのかを突き止めることをお薦めいたします。
また、当コードはGmail APIを半ば無理やりVBAマクロから呼び出すものですので、単にメール送信を行うだけであれば、下記記事のようにCDOを使うか、BASP21 ProやOutlookといった外部コンポーネントを利用した方がコードが見やすく、シンプルになります。
・CDOを使ってGmail送信を行うVBAマクロ(UTF-8対応版)
https://www.ka-net.org/blog/?p=7459