2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

DiscordにGmailの未読を流すbotをGASで作った

Posted at

動機

団体で持っているGmailを誰も確認しないで放置する事案が発生したので
GoogleCalenderと同じようにDiscordに流すことにした

前記事

  • 毎日指定時刻に未読メッセージを送信
  • 以下は除外
    • 広告メール
    • 古い未読メール
  • ウィジェットで本文もプレビュー
  • 同一の送信者が一目でわかるように色付け
  • メール本体に飛べるリンク

コード全文

基本構造は前のcalenderと同じ
daily()を走らせると今後も実行される

const
cfg={
  time:'07:00',// 通知の時刻
  webhooks:[// メッセージ送信先のwebhook 複数指定可
    'https://discord.com/api/webhooks/xxxxx/xxxxx'
  ],
  template:{// メッセージのテンプレート
    username:'Gmail-chan',
    avatar_url:'https://mcbeeringi.github.io/img/icon.png'
  }
},
crct=[...Array(256)].map((_,n)=>[...Array(8)].reduce(c=>(c&1)?0xedb88320^(c>>>1):c>>>1,n)),crc=(buf,crc=0)=>~buf.reduce((c,x)=>crct[(c^x)&0xff]^(c>>>8),~crc),// CRC32 大雑把に言うと乱数生成
widget=w=>({//discord embed形式
  color:crc(Utilities.newBlob(w.getFrom()).getBytes())&0xffffff,// 送信者情報からランダムな色を生成
  author:{name:w.getFrom()},// 送信者
  url:`https://mail.google.com/mail/u/example@gmail.com/#all/${w.getId()}`,
  title:w.getSubject(),// 件名
  description:w.getPlainBody()// 本文 改行は全部強制でCRLFになるっぽい
    .replace(/[\r\n]{3,}/g,'\n')// 空白行は詰める
    .replace(/[\s=\-\*]{3,}[\r\n]+/g,'')// 空白又は水平線のみの行削除
    .split('\n').slice(0,5).join('\n')+'……',// 5行
  timestamp:w.getDate().toISOString(),// 送信時刻
}),
send=(w={payload_json:{username:'ぬるぽ',avatar_url:'https://mcbeeringi.github.io/img/icon!.png',content:'ガッ'}})=>// webhook送信
  cfg.webhooks.forEach(x=>UrlFetchApp.fetch(x,{method:'post',payload:{...w,payload_json:Utilities.jsonStringify(w.payload_json)},muteHttpExceptions:false})),
main=w=>(// Gmail→Discord
  w=GmailApp.search(`is:unread in:inbox -category:promotions newer_than:7d`),// 広告ではなさそうな1週間以内の未読メールを含むスレッドを取得
  w=w.flatMap(x=>x.getMessages().filter(y=>y.isUnread())).reduce((a,x)=>(// 未読スレッドを未読メッセージに展開 念の為全て
      a.payload_json.embeds.push(widget(x)),// メッセージからウィジェットを作成してメッセージに追加
      a.payload_json.content+=`- ${x.getSubject()}\n`+x.getAttachments().map((y,i)=>(// タイトルと添付ファイル参照を本文に追加
        i=a.payload_json.attachments.length,
        a.payload_json.attachments.push({id:i,description:`"${x.getSubject()}" attachment file.`,filename:y.getName()}),
        a[`files[${i}]`]=y,
        ` - attachment://${y.getName()}\n`
      )).join(''),
    a
  ),{payload_json:{content:w.length?'':'未読メールはありません',embeds:[],attachments:[],...cfg.template}}),
  Logger.log(w),
  send(w)// 送信
),
daily=_=>(// 指定時刻送信
  ScriptApp.getProjectTriggers().forEach(x=>x.getHandlerFunction()=='daily'&&ScriptApp.deleteTrigger(x)),// daily予約削除
  ScriptApp.newTrigger('daily').timeBased().at(new Date(new Date(Date.now()+864e5).toDateString()+' '+cfg.time)).create(),// 翌日の指定時刻にdaily実行予約
  main()// メイン動作実行
);

解説?

cfg

各種設定項目を収めたオブジェクト

crct crc

CRC32のテーブルと生成関数
bytesを受け取りNumberを返す

同じ文字列から同じランダムな数字が作れれば良かった
zip生成の時に作ったCRC32があったので引っ張ってきた

widget

GmailMessageを受け取りdiscord embed形式に変換する関数
本文から余計な区切り線等を削除したうえで5行程度にまとめる
crcを使って色付け

send

オブジェクトを受け取り送信する
送信形式を指定しないのがみそ

main

GmailのAPIを叩いてwidgetに渡してメッセージ本体を整形
出来上がったらsendに渡す

GmailApp.search(くえり)で目当てのスレッドを取得
クエリについて

  • is:unread 未読のメッセージを含むスレッド
  • in:inbox 受信ボックスのスレッド
  • category:promotions 広告判定のメッセージを含むスレッド -を付けて否定形で使用
  • newer_than:7d 七日以内のメッセージを含むスレッド

スレッドのままでは扱いづらいのでここから未読メッセージを取得した後に
ウィジェット生成と添付ファイルの取得を取得、一つのreduceに詰め込んでいる

daily

毎日実行される関数
今回のトリガーを削除した後に次回の実行時刻を計算してトリガーに追加
ここにmainを入れて一緒に毎日動かす

はまりポイント

広告メールの除外

広告メールには配信解除のリンクが張ってあることが多い
-unsubscribe -配信停止でなんとか乗り切っていたが突破するメールもあった
久々にのぞいたら自動判別してくれるcategory:promotionsができていてこれに差し替えた

連続した改行の削除

.replace(/\n{3,}/g,'\n')ではうまく動かなかった
.replace(/[\r\n]{3,}/g,'\n')が動いたので内部ではすべてCRLFなのかと思われる

String → Uint8Array ?

gasにはUint8Arrayがなくて代わりにbytesがある
基本的に同じように扱える
そしてString→bytesは一度Blobを経由する必要がある模様
Utilities.newBlob(txt).getBytes()

ファイルの送信ができない?

fetch()で送信形式を指定しないと送れる
裏で動いている自動判別がいい感じに動いている?
詳しくはdiscord公式を参照すると書いてある
よくわからんけど動くからヨシ!

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?