こんにちは!
テダスクのSeitoです。
仕事やプライベートでもGmailを使うことは多いと思いますが、メールに添付ファイルがあるときに一度ダウンロードしてファイルを開くことになりますが、その後のファイルってPCであればダウンロードフォルダやデスクトップに溜まることってありますよね。
ダウンロードしたその時に、仕事であればクライアントさんのフォルダに入れれば良いだけなのですが、直ぐ使う場合はそのままにしてしまう場合も多いかと思います。
作業が終わった後はダウンロードしたファイルの整理が必要ですが、これを何とか自動でできないかと考えたときにGmailで特定のメールアドレスに届いたメールで添付ファイルがあるときは、自動でDropboxにクライアントまとはプロジェクトごとにディレクトリを振り分けて日付フォルダを作って保存するということができないかと考え、Google App Script(以下、GASと略します)を使うことで実現できました。
今回はGASを使ってGmailに届いたメールの添付ファイルをDropboxに保存する方法を共有したいと思います。
Google App Script(GAS)とは?
GASとは、GoogleのサービスでJavaScriptをベースとしてブラウザ上でコードを作成して実行させることができるサービスです。
Googleのサービスとの連携ができるため、Googleのサービスで何かまとめた作業を実行させたい場合に利用するととても便利です。
今回紹介するGmailもその一つで、利用しているGmailからメールを取得するための関数がすでに用意されているため、届いたメールの条件をトリガーにやりたいことを実行させることができます。
個人で利用する分には無料で使えますが、制限もあるので詳しい仕様については公式サイトをご覧下さい。
企業でGASを利用する場合はGoogle Workspaceの契約が必要になります。
Gmailから添付ファイルをDropboxに保存する全体の流れ
図で表すとこのような感じですね。
届くメールはGmailに取り込んでいる独自ドメインのメールアドレスでも問題ありません。
私の場合は業務で届くメールに限ってDropboxに入れるようにしているので、@tedask.jp宛て限定の条件にしています。(やり方はのちほどコードで説明します)
GmailからDropboxへ自動保存の流れ
動きの流れは
- 5分に1回メールのチェックを行う(メールが届いたらというトリガーができないため)
- 条件を満たすメールがあれば対象のGmailの添付ファイルを抽出する
- 添付ファイルをDropboxのAPIでフォルダの作成を行いファイルを保存する
といった流れになります。
Dropbox Developerでアプリの作成
それでは早速コードを作っていきたいところですが、DropboxにGASからアクセスするためにはDropboxのAPIを使えるようにアプリの登録をしなければいけません。
DropboxのAPIは執筆中の本日(2023年11月22日)時点では無料で利用出来ます。
詳しくは公式サイトのサポートをご覧下さい。
それでは早速Dropbox Developerでアプリの作成を行います。(通常のDropboxのアカウントとログインが必要です。)
まず、DropboxのDeveloperのサイトに移動します。
「アプリを作成」をクリックします。
「Create a new app on the DBX Platform」と表示されるので
1. Choose an APIを「Scoped access」
2. Choose the type of access you needを「Full Dropbox」
を選択します。
3. Name your appは自分が分かるアプリの名前を付けてください。今回は「Access From GAS」としました。(ガイドラインにはDropboxという名前は使ってはいけないとなっているので注意してください。)
入力が完了したら、右下の「Create app」をクリックします。
「App created.」と画面が切り替わればOKです。
Dropbox APIのアクセス権限の設定
まず先にDropbox APIでアクセスするときに、最低限必要な権限(パーミッション)を設定していきます。
デフォルトの状態では、全て一部の権限のみが有効になっている状態なので、必要な権限を確認して設定する必要があります。今回の場合はフォルダ作成やファイル作成などの書き込みの権限を許可します。
設定するのは「Files and folders」の
- files.content.write
- files.content.read
の2つにチェックを入れます。
チェックを入れると画面下に更新の通知が表示されるので「submit」をクリックして権限を確定します。
この通知では、「送信することでアクセストークンが更新されます」と書いてあり、この権限を更新する度にアクセストークンが変更されるので、この仕様を今後のために覚えておきましょう。
APIキーの取得とauth_codeの取得
権限の設定が終わったらAPIを実行するために必要なキーを取得します。
「Setting」タブをクリックします。
画面をスクロールして、「App key」を確認します。この下の「App secret」も後ほど使いますので、「show」をクリックしてメモ帳などに控えておきましょう。
https://www.dropbox.com/oauth2/authorize?client_id=App_key&&response_type=code&token_access_type=offline
上記のURLにApp keyの内容を置き換えてブラウザでアクセスします。
「続行」をクリックします。
Dropboxで作ったアプリからの権限の承認がでますので「許可」をクリックします。
Auth codeが表示されますので、メモ帳に控えます。
Dropboxでの作業は一度ここまでとなります。
※ここからの作業に時間が掛かってしまうと取得したAuth codeの期限が切れてしまう可能性があるので、もしGASで作業が時間がかかり承認が切れてしまった場合は、同じやり方でAuth codeを取得して新しいAuth codeに書き換えましょう。
アクセストークンの取得について
DropboxのAPIでハマりポイントがこのアクセストークンの取得です。
DropboxのAPIのアクセストークンの有効期限は14400秒(4時間)と決められているため、Dropboxの管理画面で生成出来るアクセストークンを一時的にしか使うことができません。
そのため、処理のたびにアクセストークンを生成するプログラムも作成します。
GASでソースコードの作成
それではGASの画面からソースコードを作成していきます。下記URLにアクセスします。
「Start Scripting」をクリックします。
「新しいプロジェクト」をクリックします。
このような画面が表示されればOKです。スクリプトのタイトルは自分が分かるように名前を付けてください。
初期値の設定
まずリフレッシュトークンを取得するための初期値の設定します。
アクセストークンを都度生成するためにリフレッシュトークンが必要になります。
Google App Script
var auth_code = 'Authcode'; var CLIENT_ID = 'App_key'; var CLIENT_SECRET = 'App_secret'; var reflesh_token = 'これから取得';
- auth_code:ブラウザで取得したコードです
- CLIENT_ID:Dropbox APIのSettingにあった「App key」です
- CLIENT_SECRET:Dropbox APIのSettingにあった「App secret」です。(App key)の下にあります。
- reflesh_token:これから取得するため空欄で良いです。
Google App Script
var auth_code = 'Authcode'; var CLIENT_ID = 'App_key'; var CLIENT_SECRET = 'App_secret'; var reflesh_token = 'これから取得'; function GetRefreshToken(){ // Request a token const message = "grant_type=authorization_code&code=" + auth_code + "&client_id=" + CLIENT_ID + "&client_secret=" + CLIENT_SECRET; const options = { method: "post", contentType: "application/x-www-form-urlencoded", payload: message, muteHttpExceptions: true, }; const response = UrlFetchApp.fetch("https://api.dropboxapi.com/oauth2/token", options); // Decode the response body (json format) const responseData = JSON.parse(response.getContentText()); const access_token = responseData["access_token"]; if (access_token !== undefined) { console.log(responseData); } else { const error = responseData["error_description"]; console.log("Failed to get access token. Error: " + response.getResponseCode() + ": " + error); return null; } }
初期値に続けて、リフレッシュトークンを生成するための関数を作成します。
入力が終わったら、「保存」アイコンをクリックします。
すると「実行」と「GetRefreshToken」がアクティブになるので、「GetRefreshToken」を選択した状態で「実行」をクリックします。
すると「承認が必要です」のダイアログが表示されるの「権限を確認」をクリックします。
アカウントの選択が表示されるので、Gmailを利用するアカウントをクリックします。
「詳細」をクリックします。
「Gmail to Dropbox(安全ではないページ)に移動」をクリックします。
外部サービスの接続の許可の確認が出るので「許可」をクリックします。
実行が始まると「実行ログ」が画面下に表示されます。
うまくいけば、情報のところに「refresh_token」が表示されるので、refresh_tokenの値をコピーします。
そして、このrefresh_tokenを初期値の変数「refresh_token」に貼り付けます。
これで、Dropbox APIを実行するために必要なキーの取得は完了です。
アクセストークンを取得するための処理
これ以降、コードは今までのコードの下に続けて書いて下さい。
Google App Script
function requestToken(client_id, app_secret, token) { // Request a token const message = "grant_type=refresh_token&refresh_token=" + token + "&client_id=" + client_id + "&client_secret=" + app_secret; const options = { method: "post", contentType: "application/x-www-form-urlencoded", payload: message, muteHttpExceptions: true, }; const response = UrlFetchApp.fetch("https://api.dropboxapi.com/oauth2/token", options); // Decode the response body (json format) const responseData = JSON.parse(response.getContentText()); const access_token = responseData["access_token"]; if (access_token !== undefined) { return access_token; } else { const error = responseData["error_description"]; console.log("Failed to get access token. Error: " + response.getResponseCode() + ": " + error); return null; } }
先程のリフレッシュトークンを取得するコードとほぼ一緒ですが、リフレッシュトークンを使ってアクセストークンを取得しています。
Gmailから対象のメールを取得してDropboxにアクセスする処理
Google App Script
function gmailToDropbox() { var Appkey = requestToken(CLIENT_ID, CLIENT_SECRET, reflesh_token); //昨日の日付計算(検索条件用) var now = new Date(); var today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); today = Utilities.formatDate(today, "JST", "yyyyMMdd"); var yesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1); yesterday = Utilities.formatDate(yesterday, "JST", "yyyy/MM/dd"); //検索条件設定(Gmail で使用できる検索演算子 参照) var condition; condition = " is:unread" //未読 condition += " has:attachment"; //添付あり condition += " after:" + yesterday; //今日(昨日より後) condition += "to:test@tedask.jp" //条件でメール検索 var search_mail = GmailApp.search(condition); //検索したメールをスレッドで取得(二次配列) var messeges = GmailApp.getMessagesForThreads(search_mail); //スレッド数ループ for(var i = 0; i < messeges.length; i++) { //スレッドの中身数ループ for(var j = 0; j < messeges[i].length; j++) { //添付ファイル取得(一次配列) var attach = messeges[i][j].getAttachments(); var fromUser = messeges[i][j].getFrom(); if(!messeges[i][j].isStarred()){ //スターがないメッセージのみ処理をする //添付ファイル数ループ for(var k = 0; k < attach.length; k++){ //添付ファイルのファイル名取得 fileName = attach[k].getName() Logger.log(fileName) //添付ファイル保存 src = attach[k] if(fromUser.match("@project1.co.jp")) { dest = "/s仕事/プロジェクト1/" +today+"/"+ fileName } else if(fromUser.match("@project2.co.jp")) { dest = "/s仕事/プロジェクト1/" +today+"/"+ fileName } else if(fromUser.match("@project3.co.jp")) { dest = "/s仕事/プロジェクト1/" +today+"/"+ fileName } else if(fromUser.match("@project4.co.jp")) { dest = "/s仕事/プロジェクト1/" +today+"/"+ fileName } else { dest = "/s仕事/メール添付自動保存Dropbox/" +today+"/"+ fileName } uploadDropbox(Appkey, src, dest) messeges[i][j].star(); //処理済みのメッセージにスターする } } } } }
添付ファイルをDropboxに送るために、届いているGmailのメールボックスから対象のメールを取得します。
Google App Script
//検索条件設定(Gmail で使用できる検索演算子 参照) var condition; condition = " is:unread" //未読 condition += " has:attachment"; //添付あり condition += " after:" + yesterday; //今日(昨日より後) condition += "to:test@tedask.jp" //条件でメール検索 var search_mail = GmailApp.search(condition);
Gmail絞込みはGmailApp.search()で処理しますが、検索方法はGmailアプリで検索する記述方法と同じです。
今回の場合は
- is:unread:未読
- has:attachment:添付ファイルがある
- after:指定日以降
- to:宛先
のAND条件で対象のメールを抽出します。
この条件以にもラベルの有無を判定に使ったりもできるので、必要以上にメールを取得しないように条件を作るのがポイントです。
Google App Script
var messeges = GmailApp.getMessagesForThreads(search_mail); //スレッド数ループ for(var i = 0; i < messeges.length; i++) { //スレッドの中身数ループ for(var j = 0; j < messeges[i].length; j++) { //添付ファイル取得(一次配列) var attach = messeges[i][j].getAttachments(); var fromUser = messeges[i][j].getFrom(); if(!messeges[i][j].isStarred()){ //スターがないメッセージのみ処理をする //添付ファイル数ループ for(var k = 0; k < attach.length; k++){ //添付ファイルのファイル名取得 fileName = attach[k].getName() Logger.log(fileName) //添付ファイル保存 src = attach[k] if(fromUser.match("@project1.co.jp")) { dest = "/s仕事/プロジェクト1/" +today+"/"+ fileName } else if(fromUser.match("@project2.co.jp")) { dest = "/s仕事/プロジェクト2/" +today+"/"+ fileName } else if(fromUser.match("@project3.co.jp")) { dest = "/s仕事/プロジェクト3/" +today+"/"+ fileName } else if(fromUser.match("@project4.co.jp")) { dest = "/s仕事/プロジェクト4/" +today+"/"+ fileName } else { dest = "/s仕事/メール添付自動保存Dropbox/" +today+"/"+ fileName } uploadDropbox(Appkey, src, dest) messeges[i][j].star(); //処理済みのメッセージにスターする } } } } }
取得したメールはスレッド単位で取得しているため2次配列になっています。
なのでforを2回まわして、メール単体の内容を取得します。
処理は何度も同じ処理をしないために、スターにマークが入っているものは処理を行わないように条件を設定し、処理が終わったらスターを付けるようにしています。
if(fromUser.match)では、fromで案件名を判定して、それぞれ指定のDropboxのフォルダにパスを指定します。また、todayの変数で今日の日付のフォルダを指定してフォルダがなければ生成するようにしています。
elseでは設定した案件に該当しない専用フォルダを指定しています。
アナログな条件の作り方なので、適宜変更してください。
取得したデータをDropboxにアップロード
最後にDropboxにファイルをアップロードする処理を作ります。
Google App Script
function uploadDropbox(Appkey, src, dest) { return UrlFetchApp.fetch( "https://content.dropboxapi.com/2/files/upload", { "method" : "post", "headers" : { "Authorization" :"Bearer " + Appkey, "Content-Type" : "application/octet-stream", "Dropbox-API-Arg" : "{\"path\":\"" + dest + "\",\"mode\":{\".tag\":\"overwrite\"}}" }, // Convert the JavaScript object to a JSON string. 'payload' : src , "muteHttpExceptions" : false } ); }
このコードは形としてはお決まりの形なので、このまま使って問題ありません。
uploadに関するDropbox公式のAPIの仕様はこちらです。
完成
var auth_code = 'Authcode'; var CLIENT_ID = 'App_key'; var CLIENT_SECRET = 'App_secret'; var reflesh_token = 'reflesh_token'; function GetRefreshToken(){ // Request a token const message = "grant_type=authorization_code&code=" + auth_code + "&client_id=" + CLIENT_ID + "&client_secret=" + CLIENT_SECRET; const options = { method: "post", contentType: "application/x-www-form-urlencoded", payload: message, muteHttpExceptions: true, }; const response = UrlFetchApp.fetch("https://api.dropboxapi.com/oauth2/token", options); // Decode the response body (json format) const responseData = JSON.parse(response.getContentText()); const access_token = responseData["access_token"]; if (access_token !== undefined) { console.log(responseData); } else { const error = responseData["error_description"]; console.log("Failed to get access token. Error: " + response.getResponseCode() + ": " + error); return null; } } function requestToken(client_id, app_secret, token) { // Request a token const message = "grant_type=refresh_token&refresh_token=" + token + "&client_id=" + client_id + "&client_secret=" + app_secret; const options = { method: "post", contentType: "application/x-www-form-urlencoded", payload: message, muteHttpExceptions: true, }; const response = UrlFetchApp.fetch("https://api.dropboxapi.com/oauth2/token", options); // Decode the response body (json format) const responseData = JSON.parse(response.getContentText()); const access_token = responseData["access_token"]; if (access_token !== undefined) { return access_token; } else { const error = responseData["error_description"]; console.log("Failed to get access token. Error: " + response.getResponseCode() + ": " + error); return null; } } function gmailToDropbox() { var Appkey = requestToken(CLIENT_ID, CLIENT_SECRET, reflesh_token); //昨日の日付計算(検索条件用) var now = new Date(); var today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); today = Utilities.formatDate(today, "JST", "yyyyMMdd"); var yesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1); yesterday = Utilities.formatDate(yesterday, "JST", "yyyy/MM/dd"); //検索条件設定(Gmail で使用できる検索演算子 参照) var condition; condition = " is:unread" //未読 condition += " has:attachment"; //添付あり condition += " after:" + yesterday; //今日(昨日より後) condition += "to:test@tedask.jp" //条件でメール検索 var search_mail = GmailApp.search(condition); //検索したメールをスレッドで取得(二次配列) var messeges = GmailApp.getMessagesForThreads(search_mail); //スレッド数ループ for(var i = 0; i < messeges.length; i++) { //スレッドの中身数ループ for(var j = 0; j < messeges[i].length; j++) { //添付ファイル取得(一次配列) var attach = messeges[i][j].getAttachments(); var fromUser = messeges[i][j].getFrom(); if(!messeges[i][j].isStarred()){ //スターがないメッセージのみ処理をする //添付ファイル数ループ for(var k = 0; k < attach.length; k++){ //添付ファイルのファイル名取得 fileName = attach[k].getName() Logger.log(fileName) //添付ファイル保存 src = attach[k] if(fromUser.match("@project1.co.jp")) { dest = "/s仕事/プロジェクト1/" +today+"/"+ fileName } else if(fromUser.match("@project2.co.jp")) { dest = "/s仕事/プロジェクト2/" +today+"/"+ fileName } else if(fromUser.match("@project3.co.jp")) { dest = "/s仕事/プロジェクト3/" +today+"/"+ fileName } else if(fromUser.match("@project4.co.jp")) { dest = "/s仕事/プロジェクト4/" +today+"/"+ fileName } else { dest = "/s仕事/メール添付自動保存Dropbox/" +today+"/"+ fileName } uploadDropbox(Appkey, src, dest) messeges[i][j].star(); //処理済みのメッセージにスターする } } } } } function uploadDropbox(Appkey, src, dest) { return UrlFetchApp.fetch( "https://content.dropboxapi.com/2/files/upload", { "method" : "post", "headers" : { "Authorization" :"Bearer " + Appkey, "Content-Type" : "application/octet-stream", "Dropbox-API-Arg" : "{\"path\":\"" + dest + "\",\"mode\":{\".tag\":\"overwrite\"}}" }, // Convert the JavaScript object to a JSON string. 'payload' : src , "muteHttpExceptions" : false } ); }
コードが完成したら、「保存」を忘れずにして下さい。
GASで作ったプログラムの実行
では作ったコードを改めて実行しますが、事前に対象のメールアドレスに添付ファイルを付けてメールを送信します。
メールは未読の状態にしておきます。画像では添付ファイルマークが見えませんが、画像の添付ファイルが添付されいています。
GASの画面に戻り実行する関数を「gmailToDropbox」を選択して「実行」をクリックします。
エラーがなければ実行ログの「情報」に添付ファイル名が表示されます。もしここでエラーが出る場合はエラーが発生している行数が表示されますので、コードの記述や変数などの間違いがないかを確認しましょう。
もしDropbox関係のエラーの場合は、最初の方で取得したAuth codeが期限切れの可能性もあるので、Auth code取得の項目に戻り再取得をして、リフレッシュトークンも再取得して再度実行してみてください。
Dropboxを見てみるとプロジェクト外のメールアドレスから届いたメールなので、「メール添付自動保存Dropbox」フォルダに入っています。フォルダも日付で作成されていますね。
これで問題なく動くことが確認できました。
最後にこのプログラムが自動で動くようにGASでトリガーの設定を行います。
プログラムの自動実行の設定
GASは「Gmailが届いたとき」というアクションでのトリガーはできません。
そのために、定期実行させるために「時間によるトリガー」を設定で行う必要があります。
左メニューの時計アイコン「トリガー」をクリックします。
右下の「トリガーを追加」をクリックします。
ダイアログが表示されたら以下のように設定します。
- 実行する関数を選択:gmailToDropbox
- イベントのソースを選択:時間主導型
- 時間ベースのトリガーのタイプを選択
- (下にスクロール)時間の間隔を選択(分):5分おき※
時間間隔は最短で1分の設定が可能ですが、GASを他のプログラムでも使う場合などはリクエスト数に制限があるので、5分や10分間隔が個人的にはおすすめです。
設定が完了したら「保存」をクリックして完了します。
これで全ての作業が完了です。
自動実行が問題なく動くことを確認しましょう。
まとめ
今回はGoogle App Script(GAS)を使ってGmailに届いたメールの添付ファイルをDropboxに自動で保存する方法を書いてきました。
DropboxのAPIはアクセストークンが一定時間で期限切れになってしまうところが私にとってはハマりポイントでしたが、トークンの取得がうまくいけばあとは仕様書をみてリクエストするだけなので、このコードを元に応用ができそうですね。
GASはGmailをトリガーにできるので汎用性が高く応用次第で様々な自動処理ができますので、みなさんもオリジナルのスクリプトを作ってみてください。
この記事をみて間違いやより良いやり方などがあれば気軽にコメントを下さい。
自動化の作成のご依頼、ご相談も承っておりますので、お気軽にご連絡ください。
以上、「GASを利用してGmailに届いたメールの添付ファイルをDropboxに保存する方法」でした!
コメント