うえださんが通りますよ

特定の文字列を含まないファイル一覧を取得する

hogeとファイル中に記載されていないファイル一覧を取得したい。

ワンライナーで書けたのでメモ。

find . -name "*.html" -print0 | xargs -0 perl -nle '/hoge/ and close(ARGV) and next; print "$ARGV" if eof(ARGV);'

findでファイルを読み込む。 perlclose(ARGV)は現在読み込み中のファイルをクローズする操作で、ただ、クローズ直後はファイルポインタがEOFになっただけなので、nextしてループを次にすることで、次のファイルを読み込みに相当させる。Perlではループ終了のための構文はbreakではなくてlastだが、上記でnextじゃなくてlastを記述すると、プログラム自体が終了してしまう。

雑な解説。

GCPでプロキシサーバ設置squidの導入

Webサイトを別サーバーに移転してあるタイミングでサーバーにアクセスできなくなった。社内以外のネットワークだとアクセスできる模様…

結論からいうと、GCEのサーバに何度かsshログインに失敗すると、pingさえ通らないようになるらしい。(リセットすれば治る)

ただ、そのことに気づくまでにあれこれ試して、プロキシまでたてた。今となっては不要だけどメモだけ。

プロキシサーバ

squidを使ったよ

プロキシサーバの設置

以下プロキシサーバ側

構成

  • Google Compute Engine
  • Ubuntu 16.04 LTS

Ubuntu 大好き人間です。はい。

CentOSとかが無難なのかな、とか思いますが、グローバルなシェアでみるとUbuntuの方が今多数派らしい。

squidインストール

$ sudo apt install squid

設定

$ sudo vim /etc/squid/squid.conf

以下コメント。

...
# 1190行目
# http_access deny all
...
# 1599行目
# http_port 3128
...

ポート3128と8080はよく使われるポートらしいので避ける

で、以下を末尾に追記。

# noneにしてるけれど任意でいい
visible_hostname none

# ポート,8080,3128以外を推奨
http_port 43288

# 許可するIPアドレスを記載
acl myacl src X.X.X.X/32
http_access allow myacl
http_access deny all

# プロキシサーバ接続端末のIP隠蔽
forwarded_for off

# プロキシ経由のアクセスをアクセス先に隠蔽
request_header_access X-Forwarded-For deny all
request_header_access Via deny all
request_header_access Cache-Control deny all

キャッシュクリアしてリスタート

$ sudo squid -z
$ sudo service squid restart

次にファイアウォールも設定しておく。

GCPでは管理コンソールから「ネットワーキング」→「ファイアウォール ルール」から、プロキシ用のポートのアクセスを許可するルールを作成し、VMインスタンスに紐付ける。

詳しくはここ↓

ネットワークとファイアウォールの使用  |  Compute Engine ドキュメント  |  Google Cloud Platform

クライアント側の設定

クライアント端末もUbuntu Desktopで接続した。 ubuntuなら「システム設定」→「ネットワーク」→「ネットワークプロキシ」から設定できるので、設定する。

それかexport http_proxy="http://sample.proxy.com"みたいにすればできるらしい。

いずれにしてもブラウザは再起動しないとプロキシを利用できないようだった。

確認

確認くんというサイトが便利だった。

これで表示されるIPがプロキシサーバのIPならプロキシ経由でアクセスできている。

AWSもそうだけど、GCPなら気軽に海外のサーバをプロキシにできるから、IPブロックとかもうまいこと避けられるんでないかな

ziproxyとかも導入したい

【GAS】Gmailのメール受信をChatworkに通知

zapierChatworkと連携していて便利。Gmailの特定メール受信時に、Chatworkへ通知させてみたら喜ばれた。

けれども無料プランだと

  • 月に100のタスクまでしか処理できない
  • 5つまでしか設定を保存できない

という制限がある。

人は無料だと喜ぶけど、有料になったとたんに「はん。まぁ、金払えば便利にはなるわな」となる。切ない。

とりあえず前者だけ解決したくて、今回GASで書いたときのメモ

理想的な流れ

Gmail受信→Chatwork APIたたく→Chatworkに通知くる

現実

GAS定期実行で新規メール確認→新規メール発見したらChatwork API たたく→Chatworkに通知くる

要するにバッチ処理で新規メールを確認して判定する。ただ、おもむろに1分間隔でバッチ走らせてたら夜にメールがきた。

f:id:nao_0x2c6:20170715225451p:plain

泣いた。

ここ↓をよく読んでおきましょう

https://developers.google.com/apps-script/guides/services/quotas?hl=en

最終的に15分間隔くらいでバッチを回しました。

用意するもの

当然だけど、自分のGoogleアカウントで受信できるメールを通知します。

スクリプトはよ

とりあえずGAS(Google Apps Script)のコードのサンプル。

function run() {
  var title = "メール通知";
  var searchQuery = "from:delivery@mail.com to:me@gmail.com";
  var checkSpanMinute= 900; // 起動時間間隔(15分)
  /* debug
        var roomId = "1010101"; # テスト用のマイチャットルームID
  /*/      
        var roomId = "21212121"; # 通知したいチャットのルームID
  //*/
  
  var dt = new Date();
  dt.setMinutes(dt.getMinutes() - checkSpanMinute);
  
  var threads = GmailApp.search(searchQuery);

  var msgs = GmailApp.getMessagesForThreads(threads);
  for(var i = 0; i < msgs.length; i++) {
    var lastMsgDt = threads[i].getLastMessageDate();
    
    if(lastMsgDt.getTime() < dt.getTime()) {
      break;
    }
    
    for(var j = 0; j < msgs[i].length; j++) {
      var msgDate = msgs[i][j].getDate();
      var msgBody = msgs[i][j].getPlainBody();
      var msgFrom = msgs[i][j].getFrom();
      var matches = msgFrom.match(/"(.+)".*<(.+)>/)
      {
        var subject = msgs[i][j].getSubject();
        var postMsg = "[info]" +
            title + "\n" +
          Utilities.formatDate(msgDate, 'Asia/Tokyo', 'yyyy/MM/dd hh:mm:ss') + "\n" +
              "件名:" + subject + "\n" +
              "[hr]" +
               msgBody + 
            "[/info]";
        
        // リクエストトークン
        var token = {
          "method" : "post",
          "headers" : {
            "X-ChatWorkToken" : "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", // Chatworkの認証トークン
          },
          "payload" : {
            "body" : postMsg,
          }
        };

        var apiUrl = "https://api.chatwork.com/v2/rooms/" + roomId + "/messages"
        var response = UrlFetchApp.fetch(apiUrl, token);

      }
      
    }
  }
}

動作結果 in Chatwork

f:id:nao_0x2c6:20170715231127p:plain 真っ黒じゃねぇか

解説

  var searchQuery = "from:delivery@mail.com to:me@gmail.com";

Gmail検索クエリ。Gmailの検索バー?で入力してみて、通知させたいメールだけヒットするか試してみるといい。

参考:Gmail で使用できる検索演算子 - Gmail ヘルプ

複数条件をスペースで入力するとAND検索になる。リンク先には紹介されていないが、NOTも使えた。

  /* debug
        var roomId = "1010101"; # テスト用のマイチャットルームID
  /*/      
        var roomId = "21212121"; # 通知したいチャットのルームID
  //*/

あんまりGAS関係ないけど このコメントの書き方は

新卒エンジニアでもできる!コメントアウトで同期に差をつける裏技 - Qiita で知った。

debugと書かれた先頭の行に/をつけると、デバッグモードのチャットルームIDが有効になる。

  //* debug
        var roomId = "1010101"; # テスト用のマイチャットルームID
  /*/      
        var roomId = "21212121"; # 通知したいチャットのルームID
  //*/

ほらね。すごい。

ちなみにルームIDは、通知させたいチャットを開いた際のURL末尾のridに続く数字のこと。

例えばhttps://www.chatwork.com/#!rid102030なら102030がルームID。

  var dt = new Date();
  dt.setMinutes(dt.getMinutes() - checkSpanMinute);

分単位の計算。現在時刻からcheckSpanMinute分だけ遡って検索してる

  var threads = GmailApp.search(searchQuery);

  var msgs = GmailApp.getMessagesForThreads(threads);

検索クエリsearchQueryGmailを検索してる。

GmailThreadクラスとGmailMessageクラスの概念をここで把握しておくべきなのだけれど、ざっくり言うとGmailThreadはスレッド(まんまや)。同じ件名でお互いが返信し続けると、Gmailって1つのスレッドで表示されますよね。あのひとまとまりが、GmailThreadクラスのインスタンスとして扱える。

GmailThreadインスタンスには、そのスレッドを構成するGmailMessageクラスのインスタンスを持つ。

Gmail Service  |  Apps Script  |  Google Developers

ここで注意が必要だったのが、それぞれのインデックスの扱い方。

  var msgs = GmailApp.getMessagesForThreads(threads);
  for(var i = 0; i < msgs.length; i++) {
    var lastMsgDt = threads[i].getLastMessageDate();
    
    if(lastMsgDt.getTime() < dt.getTime()) {
      break;
    }
    
    for(var j = 0; j < msgs[i].length; j++) {
      var msgDate = msgs[i][j].getDate();
  
...
    }

上記ではmsgsが2次元のインデックスを持っているが、msgs[i]iが小さいとスレッド中の最終受信メールの時刻がより新しいものとなる。

msgs[i][j]jでは、スレッドi中のメッセージjの時刻がより古いものになる。

から、一定時間前のメッセージで走査を打ち切る場合は、i0から、jはスレッドiメッセージ数-1からインデックスを走査する必要がある。

        // リクエストトークン
        var token = {
          "method" : "post",
          "headers" : {
            "X-ChatWorkToken" : "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", // Chatworkの認証トークン
          },
          "payload" : {
            "body" : postMsg,
          }
        };

        var apiUrl = "https://api.chatwork.com/v2/rooms/" + roomId + "/messages"
        var response = UrlFetchApp.fetch(apiUrl, token);

参考: Class UrlFetchApp  |  Apps Script  |  Google Developers

Chatwork APIはヘッダにX-ChatWorkTokenが必要で、個々の認証トークンを記述する必要がある。

こういった独自のヘッダはコードに示したようにheaders内にKey Value形式で書いたらいい。

ここまでできたら、定期実行のトリガーを仕掛ける。

GASの編集画面のメニューから編集→現在のプロジェクトのトリガー f:id:nao_0x2c6:20170716001110p:plain

実行したい関数を設定(この例ではrun)、「時間主導型」のトリガーにする。当然だが、スクリプトで設定した起動時間間隔に合わせること。

f:id:nao_0x2c6:20170716001541p:plain

課題・未確認事項

エラー処理は特にしてない。スクリプトの実行時間と起動間隔の兼ね合いで、スクリプト実行中に受信したメールは通知されないかも知れない。

ほか

もっとトリガーの種類ほしい。GASつよい。