見出し画像

『プログラミング』noteの抽出ツールを作ってみたいのじゃ(3)

以下記事の続き!

コードの詳細からですね。

前回の記事で記載し忘れていましたが、参考にしたサイトはこちらです。参考サイトのコードをサンプルとして、書き換えたものがこの記事のコードとなります。

先に参考サイトを見たほうがイメージが付きやすいと思います。

🌸完成コード

改めて、完成コードは以下です。

function sukiFunction() {

//アクティブなスプレッドシートを取得
ss = SpreadsheetApp.getActiveSpreadsheet();

//シート名「値を指定」を選択
sheet = ss.getSheetByName('値を指定');

//シート名「値を指定」のA2にある値を取得(記事ID)
id = sheet.getRange('A2').getValue();

//シート名「値を指定」のB2にある値を取得(記事title)
title = sheet.getRange('B2').getValue();

//シート名「test」をアクティブにする
sheet_test = ss.getSheetByName("test");
sheet_test.activate();

Logger.log('id:' + id);
Logger.log('title:' + title);


// 検索条件に該当するスレッド一覧を取得
var threads = GmailApp.search('subject:スキのおしらせ -label:処理済み ' + title);

// スレッドを一つずつ取り出す
threads.forEach(function(thread) {

  // スレッド内のメール一覧を取得
  var messages = thread.getMessages();
  
  // メールを一つずつ取り出す
  messages.forEach(function(message) {
   
    // メール本文を取得
    var plainBody = message.getPlainBody();

    // メール件名を取得
    subject = message.getSubject();

    var from = message.getFrom();

    //送信元のアドレスが「noreply@note.mu」のみを対象とする。
    if(!from.match("noreply@note.mu")){
      return;
    }

    //指定した記事IDが含まれているものだけを対象
    if(!plainBody.match(id)){
      return;
    }

    //コメントへのスキは除外
    if(subject.match("コメントへのスキのおしらせ")){
      return;
    }


    //メール本文を改行ごとに区切る
    var arr_n = plainBody.split('\n');
    
    ////////スキしたユーザーのURLを取り出し////////

    //?nt=が始まる文字位置を検索
    var str_index = arr_n[3].indexOf("?nt=");

    //userのurlを取り出し。
    var user_url = arr_n[3].slice(1,str_index);


    ////////スキしたユーザーの名前を取り出し////////

    //<httpsが始まる文字位置を検索
    var str_index = arr_n[5].indexOf("<https");

    //userネームを取り出し。
    var user_name = arr_n[5].slice(0,str_index-1);


    ////////スプレッドシートに書き出し////////

    // 最終行を取得
    var lastRow = sheet_test.getLastRow() + 1;
    
    // セルを取得して値を転記
    sheet_test.getRange(lastRow, 1).setValue(user_name);
    sheet_test.getRange(lastRow, 2).setValue(user_url);

  });
  
  // スレッドに処理済みラベルを付ける
  var label = GmailApp.getUserLabelByName('処理済み');
  thread.addLabel(label);
});

// 最終行を取得
var lastRow = sheet_test.getLastRow();

//重複削除用に範囲を指定
var range = ss.getRange("A:B");

//重複行を削除する
range.removeDuplicates([2]);

}

コードを上から順番に解説していきます!


-------コード詳細-------


🌸スプレッドシート操作

//アクティブなスプレッドシートを取得
ss = SpreadsheetApp.getActiveSpreadsheet();

//シート名「値を指定」を選択
sheet = ss.getSheetByName('値を指定');

//シート名「値を指定」のA2にある値を取得(記事ID)
id = sheet.getRange('A2').getValue();

//シート名「値を指定」のB2にある値を取得(記事title)
title = sheet.getRange('B2').getValue();

スプレッドシートに記載した「記事ID」と「記事タイトル」を取得する処理です。

■セルの値取得までの流れ
どのスプレッドシートの
(2行目のコード)

どのシート名の
(4行目のコード)

どのセルに(getRange)
何をするか(getValue)
(6行目のコード)
(8行目のコード)


これでセルの値を取得できます。この時点ではシート名「値を取得」を開いている状態なので、書き込み用のシート「test」を開いておきます。それが以下コードになります。

//シート名「test」をアクティブにする
sheet_test = ss.getSheetByName("test");
sheet_test.activate();

このコードは必要ないかもだけど、操作するシートは念の為アクティブにしておいたほうがいいかなって思う。



🌸実行画面に表示

Logger.log('id:' + id);
Logger.log('title:' + title);

Logger.log で スクリプトエディタの実行画面に値を表示することができます。

■上記コードは以下のように表示される
id:シート「値を指定」セルA2で指定した値

title:シート「値を指定」セルB2で指定した値

実際の機能に影響はないため、このコードはなくてもいいのですが、開発中に値を確認しながら行いたい場合があるので書いています。



🌸メールの検索条件

■今回のメール取得条件
1 タイトルが「スキのおしらせ」
2 処理済みではない
3 メール本文に
  指定したタイトルを含んでいる

コードは以下になります。

// 検索条件に該当するスレッド一覧を取得
var threads = GmailApp.search('subject:スキのおしらせ -label:処理済み ' + title);


Gmailの検索画面に打ち込んでいるイメージ。

画像1


■問題発覚!
記事のタイトルが
「タイトル(1)」「タイトル(2)」のように、カッコ内の数字違いで作成したものがあると、検索がうまくいかない。

▼本来してほしい動き
「スキのおしらせ タイトル(1)で検索

タイトル(1)の通知メールが表示される

▼現状なっている動き
「スキのおしらせ タイトル(1)で検索

タイトル(1)とタイトル(2)の
通知メールが表示される

▼起こる問題
タイトル(1)の処理を行っただけなのに、タイトル(2)も「処理済み」にされてしまう。

まぁこういう問題があるよ~ということがわかっていれば、何かしら対応はできると思います。解説続けます(笑)。



🌸メール本文取得

先ほどのコードで検索条件を指定しています

// 検索条件に該当するスレッド一覧を取得
var threads = GmailApp.search('subject:スキのおしらせ -label:処理済み ' + title);

検索結果は 「threads」 に入っています。
「スレッド」と「メール」については、最初に紹介したこちらの「スレッドからメール本文を取り出す」がわかりやすいです。

// スレッドを一つずつ取り出す
threads.forEach(function(thread) {

  // スレッド内のメール一覧を取得
  var messages = thread.getMessages();
  
  // メールを一つずつ取り出す
  messages.forEach(function(message) {
   
    // メール本文を取得
    var plainBody = message.getPlainBody();

このコードが本文を取り出すコードです。上記サイトを見ると、本文を取り出すまでのイメージがわかると思います。



🌸メール本文から抽出

ここがおもしろいところで、
個性が出るところだと思います。

まず、「スキのおしらせ」をGmailで確認すると……

画像2


このようになっております。
これをプログラムで取得してみましょう。
メール本文を取得するプログラムだけ整理すると……


▼本文のみ取得プログラム

function sukiFunction() {

// 検索条件に該当するスレッド一覧を取得
var threads = GmailApp.search('subject:スキのおしらせ -label:処理済み ');

// スレッドを一つずつ取り出す
threads.forEach(function(thread) {

  // スレッド内のメール一覧を取得
  var messages = thread.getMessages();
  
  // メールを一つずつ取り出す
  messages.forEach(function(message) {
   
    // メール本文を取得
    var plainBody = message.getPlainBody();

    // メール本文が取得できているかログに出力して確認
    Logger.log(plainBody);
    
  });
  
});

}


このようになります。
メール本文を取得して、スクリプトエディタに結果を表示するプログラムです。これを実行してどのように本文が取得されるかを確認すると……

画像3


こんな感じ。今回欲しい情報は

■今回欲しい情報
・ユーザーページのURL
・ユーザ名


なので、先程取得した本文からどう取り出すかを考えます。まず、「どの場所を取り出すか」を決めます。私は以下画像の赤枠のところからにしました。

画像4

1 : ユーザーページのURL
2 : ユーザー名

ここを取り出したいと思います。



✨改行ごとに区切る
メール本文を1行ずつ配列に入れていきます。

    //メール本文を改行ごとに区切る
    var arr_n = plainBody.split('\n');

これをループ内で行うと以下イメージになります。

画像5


抽出したいものはそれぞれ以下にあるとわかります。

■ユーザーページのURL
arr_n[3]

■ユーザー名
arr_n[5]



✨欲しい情報だけに切り取る
現状 arr_n[3] と arr_n[5] の中身は

■arr_n[3]
<https://note.com/co2co2cocchan?nt=like_3833710>

■arr_n[5]
ぽんこっちゃん <https://note.com/co2co2cocchan?nt=like_3833710>さんがスキしました。

になっています。
以下のようにしたいですね。

■arr_n[3]
<>がいらない 
?nt=like_3833710がいらない

■arr_n[5]
「ぽんこっちゃん」以外いらない


切り出しは「slice」を使います。

■slice使い方
切り出したい文字.slice(どこから,どこまで);

■例
var text = "りんごとバナナ";
var str = text.slice(0,2);
とすると、「str」に「りんご」が入ります。

var str = text.slice(4,6); だと
「バナナ」が入ります。

■注意
1文字目は0番目から始まります。

0番目 : 1文字目
1番目 : 2文字目
2番め : 3文字目


これを使っていきましょう。
まずユーザーページURLを切り出し。

■arr_n[3]
<https://note.com/co2co2cocchan?nt=like_3833710>

sliceの指定
どこから : hから(2文字目)
どこまで : ?ntの前まで

■なぜ「?ntの前まで」?
noteのユーザーページは
https://note.com/ID で表されるから。


「?ntの前まで」は以下の .indexOf で求めます。指定した文字が最初にヒットした文字位置を返してくれます。これで切り出しのコードは以下のようになります。

    ////////スキしたユーザーのURLを取り出し////////

    //?nt=が始まる文字位置を検索
    var str_index = arr_n[3].indexOf("?nt=");

    //userのurlを取り出し。
    var user_url = arr_n[3].slice(1,str_index);



ユーザー名も同様に

    ////////スキしたユーザーの名前を取り出し////////

    //<httpsが始まる文字位置を検索
    var str_index = arr_n[5].indexOf("<https");

    //userネームを取り出し。
    var user_name = arr_n[5].slice(0,str_index-1);

str_index-1 となっているのは、「ぽんこっちゃん」の後に空白が入っているため、その分をマイナスしています。

だったら indexOf("<https") じゃなく
indexOfで空白を指定でもいい気がしてきた。

まぁこれで目的の文字は取り出せました。



🌸除外処理

    // メール件名を取得
    subject = message.getSubject();

    var from = message.getFrom();
    
    //送信元のアドレスが「noreply@note.mu」のみを対象とする。
    if(!from.match("noreply@note.mu")){
      return;
    }

    //指定した記事IDが含まれているものだけを対象
    if(!plainBody.match(id)){
      return;
    }

    //コメントへのスキは除外
    if(subject.match("コメントへのスキのおしらせ")){
      return;
    }
■getSubject
件名を取得しています。

■getFrom
送信者を取得しています。
■なぜ送信元のアドレス判定
以下画像のように、スレッドの中に複数のメールが存在する場合があります。転送先の設定を行っていたり、返信等を行ったりした場合になります。なので、noteからの「noreply@note.mu」のみを対象としています。

つう



    //送信元のアドレスが「noreply@note.mu」のみを対象とする。
    if(!from.match("noreply@note.mu")){
      return;
    }
■コード説明
・!from.match 
noreply@note.muじゃなかったら

・return
このコード以下は処理しない



    //指定した記事IDが含まれているものだけを対象
    if(!plainBody.match(id)){
      return;
    }
■コード説明
!plainBody.match
記事IDが本文に含まれていなかったら

・return
このコード以下は処理しない



    //コメントへのスキは除外
    if(subject.match("コメントへのスキのおしらせ")){
      return;
    }
■コード説明
subject.match
メールタイトルが「コメントへのスキのおしらせ」”””だったら”””

・return
このコード以下は処理しない

■注意
subject.match の前には 「!」がついていません。「!」は否定を表しています。

OO.match : 一致
!OO.match : 不一致 

■なぜこのコードが必要
「スキのおしらせ」で検索すると「コメントへのスキのおしらせ」がひっかかってしまうからです。

次はようやく出力処理です。



🌸出力処理

出力のコードは以下になります。

    ////////スプレッドシートに書き出し////////

    // 最終行を取得
    var lastRow = sheet_test.getLastRow() + 1;
    
    // セルを取得して値を転記
    sheet_test.getRange(lastRow, 1).setValue(user_name);
    sheet_test.getRange(lastRow, 2).setValue(user_url);
■最終行を取得
シート名「test」の最終行を取得し
それにプラス1しています。
最終行の1個下にどんどん書き込みたいから

■セルを取得して値を転記
シート名「test」の

書き込み行 A列 に

user_nameを書き込む

ーーーーーーーーーー

シート名「test」の

書き込み行 B列 に

user_urlを書き込む

こんな感じになる。

画像6



🌸処理済み

  // スレッドに処理済みラベルを付ける
  var label = GmailApp.getUserLabelByName('処理済み');
  thread.addLabel(label);

処理を行ったスレッドにラベルを追加しています。「処理済み」ラベルは事前にGmailで準備しておく必要があります。前回の記事ではそこの説明が抜けていたため、前回の記事に「ラベル準備」を追加いたしました。

あの状態で実行したらエラーにならないんだろうか。誰も記事を真似して実行してなかったということかな(笑)。

■処理済みが必要な理由
第一回で説明したとおり、もともとこのツールは「一定時間毎」に稼働して「追加更新」を行う予定だったからです。

なので、一度処理したものは次回は処理しないようにしなくてはいけない。



🌸重複削除

// 最終行を取得
var lastRow = sheet_test.getLastRow();

//重複削除用に範囲を指定
var range = ss.getRange("A:B");

//重複行を削除する
range.removeDuplicates([2]);

すべての書き出しが終わったあとに実行されます。B列の「user_url」に重複があった場合、その行を削除します。

通知が重なって来てしまった場合に同じユーザーが出力されるからです。



🌸最後に

ちょっと微妙かなというのが正直なところです。力技感があるかな~って。APIを使った方法のほうがもっと美しくできると思います。

なのでAPIについてもうちょっと調べてみます。




この記事が気に入ったらサポートをしてみませんか?