{"id":19300792,"url":"https://github.com/takuya/ruby-gmail-imap-watch","last_synced_at":"2026-05-16T09:32:02.626Z","repository":{"id":261724111,"uuid":"861403030","full_name":"takuya/ruby-gmail-imap-watch","owner":"takuya","description":null,"archived":false,"fork":false,"pushed_at":"2026-05-13T04:55:52.000Z","size":91,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-05-13T06:38:59.996Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/takuya.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-09-22T19:47:01.000Z","updated_at":"2026-05-13T04:55:57.000Z","dependencies_parsed_at":"2025-12-09T05:07:00.584Z","dependency_job_id":null,"html_url":"https://github.com/takuya/ruby-gmail-imap-watch","commit_stats":null,"previous_names":["takuya/ruby-gmail-imap-watch"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/takuya/ruby-gmail-imap-watch","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/takuya%2Fruby-gmail-imap-watch","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/takuya%2Fruby-gmail-imap-watch/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/takuya%2Fruby-gmail-imap-watch/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/takuya%2Fruby-gmail-imap-watch/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/takuya","download_url":"https://codeload.github.com/takuya/ruby-gmail-imap-watch/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/takuya%2Fruby-gmail-imap-watch/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33096862,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-16T04:41:52.686Z","status":"ssl_error","status_checked_at":"2026-05-16T04:41:52.009Z","response_time":115,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-09T23:15:50.668Z","updated_at":"2026-05-16T09:32:02.607Z","avatar_url":"https://github.com/takuya.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"## Gmail IMAP サーバーに繋いで、Push通知をもらう。\n\nIMAPにはIDLEコマンドが有り、IDLEコマンドで接続待ちをしてると、メール受信の通知を受け取ることができる。\n\nこのことから、GMailのIMAPにIDELコマンドで接続し、PUSHで通知をもらおうということである。\n\n## Installing\n\n## Example \nexample run\n`bundle exec ruby bin/imap-subscribe.rb`\n\nsample usage\n```ruby\n# Load XOAUTH config \nDotenv.load('.env', '.env.sample')\nENV['client_secret_path'] = File.realpath ENV['client_secret_path']\nENV['token_path'] = File.realpath ENV['token_path']\nENV['user_id'] = ENV['user_id'].strip\n##\nraise \"Empty file (#{ENV['token_path']}).\" unless YAML.load_file(ENV['token_path'])\nENV['user_id'] = YAML.load_file(ENV['token_path']).keys[0] if ENV['user_id'].empty?\n\n## enabling imap log\n# ENV['DEBUG']='1'\n\n\nwatcher = Takuya::GmailIMAPWatcher.new\n\n## add event handler \nwatcher.on_message_received do |mail|\n  # @type mail [Mail]\n  puts \" new message delivered.\"\n  puts mail.message_id\nend\n\n## start listening\nwatcher.start\n\n```\n\n## IMAP#idle \n\n`IMAP#idle(timeout,\u0026block)` は、排他的に動く。サーバーからのTCP受信を待ち受ける（PUSH通知）\n\n`idle_done` は idle block 内部で行う必要がある。\n\nidle_done すると idle は停止するが、TCP受信処理は行われる。（⇐ここがややこしい）\n\n例えば、３つのFETCH が来たとき(３つ同時に既読フラグを付けた場合)\n\n```text\n+ idling\n* 2 FETCH (UID 216 FLAGS (\\Seen))\n* 3 FETCH (UID 217 FLAGS (\\Seen))\n* 5 FETCH (UID 218 FLAGS (\\Seen))\n```\n\nidle_done は 最初の uid 216 で呼び出される。しかし、連続でTCP受信している。\nidle_done は実行待ちになる。 ３つのFETCHがそれぞれidle_doneを呼び出し、最初 idle_done が優先されるような動作になる。\n\nそのため、次のようなコードは、３つのFETCHのうち、どのresponseを見ているのだろうか。見失うことになる。\n\n```ruby\nimap.idle(300) do | res |\n  if res.kind_of?(Net::IMAP::UntaggedResponse) \u0026\u0026 res.name == 'FETCH'\n    # uid=216 で done されるので、uid=217,uid=218はココまで来ない。ただしTCP受信と保存はしてる。\n    uid = res.attr[\"UID\"]\n    imap.idle_done\n  end\nend\n## でも受信はしてる\nimap.responses[\"FETCH\"].size # =\u003e 3\nimap.responses[\"FETCH\"].map{|e|e.attr[\"UID\"]} #=\u003e [216,217,218] \n```\n\nなので、次のように、idle_doneを終えてからデータを取り出す必要がある。\n\n```ruby\nloop do\n  last_response = nil\n  imap.idle(300) do |res|\n    if res.kind_of?(Net::IMAP::UntaggedResponse) \u0026\u0026 res.name=='FETCH'\n      last_response = res\n      imap.idle_done\n    end\n  end\n\n  ## after idle_done\n  if last_response.kind_of?(Net::IMAP::UntaggedResponse)\n    case last_response.name\n      when 'FETCH'\n        imap.responses[\"FETCH\"]\n    end\n  end\nend\n\n```\n\nIMAPをスレッドで扱えるようなことがマニュアルに書いてあるが、idle中は動作不良なので注意\n\nIMAPのidle中はTCP（OpenSSL）のコネクションを占拠する。\nIMAP#idle_doneの実装をつかうと、コード複雑になりがち。 idleとidle_doneの実装は問題が多い。\n\nidleを扱うなら、**IMAPコネクションを複数使ったほうがマシ**。\nIDLE専用のコネクションと検索コネクションで別個にIMAPをインスタンス化するほうがスッキリかける。\n\nただし、このレポジトリではコネクション１つで頑張った。コネクション複数つかうような書き方はしていない。\n\n１接続なのでidle時に通知を受け取り後に、idleを中断して通知を処理する。\n\nそのためにミュータブルオブジェクトを使って、通知を保存してメソッドの実装箇所を分けた。\n\n```ruby\n\ndef callback_generator(mutable_object)\n  lambda do |res|\n    if res.kind_of?(Net::IMAP::UntaggedResponse) \u0026\u0026 res.name=='FETCH'\n      mutable_object.body = res\n      imap.idle_done\n    end\n  end\nend\n\nloop do\n  mutable_object = Struct.new(:body).new\n  imap.idle(300, \u0026( callback_generator mutable_object) )\n  ## after idle_done\n  if mutable_object.body.kind_of?(Net::IMAP::UntaggedResponse)\n    case mutable_object.body.name\n      when 'FETCH'\n        imap.responses[\"FETCH\"]\n    end\n  end\nend\n\n```\n\n`lambda`と`Proc.new` には例外キャッチや変数束縛に違いがあるので注意すること。\n\n特に、IMAP.idleはスレッド動作だからProc.newでは例外キャッチができないので注意。\n\n最初`Net::IMAP.responses`の存在に気づいて無かったので、コードが煩雑になった。気づいてたらもっと簡単に書けた気がする。\n\nそのうちすべて書き直したい。\n\n## IMAP EXPUNGE, EXISTS\n\nGMail IMAP で EXPUNGE でメールが「ゴミ箱」「アーカイブ」された場合はuidが変わるので追跡できない。\n\nIMAPはメールボックスごとにUIDが作られるので、ボックスを移動したらUIDは変化する。\n\nまた、idle時にメールを削除（移動）したとき、EXPUNGEとEXISTSがペアで通知されてくる。\n\n## IMAP#select IMAP#examine \n\nGmailをIMAPでメールを読み出すとき、自動的に既読になる。\n```ruby\nimap.select(\"INBOX\")\nenvelope = imap.uid_fetch(uid, 'RFC822')[0].attr['RFC822'] # ここで既読になる。\n```\n\nメールボックスの開き方には２種類ある。\n\n```ruby\nimap.select(\"INBOX\")\nimap.examine(\"INBOX\")\n```\n\nexamineはREADONLYである。リードオンリーだと既読フラグがつかない。\n\nSELECTは便利だけど、同期的に操作される。なので「メール本文」を読み込むと既読になる。\n\nメール本文の開き方（既読をつけずに取得）\n\n```ruby\n# 既読がつく\nenvelope = imap.uid_fetch(uid, 'RFC822')[0].attr['RFC822']\n# 影響なし\nenvelope = imap.uid_fetch(uid, \"BODY.PEEK[]\")[0].attr['BODY[]'] \n```\n\n\nRFC822で開くとき、`select`だと容赦なく既読 `read(:seen)`　にされる。 `examine` の場合は変更されない。 不安定なので、`BODY.PEEK[]`を使うべき\n\n\nまとめると、次のようになる。\n\n\n|fetch/mbox| select | examine |\n|:---:|:---:|:---:|\n|RFC822|**既読**|無変|\n|BODY.PEEK|無変|無変|\n\n\nselect で開かない限り、既読がつくことはない。\n\n- 自動化プログラムから扱うときは基本的に examineで行う。\n- メールアプリなどユーザー操作と連動するときは selectをつかう。\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftakuya%2Fruby-gmail-imap-watch","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftakuya%2Fruby-gmail-imap-watch","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftakuya%2Fruby-gmail-imap-watch/lists"}