Quantcast
Channel: Slack Advent Calendarの記事 - Qiita

アイディアが閃いた? それすぐにSlackBotにしましょう。秒で。


温度データSlack投稿システムを「5分」で作ってみた!

$
0
0

Slack Advent Calendar 2018 2日目です!
先日Slackとボタンスイッチセンサーで「呼び出しシステム」を作ってみた!
を投稿したのですが、今回は温度データをSlackに簡単に投稿します!
image.png
作るのは5分でやっちゃいます:sunglasses:

ただ単に温度だけ表示するより、暑いときには別のメッセージが出るようにしたいと思います!

条件 投稿コメント
28℃以上 ○○度です。暑いですね・・・。
20℃以下 ○○度です。寒いですね・・・。
それ以外 ○○度です。

とコメントを変えてみたいと思います!

準備するもの

■実機
WindowsPC
Gravioレンタルデバイスの温度データ

■サービス
Gravio (インストールが必要)
・Gravio Studio Version 2.0.2000
・Gravio Server Version 2.0.1997
Slack(トークンまで事前準備)

以上です。

え?これだけ?
そもそもGravioって??
となるかもしれませんが、そのあたりが気になる方は参考のリンク集を見ていただければと思います。

Gravioの設定

STEP1 センサーの設定

センサーとペアリング

温度センサーとペアリングします。
センサーがチカチカっと光り、以下の画面が表示されれば、ペアリング完了です。
温度センサーペアリング
ペアリングについては、こちらをご参照ください。

センサーの登録

(1) エリア・レイヤーを登録する
画面右上の”+”ボタンをクリックし、エリアを作成します。
エリアを作成すると自動的にレイヤーが作成画面が表示されるので、SENSING DEVICE TYPEにAqara-Temperatureを選択します。

(2) レイヤーにデバイス(センサー)を登録する
一番右のデバイスウィンドウの”⊕”ボタンを選択し、デバイスを追加します。
デバイスのスライドボタンをONにすることで、データの収集が開始されます。

STEP2 Slackの設定

今回は、温度に関する3種類のSlack投稿動作を準備します。

動作 動作ファイル名 コメント
1 温度通知 ○○度です。
2 暑いときコメント 暑いですね・・・。
3 寒いときコメント 寒いですね・・・。

Gravio上で、アクションを作成します。
Slackコンポーネントを設置して、
コンポーネントのプロパティにトークンとチャネル、メッセージを設定するだけです。
動作1を作成した場合は以下を設定します。

項目 内容
Token "取得したトークン"
Channel #general
Text av.Data +"℃です。"

メッセージ内容にセンサーデータを使用する場合は、"av.Data"を使用します。
アクション(動作)ファイル名を編集しておくと、あとで設定をするときわかりやすいです。
(アクション編集画面の左上にあるAction1と書かれているところを変更するとアクション名が変わります)

【2018/1/31追記】
アクション変数について
アクション変数とは、センサーデータとSTEP3で設定する動作条件が一致した場合に実行されるアクション内にて使用できるデータです。
条件と一致したタイミングのセンサーデータ(Data)の他にも、データの取得日時(Timestamp)やセンサーのID(SensorDeviceID)などのデータを使用できます。

STEP3 動作タイミング(トリガー)の設定

トリガー画面から、"+”をクリックしトリガーを作成します。
温度データを受信したタイミングで常に温度を投稿する
トリガーの設定内容は以下のとおりです。

項目 設定値
エリア 温度データのエリア
レイヤー 温度データのレイヤー
アクション名 温度通知
Interval 10(Default)
Classic/Threshold Triger Classic Trigger(Default)
温度 >=0

温度が28℃を超えたとき、20℃を下回ったときのトリガーは、
アクション名と温度の項目を変更し、トリガーを追加します。

変更点

項目 暑いときのトリガー 寒いときのトリガー
アクション名 暑いときコメント 寒いときコメント
温度 >=28 <=20

ここまでで設定が完了です。

動作確認

室温から手で温めたあと、冷凍庫にセンサーをいれて、両方の動作が確認できました!
Slack画面

あとがき

簡単に室温をSlackで共有してみました。
サーバー室や飲食店の室温管理には使えそうですね!
今回は温度センサーを使いましたが、それ以外でも同じようなことができます。
例えば、開閉センサーを使ってドアが空いたタイミングでSlackに通知するといったことができちゃいます!
よかったら、Gravioを試してみてください♪

参考

Gravioとは?

Gravioのインストール方法

温度センサーのデータをエクセルで簡単に表示

Slackとボタンスイッチセンサーで「呼び出しシステム」を作ってみた!

Slackでjoinしている231チャンネルを余すことなく見たい人生だった

$
0
0

この記事はSlack Advent Calendar 2018の3日目の記事です。

どういうわけか?: joinしすぎて見れない問題

皆さんSlackは活用していますか? 僕はSlackがとっても好きです!
チャットツールとしての基本を抑えているのはもちろんのこと、
様々なインテグレーションツールが利用できることや、
何よりAPIを使うことで自分好みの様々なツールやbotが作成できる点が
とても便利&楽しいと感じています。

……が、その反面、いろんなチャンネルの話題を見たい!と思っていると、
気づいたらjoinしているチャンネル数が増えてしまうことがあります。

僕の入っているSlackチームには公開チャンネルだけで6000チャンネルくらいあるので、
その中から業務に関係あるもの、ちょっと興味があるものにjoinするだけでも
チャンネル数が軽く200を超えてしまったりします><;

さすがにこれを全部じっくり見ていたら時間が無くなってしまうので、
普段は大半のチャンネルをミュートにしているのですが、
じっくり見ないにしてもなんとなく雰囲気は感じていたいな……と思ってしまいます。

そうだ、ターミナルに全部流そう

昔、 IRC を使っていたときは LimeChat の下部分に
「全チャンネルの発言が表示される領域」があったため、僕はそれを活用していました。

あの体験ができれば今の僕の悩みも解決できるのでは!?と思い、
ちょっと前にペライチのRubyコードを書いて運用をしてみたところ
かなり僕の生活が豊かになりました!

せっかくなので自分用コードのまま眠らせず、お外に出してしまおう!ということで
今回ちょっとコードを整理したので紹介させていただきます。

Senrigan : Slackの発言をターミナルに流すやつ

senrigan.jpg

Senriganは指定のチームで自分がjoinしているチャンネルの発言を
チャンネルの区別をすることなく、ターミナルに垂れ流すツールです。

「それってターミナル向けのSlackクライアントと違うの?」と思われるかもですが、
Senriganは投稿系の機能がなく、ただただログを表示するように
joinしている全チャンネルのログを流すだけのツールになっています。

Rubygemsとして作成しているので gem install senrigan でインストールできます。

$ gem install senrigan

$ SLACK_TOKEN='ここにキミのlegacy tokenを入れよう' senrigan

なかみ

slack-ruby-client でSlack の Real Time Messaging API に接続し、
そこから送られてくるメッセージイベントの内容を解釈して、それっぽい色をつけて表示します。
シェルの色のコマンド覚えられないので、色付けにはcolorizeを使っています><;

対応しているメッセージ

  • 普通のメッセージ
  • Attachment
  • 編集(Edited)された通知

Editedを表示する機能は僕的にはとても便利で、
面白いことを言った後にすぐ消しちゃう人にも対応することができます。べんり。

ところで、SlackのMarkdownっぽいやつは、
パースの方法が極めて難解であることで有名です。
難解な例

ターミナル表示なのであんまり頑張る必要はないのですが、
さすがにユーザー名が <@U123456> とかなってたら何のこっちゃなので、
そのあたりの最低限の置換はやった上で表示しています。
(でも僕が知らない機能もたくさんあるので、まだまだ足りないかも…)

なお、絵文字は…

諦めよう

macとかで表示できる普通の絵文字はなるべく対応したい気もするので、
gemoji をつかって、文中の :〜: な部分を可能な限り絵文字に置き換えて表示しています。

require 'gemoji'

emoji = Emoji.find_by_alias('smile')
emoji.raw # => 😄

でも gemoji は 🤔 のエイリアスが thinking なので、
Slackの :thinking_face: は引っかからないんですよね……
ちゃんとやるなら、Slack用のエイリアス表が必要だ><

まとめ

僕がSlackを楽しむためにつくった&使っているツールである Senrigan を紹介しました。

おそらくSlackは様々な使われ方をしていると感じており、
きっと各チームごとの特性に合わせた秘密のツールとかもたくさんあるんじゃないかなぁと思っています。

この AdventCalendar でもそういったツールの片鱗が見えたりするかもしれませんね٩(๑❛ᴗ❛๑)۶

荒ぶるSlackのEmojiを静める方法

Slack の /remind を敢えて使わずにリマインダーを自作した話

$
0
0

まえがき

本記事は Slack Advent Calendar 2018 の 5日目の記事です。

何故自作したの?

Slack の /remind って便利ですよね!(仕事でもプライベートでも愛用しています)
/remind って何?」ってなった人はこちらの記事を読んでみてください

slack の /remind 機能まとめ

この時点で普段抱えている課題の解決に繋がったりするかもしれません。

ただ、実は微妙に痒いところに手が届かなかったりするんですよね。
そして、今回私は Slack API と AWS Lambda Function を使って敢えて Slack のリマインダーを自作することにしました。

/remind でできなくて困ったこと

では、私が「痒いところに手が届かない」と感じたポイントを整理します。

1. 投稿者が Slack bot になってしまう

/remind は Slack bot の標準機能として提供されています。
なので、当たり前ではあるのですが Slack bot の投稿としてリマインドされてしまいます。

例えば下記のような /remind を登録した場合

/remind @channel 

忘年会の出欠連絡の締め切りは本日の 19:00 までです!
忘れずに 19:00 までにご連絡ください!

at 18:00 on today

リマインドは下記のように表示されます。
スクリーンショット 2018-11-23 18.00.23.png
機能としては全然問題ないのですが
やっぱりアイコンもユーザー名も自分の好きなように設定したいじゃないですか。

2. チャンネル宛のリマインドはリマインド設定したことがバレてしまう

例えば #hoge チャンネル宛に下記のような /remind を登録した場合

/remind #hoge @here

そろそろお昼にしませんか?

at 12:00 on every weekday

#hoge チャンネルには下記のようなメッセージが表示されます。
スクリーンショット 2018-11-23 23.14.42.png

やっぱり機能としては問題ありませんが、
やっぱりリマインドを設定したことはチームメンバーにはサプライズにしたいじゃないですか。

3. 登録したリマインドを編集することができない

現在のところ /remind で登録したリマインドの内容(文言や宛先や日時)を再編集することはできません。
登録した内容を削除して再登録することになります。

先程の #hoge チャンネルへの /remind 登録のときに
間違えて「そろそろお春にしませんか?」と登録してしまった場合は

  1. 間違えた文言のリマインドの登録メッセージがチャンネルに表示される
  2. 間違えたからリマインドの削除
  3. 正しい文言でリマインド再登録
  4. 再度リマインド登録メッセージがチャンネルに表示される

となりますね。
折角ならば痕跡を残さずにスマートにリマインドを運用したいじゃないですか。

え?気にしすぎ?

ですよね笑

なので、「敢えて」自作した話なのです。
それでは実際に作ったものをご紹介します。

実際に作ったもの

こちらのリボジトリに

  • 今回必要な AWS 系のリソースを作成する CloudFormation のテンプレート
  • Lambda Function の index.js

を格納しています。

自作した Slack リマインダーの仕組みは大体こんな感じです。
画像作成用.001.jpeg

登場するエッセンス

  • CloudWatch Events
    • /remindの at や on で指定する「いつリマインドするか」にあたる部分を設定
    • スケジュールの設定方式についてはこちら
    • 設定したスケジュールにしたがって、Lambda Function にイベントを通知する
  • Lambda Function
    • /remind「どのチャンネルにどんな内容でリマインドするか」に当たる部分を設定
    • CloudWatch Events からのイベントが通知されると処理を行う
    • 実際は Slack API を呼び出すだけ
  • Slack API
    • Slack の Workspace の管理者がアプリを登録することで Slack API 用のトークンが払い出されるので、そのトークンを指定して Slack API を呼び出す
    • トークンには呼び出せる API のスコープなどの認可情報が含まれている
    • 今回は chat.postMessage という API を使用している
  • Slack Workspace
    • 今回リマインドを投稿したい対象のワークスペース

もっとざっくり分けるならば

  • Slack にシステムからメッセージを投稿する仕組み
  • 実行タイミングをスケジューリングできる仕組み

の組み合わせです。
では、それぞれの仕組みについてもう少しだけ詳しく見ていきましょう。

⬛️ Slack にシステムからメッセージを投稿する仕組み

基本的に下記の記事を参考にさせていただき、簡単に作ることができました。

Slack のアカウントを持たざる人(モノ)向けに匿名で投稿できる仕組みを作る

では、実際の Lambda Function のコードを見てみましょう。

index.js
const axios = require('axios')

exports.handler = async (event, context) => {
    try {
        const url = `https://slack.com/api/chat.postMessage`;
        const params = {
            "channel": "*****", //投稿したいチャンネルのチャンネルID
            "text": "リマインドだよ!", 
            "icon_emoji": ":slack:", 
            "username": "AdventCalendarReminder" 
        }

        const config = {
            headers:{
                'Authorization': "Bearer *******", //Slack API のトークン
                'Content-type': 'application/json' 
            }
        }

        await axios.post(url, params, config);
    } catch (err) {
        console.log(err);
    }
};

チャンネルIDを取得する方法は下記の記事を参考にさせていただきました。

Slack — APIに使う「チャンネルID」を取得する方法

こちらの Lambda Function 単体実行で Slack にメッセージが投稿される状態が出来上がります。

⬛️ 実行タイミングをスケジューリングできる仕組み

これはもはや CloudWatch Events の機能そのものがカバーしているので
いつ実行するか?のスケジュールの設定と実行したい Lambda Function を指定すれば完了です。
スクリーンショット 2018-11-24 2.24.35.png

自作したリマインダーでできるようになったこと

今回の自作リマインダーで解決したかったことは下記の3点でした。

  • 投稿者が Slack bot になってしまう
  • チャンネル宛のリマインドはリマインド設定したことがバレてしまう
  • 登録したリマインドを編集することができない

そして、それぞれ

  • 投稿者が Slack bot になってしまう
    • Slack API::chat.postMessage の usernameicon_emoji でカスタマイズ可能
  • チャンネル宛のリマインドはリマインド設定したことがバレてしまう
    • 基本的に AWS 側に設定するだけだからバレない!
  • 登録したリマインドを編集することができない
    • 基本的に Lambda Function の実装を変更すれば後から編集可能

といったように解決されました。

また、Lambda Function で Slack API を呼び出すだけなので、
投稿内容や投稿先のチャンネル等々に条件分岐をつけたり、別の API で取得した内容を投稿するなど、できることはいっぱい増えた気がします。
(というかリマインダーに限定して考えなくてもいいですね笑)

自作したリマインダーでは(今のところ)できないこと

冒頭でも書いた通り、私は /remind を仕事でもプライベートでも愛用しています。
理由は、ちょっとしたリマインド設定ならばほぼ間違いなく /remind で事足りるからです。
(実際態々作らなくても・・・という場面はいっぱいあるので。)

そして、今回自作したリマインダーにはそれはそれで不便な点もあるのです。

  • Slack の Workspace に Slack API のアプリを登録する権限を持っている必要がある
  • AWS の環境が必要
  • リマインドのリスト機能、リマインドの削除機能がない
    • /remind list でリマインド一覧を表示 => 不要なリマインドの削除機能は偉大
    • この機能を自作しようと思うとそれなりに面倒ではあると思います

おわりに

今回の記事を書こうと思ったきっかけとしては
Slack の /remind とても便利な割には意外に知られていないので、もっと知ってもらいたいという気持ちが大前提にありました。

その上で、"敢えて"同じようなことを実現してくれるものを自作してみたというお話を書かせていただきました。
Slack はアプリ連携が豊富な上、Slack API を提供してくれているのでハックが捗るエンジニアとしてはワクワクするチャットツールだと個人的に思っています。
この記事を読んでくださった皆様の Slack ライフがより楽しくなることを心から願っております!

自社導入の経験からSlack導入のアンチパターン

$
0
0

概要

 部門内Slack導入「50人規模の部門にSlackを導入してみた結果」と全社向けSlack導入「500人(全社)規模にSlackを導入する道のり(途中)」の経験から導入のアンチパターンとして残したいと思います

 部門内Slackは前述のように部門内の文化として根付いていて、個人的には成功していると感じています。しかし、全社用は残念ながら成功とは言えない状況(現時点失敗)です。Slackの適用範囲や企業文化によっては様々な対応が必要かもしれないが、これから導入を考えている方や導入しているがうまくいかない方の参考になれば幸いです

導入したらフォローしない(もしくは遅い)

 導入直後に使い方や要望などの各種問い合わせがありますが、その返信に時間がかかったり、返信しなかったりすることで利用者の熱量が下がっていき、結果的に使う気がなくなっていきます

対策

 とにかくすぐにフォローする。即断できない内容はいつまでに回答すると期限を設けて対応すること。のちに経過報告や結果の返答を忘れずに!(鉄は熱いうちに打て)

ハードルが1mmでも大勢の人がコケる

 全社Slack導入では、参加するためには利用申請しなければならないことになった。そのため、あえて参加しようと思わない人もそれなりにいるので、未だに参加していない人がいる。そうするとチーム内でそうした人がいると結局連絡手段が従来のままに引きずられるので、アカウントを発行したが、使わない人が続出する状況になりました

対策

 好奇心旺盛なメンバーではない限り、使い始めるまでの心理的ハードルがどうしても高く感じるので、使い始めるまでのハードルを無くす事に努めるべき。最初は一般的な活字コミュニケーションのマナーと必要最低限かつ最重要な禁止事項で運用開始し、徐々にルールを加えていく

誰も発言しない

 導入直後は多くの人が様子見状態です。その中で誰も発言しなければ、結局発言しづらい雰囲気になってしまって、結局誰にも使われないプラットフォームになる

対策

 運営局や協力者が積極的に話題(業務関連、趣味、育児、食べ物など)提供していくことが必要です。個人用のつぶやき用チャネルや旬の話題などを投稿して、何かと情報が発信されている場なんだなっと印象づけることで、発言するハードルを下げることに繋がる
 さらに誰か投稿した内容に積極的に返信しましょう。誰か1人でも返信があると意外と他の人が発言し始めることがあるので、最初は居酒屋のマスターになったつもりで話を広げるように頑張りましょう

ビジネスパートナー(BP)さんの参加問題

 弊社ではBPさんと協力して業務を実行しているチームが多くあります。そのため、日々の業務連絡は既存のGoogle Apps(ドメイン内のみ利用可能)で行っています。しかし、一部の契約形態のBPさんにSlack経由でプライベート時間に業務連絡していいのか?とかが議論になり、いまだにその問題が解決されていない。結果的にBPさんを含むチームメンバーが参加しきれていないので、利用者も思うように増えていない

対策

 Slackには特定のユーザに特定のチャネルのみに参加してもらえる仕組みがありますので、BPさんや外部ユーザにも限定した情報公開が可能(有償版)。ただ、上記のように契約上のリスクをどう解決していくかはBPさんとチーム内でルール作りが必要です。契約上でそういうリスクがあるからと言って、何もしないよりもどうしたらスムーズに利用できるのかを考えるべき

工数確保

 業態特有のものですが、何をするにも工数を予め見積もってから作業しなければならない。そうすると運営メンバーのSlackサポート用工数も予め確保しないと対応できないため、結果的に対応がおろそかになる(前述のフォローが遅くなったりする)

対策

 質問の返答、設定変更、導入直後の盛り上げタスクに要するコストは組織として理解する必要があり、本業として見なしてあげる必要がある。対応時間が大幅に大きくなった時は、時間を確保してしっかりと対応する

なんでもかんでもチャットで会話しようとする

 隣にいるのに全部チャットで会話する。かえってコミュニケーションコストが増える

対策

 チャットはあくまでもコミュニケーションのきっかけでしかないので、リアルなコミュニケーションを大事にしてください!ただし、全体に周知しなければならないことや履歴として残したい時の一つの手段にしましょう

まとめ

 最後になりますが、一番大事なこととして単なるツール導入に留まらず、組織文化を形成するまでがゴールになります。こう聞くと壮大で手間がかかると感じられるかもしれないですが、現実問題そのぐらいの覚悟が必要だと思います。もちろん組織規模によってかかるコストが異なりますので、大きな組織の場合は、小さい単位(部門単位)から順に適用していく。それに合わせて理解者や協力者を増やすことで、徐々に定着されていくと思います。可能ならばトップレベルの理解を得られると進みやすいので、まずそこから理解を得られるように努力しましょう!

GCPを使ってサーバレスでSlackの統計データを取れるようにした話

Slackチームつなげるワームホールを開発した話


Slackを便利にするブラウザ拡張taut.rocksをキミは知っているか

$
0
0

Slackはネイティブアプリケーションも提供されているが、Webからでも利用できる。
筆者はブラウザで完結するのが嬉しくてChromeからSlackを使っているが、最近taut.rocksという
拡張機能を知り、試してみた。
GitHub Starも(筆者執筆時点で)142しかなく、もっと知られるべき!と思い、この記事で紹介していく。

なお、taut.rocksはChrome、Firefox、Operaで拡張機能が提供されているが、
筆者はChromeで動かしており、他のブラウザでは未確認。

できること

Mute users

image.png

アイコンもしくはユーザーネームをクリックすると、 Mute というリンクが表示されるようになる。
Muteしたユーザーの発言は表示されなくなる。
また、MuteしたユーザーはPreferences > Messages & Medisから管理できる。

Enable markdown links < オススメ

image.png

こうすると

image.png

こうなる!!

ちなみに、絵文字もリンクにすることができる。

image.png

こんな感じで

image.png

わかりにくいが、クリックするとちゃんとリンク先にジャンプできる。

Show unread messages count on title < オススメ

title(html)に、未読数を表示できる。

Show unread messages count on the favicon

faviconに未読数を表示できる。

Move reactions to the right

image.png

リアクションが右に表示される。見た目がスッキリする気がする。

Threads on channel by default

Also send to #channel にデフォルトでチェックがつく。

Make threads on channel sticky

Also send to #channel の状態を管理してくれる。最後にチェックを入れたかどうかを保持して、
今後それを維持してくれる。

Disable Google Drive previews

Google Driveのプレビューをオフにする。

Disable Url previews

URLを貼り付けると、プレビューが表示されるが、それをオフにする。

Hide status emoji

ユーザーネームの横に表示されるステータス絵文字を非表示にする。

Avoid opening Slack links on the app

Web版のSlackでは、Slackメッセージのリンクをクリックすると、別タブが開いて
ネイティブアプリへの動線が表示されるが、それをオフにする。
そのタブで即該当のメッセージへジャンプできるようになる。

Move sidebar to the right

image.png

チャンネルが表示されるサイドバーを右側に移せる。

Show details when changing a channel

チャンネルを移動したときに、必ず右側にChannel Detailsが表示されるようになる。

インストール

(最新情報は公式を参照。)

まとめ

個人的にはマークダウン記法でリンクを書けるのがお気に入りです。

【高速開発】開発環境のバグをチームSlackに垂れ流してノンストップ開発

$
0
0

この記事の概要・目的

みなさんは、バグレポートツールを使っていますか?

この記事では、バグレポートツールを使って、開発環境のバグもslackに垂れ流すことの恩恵についての話をします。

弊チームでは、ファーストコミットから今に至るまでソースコードの7割をインターン生が書いているのですが、どうやってそれが実現できたか?ということの一つのポイントがバグレポートツールでした。
Git/GitHubその他、slack等のコミュニケーションツール、CI(継続的インテグレーション)については、いわゆるWeb系のみならずSIerでも自然に使われるようになってきているかと思いますが、バグレポートツールはあまり導入していないところも多いのかなと思います。また、バグレポートツールを導入しているところであっても、開発環境のバグまで全て垂れ流している、という事は少ないかなと思っています。
しかし、作業の行き詰まりを解消できるという大きな利点があるので、多くの開発チームに積極的に紹介をしたいと思っています。この記事では、弊チームの実際の事例を通して、そのメリットを説明します。

概要図

スクリーンショット 2018-12-09 19.08.05.png

バグレポートツールとは

この記事は「Bugsnagというツールを使ってSlackにバグを垂流す話」なんですが、そもそもBugsnagって大分類としてはどういうツールなんだっけ、と思いました。(私はわかってなかった)
「バグ管理ツール」という言葉を使うのかな、と思ったのですが、そうするとBugzillaとかバックログみたいなのがかかってきて、どうも違うっぽい。確かに、バグを管理するというよりは、報告・監視するのが目的なので、大分類としてはバグレポートツールというものに相当するようです。

バグレポートツールは、文字通りバグを報告してくれるツールで、Webアプリではサーバー側のエラーはもちろんのこと、クライアントのエラーも捕捉して勝手に通知をしてくれます。
Bugsnagとは:
https://techblog.lclco.com/entry/2017/01/31/233000

類似のサービスにはSentryなどがあります。

参考:Bugsnagのalternative
https://www.slant.co/options/9134/alternatives/~bugsnag-alternatives

slackと連携するとどのようになるか?

Bugsnagでは、デフォルトではエラーを補足するとBugsnagのサービスに記録+メールで連絡してくれますが、通知をslackに連携することもできます。詳細手順はBugsnagのページにあるのでそちらを参照頂くとして、slackに連携すると、このようになります…

スクリーンショット 2018-12-09 9.58.16.png

このような通知のdetailsをクリックすると、BugsnagのWeb画面で詳細を表示することができます:

スクリーンショット 2018-12-09 10.02.31.png

これは、本番やステージング等で意図せぬエラーの捕捉にも当然便利ですが、開発環境にも使えます。
実際、このエラーのRELEASE STAGESはdevelopとなっています。これが実は作業の効率化につながるのです。

ということで、最近(といっても実際に"分析"を行ったのは8月ですが…)の弊チームのバグを分析してみます。

よくあるバグの紹介・分析

よくあるバグ

以下のような頻発系バグは、全部見て5秒(≒slackで通知が来て5秒)で仮説が2〜3個立ちます。
※具体的なバグの内容についてはこの例ではPythonのエラーですが、他の言語・環境でも同様かと思います。

1.★ ReferenceError: Uncaught ReferenceError: foo is not defined

→fooを使っているところでfooが未定義のエラーだね(当たり前)

2−A.★ Neither 'BinaryExpression' object nor 'Comparator' object has an attribute 'selectable'

→(要するに)期待する型が違うね(当たり前)

2−B. 'NoneType' object has no attribute 'strftime'

→ぬるぽのPython版みたいなものだね
※strftime以外もあります。

3−A. sqlalchemy.orm.exc.NoResultFound: No row was found for one()

→SQLAlchemyでデータを取ってくるところのエラーだね(当たり前)
→多分データの不整合か、存在しないデータを取得しようとしているね

3−B. sqlalchemy.exc.ProgrammingError: 1054 (42S22): Unknown column 'foo.bar' in 'field list' ...

→SQLAlchemyでデータを取ってくるところのエラーだね(当たり前)
→だいたいの場合はalembicを実行してないね

4.★ jinja2.exceptions.UndefinedError: 'foo' is undefined

→Jinja2(テンプレート)で使っている変数fooが未定義のエラーだね(当たり前)

Bugsnagにより作業者が受ける恩恵

まず、これらのエラーは、上で実際に見たように、すぐに問題点を指摘できるエラーです。多くの場合は、すぐに解消するのですが、もしこれらが繰り返し起こっているような場合には、作業者に直接声をかけて解消することができます。
また、バグが出る頻度は概ね作業に比例するので、あまりバグが出ていないと、この人の作業は止まっていないかな?というようなチェックもできるようになります。
これはリモートでも有効なので、時差がなければ進捗や詰まりを見にくいリモートでも作業を進めやすくなります。
冒頭の概要図のとおりですね!
(再掲)

スクリーンショット 2018-12-09 19.08.05.png

現在、うちのチームでPythonの開発中に出るバグの8割以上は上記のいずれかに該当するので、つまり8割以上は即座に解決できるというか、ある意味で作業中に出て当然というバグですね。(実際、上のスクリーンショットのエラーも、invalid syntax以外は全てここに挙げたエラーなのでした。)

また、残りの「難しいバグ」についても、普段に見ないバグが流れてきたということで注意することができます。いずれにしても、画面に向き合って作業をしている今の状況をリアルタイムに流すツールとして、一種のコミュニケーションの道具としての価値もあるのです。
私はシャイなのでペアプログラミングとかはつらいのですが、これなら自分のタイミングで見てアドバイスをすることができます。作業の妨げにならずにサポートができる、便利ツールですね。

これは大きな恩恵が得られます。

と、ここまでで話を終えてもよいのですが、せっかくバグをまとめたので、もう少し教訓じみたことについて突っ込んで考えてみます。

エラーの突っ込んだ解説

感想

  • エラーの英語は単刀直入に事実を述べていて、読むとそのまんまだね
  • 静的型付けがあるとランタイムエラーはけっこう無くなるよね
  • FW的なものの仕様を理解しているかいないかで原因を瞬時に判断できるかどうかが変わるものが結構あるよね

(主に型の観点からの)ランタイムエラー防止に向けた解説

まず、前提というか当然の事ですが、エラーの説明は事実を端的に正確に述べています。エラーの文章を読むのはとても大事ですね。英語で書いてあっても身構えず、普通に日本語に訳して考えるとかなり親切な事を言っています。まずはそのことに注意しましょう。

その上で、エラーをコーディング時にどれぐらい防げるかということについて、(Pythonは型宣言を必要としない言語ですが)型や変数定義の観点で考えてみます。1は変数定義を強制すればこのタイプは無くなります。型を付けると2−Aのパターンもだいたい消えます。
4も、Jinja2(テンプレートエンジン)に渡すインターフェイスの定義という意味では、だいたい消えます。
型によって、やはりランタイムエラーをある程度防止することが可能であることがわかりますね。

一方で、3−Aや3−BはDB起因なので、残念ながら型ではどうにもなりません。(例えばJavaでも類似の例外はよく出ます)

3−Bで自分のスキーマが古い事をすぐに疑えるかどうか?はSQLAlchemyの慣れ・経験かもしれません…というのも、このエラーは、(少なくともうちの書き方では)modelが間違ってないと出ない例外で、queryする側で取得するアトリビュートが間違っていてもこのエラーにはならないからです。(クエリだけでモデルと合ってないフィールド書くと2−Aが出ます)
でも、経験と言っても、一ヶ月ぐらいずっと触っていればだいたいわかります。
ただ、インターン生を中心に回すには、自然に慣れるまで待つよりも、最初にひっかかったタイミングでこういうバグだよと教えて、学んでもらった方が効率がよくなるので、それをslackで捕まえて説明しています。

※スキーマ云々について…弊チームでは一人一インスタンス(以上)渡して作業をしており、他の人がPythonのソースコードを更新すると各々のスキーマとソースの間にずれが発生します。migrationをalembicで管理しているので、alembicを実行すると直ります。

「Bugsnagのおかげ」だけではないんだけど…

ここまで、slack×Bugsnagを通して、運用だけでなく開発の現場においてもやれる事の可能性を示しました。
実際、これでどれぐらい効率が改善しているのか?という事を直接示すのは難しいのですが、弊チームの一つのサービスのコード量スタッツを以下に示します。
ファーストコミットから約1年半です。

lines_of_code.png

途中ぼこっと上がっているタイミングは、一部の不要なtsファイルやuglifyされていないwebpackのファイルなどによるものなので無視すると、概ね線型の進捗であることがわかります。
これまでインターンを中心に回していて、コミッターは20人おり、去年の春・去年の秋・今年の春・今年の秋でメンバーがざっくり4回入れ替わっていますが、継続的に開発が進んでいます。
メンバーが入れ替わったり、あるいはそれぞれの人が週1〜2回しか参加しない場合でも開発を止めない為には、コーディングを止める微妙な障壁の解消が必須で、弊チームの場合ではslack×Bugsnagが管理や作業進捗の把握まで含めて非常に重要なツールになっています。

一日250バグまでは無料で使えるので、大変おすすめです。
チームの開発環境を改善しましょう!
https://www.bugsnag.com/

おしまい。

技術(者)を支えるネコ - にゃーんbotを作った

$
0
0

この記事は『Slack Advent Calendar 2018』の11日目の記事です。

概要

技術(者)を支えるネコということで「にゃーん」と発言すると、すかさず猫画像を返してくれるbotを作ってみました。

スクリーンショット 2018-12-11 13.26.46.png

背景

日々過ごしていて「にゃーん」って発言する機会多いと思うんですが、slackで「にゃーん」って言ってもなんの反応なしに虚しく流れていくのを見つめるのは辛いと思ったので作りました。
(ちなみにこのbotを知人のslackで運用しているのですが、割と利用率が高くて嬉しいです)

実装

Slack側はOutgoing webhookを利用して、バックエンドはAWSのAPI Gateway+Lambdaで作ってます。
(Outgoing webhookはdeprecatedになる予定だそうなので、近々Slack Appを使った実装に直したいと思っています)

猫画像は TheCatApi.com で提供されているApiを利用しています。
静止画よりも動画のほうが嬉しさあると考えたので今回はgifで取ってくるようにしてます。

Lambda ソース

lambda
const https = require('https');

const API_URL = "https://api.thecatapi.com/v1/images/search?format=json&mime_types=gif";

exports.handler = (event, context, callback) => {
    https.get(API_URL, (res) => {
        res.setEncoding('utf8');
        res.on('data', (str) => {
            const data = JSON.parse(str);
            callback(null, {
                username: "cat",
                text: data[0].url
            });
        });
    });
};

まとめ

こんなふうにAPI Gateway+Lambdaを利用すれば比較的簡単に作れて、コストもほとんどかけずにslack botを運用できるのでおすすめです。
もちろん、Cloud FunctionsやAzure Functionsといった他のFaaSでも簡単に実現できそうです。

以上、全力で技術者を支えるにゃーんbotの雑な紹介でした。
明日は、takahyonさんの「LINEのメッセージをSlackに転送するBotをAWSで作成してみる。」です!

LINEのメッセージをSlackに転送するBotをAWSで作成してみる。

独身30代サラリーマンの「ひとりSlack」運用について

我が家の救世主、Google Calendar連携Slack botをつくました

$
0
0

この記事は

Slack Advent Calendar 2018 - Qiita の14日目の記事です。

私は個人で使用しているSlackのワークスペースに、
Google カレンダーと Slack を連携させる – Slack 
これを使っているんですが、なんせ通知が来ても彼氏がシカトして予定を忘れやがる。

この窮地を救うため、聞けば返してくれるGoogle Calendar連携botくんを作成しました.

botの概要

  • "今日の予定は?"って聞くと一覧で出してくれる スクリーンショット 2018-12-11 13.33.48.png
  • "今週の予定は?"って聞くと一覧で出してくれる スクリーンショット 2018-12-11 13.37.00.png

予定が丸見えです。

実装内容

Slack API | Slack と Calendar API | Google Developers を使用します

main.go

func main() {
    token := os.Getenv("SLACKBOT")
    api := slack.New(token)
    client, err := gcalendar.Authorize()
    if err != nil {
        logger.Errorf("can not google calendar API authorized")
    }

    // WebSocketでSlack RTM APIに接続する
    rtm := api.NewRTM()
    // goroutineで並列化する
    go rtm.ManageConnection()

    // イベントを取得する
    for msg := range rtm.IncomingEvents {
        // 型swtichで型を比較する
        switch ev := msg.Data.(type) {
        case *slack.MessageEvent:
            switch ev.Msg.Text {
            case "今週の予定は?":
                schedule, err := schedule.New(client, "week")
                if err != nil {
                    logger.Errorf("get event error: %v", err)
                }
                rtm.SendMessage(rtm.NewOutgoingMessage(schedule, ev.Channel))
            case "今日の予定は?":
                schedule, err := schedule.New(client, "day")
                if err != nil {
                    logger.Errorf("get event error: %v", err)
                }
                rtm.SendMessage(rtm.NewOutgoingMessage(schedule, ev.Channel))
            }
        case *slack.InvalidAuthEvent:
            log.Print("Invalid credentials")
        }
    }
}

schedule.go

func New(c *http.Client, duration string) (string, error) {
    srv, err := calendar.New(c)
    if err != nil {
        return "", err
    }

    now := time.Now()
    var end time.Time
    switch duration {
    case "day":
        end = now.AddDate(0, 0, 1)
    case "week":
        end = now.AddDate(0, 0, 7)
    }
    if end.IsZero() {
        end = time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.UTC)
    }

    events, err := srv.Events.List("5t9khdis149i45g9a43dvg7vn8@group.calendar.google.com").
        ShowDeleted(false).SingleEvents(true).TimeMin(now.Format(time.RFC3339)).
        MaxResults(10).TimeMax(end.Format(time.RFC3339)).OrderBy("startTime").Do()
    if err != nil {
        log.Fatalf("Unable to retrieve next ten of the user's events: %v", err)
        return "", err
    }

    var messages []string
    if len(events.Items) == 0 {
        fmt.Println("No upcoming events found.")
    } else {
        for _, item := range events.Items {
            date := item.Start.DateTime
            if date == "" {
                date = item.Start.Date
            }
            messages = append(messages, fmt.Sprintf("%v: %v\n", date, item.Summary))
        }
    }

    return strings.Join(messages, ""), nil
}

gcalendar.go

func Authorize() (*http.Client, error) {
    // If modifying these scopes, delete your previously saved token.json.
    b, err := ioutil.ReadFile("credentials.json")
    if err != nil {
        log.Fatalf("Unable to read client secret file: %v", err)
        return nil, err
    }
    config, err := google.ConfigFromJSON(b, calendar.CalendarReadonlyScope)
    if err != nil {
        log.Fatalf("Unable to parse client secret file to config: %v", err)
    }
    // The file token.json stores the user's access and refresh tokens, and is
    // created automatically when the authorization flow completes for the first
    // time.
    tokFile := "token.json"
    tok, err := tokenFromFile(tokFile)
    if err != nil {
        tok = tokenFromWeb(config)
        saveToken(tokFile, tok)
    }
    return config.Client(context.Background(), tok), nil
}

func tokenFromFile(path string) (*oauth2.Token, error) {
    f, err := os.Open(path)
    if err != nil {
        return nil, errors.New("cannot open file")
    }
    defer f.Close()

    tok := &oauth2.Token{}
    err = json.NewDecoder(f).Decode(tok)
    return tok, err
}

// Request a token from the web, then returns the retrieved token.
func tokenFromWeb(config *oauth2.Config) *oauth2.Token {
    authURL := config.AuthCodeURL("state-token", oauth2.AccessTypeOffline)
    fmt.Printf("Go to the following link in your browser then type the "+
        "authorization code: \n%v\n", authURL)

    var authCode string
    if _, err := fmt.Scan(&authCode); err != nil {
        log.Fatalf("Unable to read authorization code: %v", err)
    }

    tok, err := config.Exchange(context.TODO(), authCode)
    if err != nil {
        log.Fatalf("Unable to retrieve token from web: %v", err)
    }
    return tok
}

// Saves a token to a file path.
func saveToken(path string, token *oauth2.Token) {
    fmt.Printf("Saving credential file to: %s\n", path)
    f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
    if err != nil {
        log.Fatalf("Unable to cache oauth token: %v", err)
    }
    defer f.Close()
    json.NewEncoder(f).Encode(token)
}

あとは go run main.goするなり、buildして実行するなりで動きます。
SlackのAPI登録、GoogleCalendarAPI登録に関しては他の記事を参考になさってください。

まとめ

現状、作ったbotアプリによる生活の向上は見えていません。ただの気休めでした。

ちなみに、冒頭で紹介した現状を招いている素晴らしいアプリは、Slackで見るとこんな感じに表示してくれます。

gcal_view

リマインダーも設定できるし、見た目も綺麗だし最高です。

これからの課題

今回急いで作ったので、Slackの用意したアプリに近づけるため、これからも進化させてゆきたいと思います。

  • 時間をもっとみやすく
  • せっかくのbotなのでもっと愛嬌をもたせたい
  • 時間の設定までしてあげたり
  • Alexaと連携してみたり

日々精進します

デカい!血管うねうねマスクメロン!と叫んでくるSlack Botを作った💪

$
0
0

Slack Advent Calendar 2018 - Qiita の15日目の記事です。

遅刻して申し訳ございません :bow:

概要

みなさんはボディビル大会でのかけ声という文化をご存知でしょうか。

辛く苦しいトレーニングを乗り越え、自分だけの肉体を作り上げてきた選手たち。ステージにたどり着くまでに、どれほどの努力を積み重ねてきたのか。そのバックボーンへの感動と賛美の声が自然と声に出てしまう。それがボディビル大会における"かけ声"の正体。
--- スモール出版 「ボディビルのかけ声辞典」 より引用

最近ではテレビなどでもよく特集されているので、ご存知の方も多いと思います。
(自分もネットで話題になっていて知りました)

とても個性的で面白いかけ声が多く、会社でも一部のメンバーがよく使っていたりまします

エンジニア> このコントローラー、厚みがあってデカい!
エンジニア> (JSファイルが500KBを)切れてる!切れてる!

というわけで、かけ声をランダムに発言してくれるBotを作ってみました💪

作成したもの

スクリーンショット 2018-12-16 09.43.05.png

:muscle: が最後につくか,emoji reaction で :muscle: がつけられたときに反応します🏋️‍♂️

実装

実装は Elixir で行いました。
Slackへの連携部分は Elixir-Slackというライブラリがあったのでそちらを使用しています。

  def handle_event(message = %{type: "reaction_added", reaction: "muscle" <> _}, slack, state) do
    %{item: %{channel: channel}, item_user: user_id} = message
    shout(user_id, slack, channel)
    {:ok, state}
  end

  def handle_event(message = %{type: "message"}, slack, state) do
    %{text: text} = message
    if text |> String.ends_with?(":muscle:") do
      shout(message.user, slack, message.channel)
    end
   {:ok, state}
  end

SlackからのRTMをパターンマッチして処理をかけるので、とても実装が楽でした。

かけ声は ボディビルのかけ声辞典とネットでまとめられたものを載せています。

みなさんもよかったら使ってみてください💪
(個人的には そこまで仕上げるために眠れない夜もあっただろうに が好きです。デプロイした人に言ってあげたい)


public channel を private group にした場合の落とし穴

$
0
0

前書き

エンジニアもすなる Qiita といふものを、私もしてみむとて、するなり。
という感じで、憧れの Qiita に初投稿です。

途中で出てくるコードは https://rubygems.org/gems/slack-api/ を使用して実行しています。

Slack の部屋の種類

Slack には public channel と private channel があり、部屋を作成する際に選ぶことができます。
image.png
作成した部屋を API から取得してみます。
public と private で取得方法が変わります。

public の場合は channels.list です。
https://api.slack.com/methods/channels.list

{
    "ok": true,
    "channels": [
        {
            "id": "C0G9QF9GW",
            "name": "random",
            "is_channel": true,
            "created": 1449709280,
            "creator": "U0G9QF9C6",
            "is_archived": false,
            "is_general": false,
            "name_normalized": "random",
            "is_shared": false,
            "is_org_shared": false,
            "is_member": true,
            "is_private": false,
            "is_mpim": false,
            "members": [
                "U0G9QF9C6",
                "U0G9WFXNZ"
            ],
            "topic": {
                "value": "Other stuff",
                "creator": "U0G9QF9C6",
                "last_set": 1449709352
            },
            "purpose": {
                "value": "A place for non-work-related flimflam, faffing, hodge-podge or jibber-jabber you'd prefer to keep out of more focused work-related channels.",
                "creator": "",
                "last_set": 0
            },
            "previous_names": [],
            "num_members": 2
        }
    ]
}

private の場合は groups.list になります。(作成する時には private channel と表記してるクセに!)
https://api.slack.com/methods/groups.list

{
    "ok": true,
    "groups": [
        {
            "id": "G024BE91L",
            "name": "secretplans",
            "created": 1360782804,
            "creator": "U024BE7LH",
            "is_archived": false,
            "members": [
                "U024BE7LH"
            ],
            "topic": {
                "value": "Secret plans on hold",
                "creator": "U024BE7LV",
                "last_set": 1369677212
            },
            "purpose": {
                "value": "Discuss secret plans that no-one else should know",
                "creator": "U024BE7LH",
                "last_set": 1360782804
            }
        }
    ]
}

id はどうやら、public channel の場合は C からスタートし、private group の場合は G からスタートするようですね。
(今回は関係ありませんが、 user id も U からスタートするようです)

本題

Slack には public channel を private group へと後から変更する機能があります。
さて、これにより変換されてしまったチャンネルの ID は果たして C スタートのままなのか、 G スタートに置き換えられるのか、試してみることにしました。

まずは slack のここから変更してみまして。

image.png

groups.list を叩いてみると

Slack.client.groups_list
=> {"ok"=>true, "groups"=>[]}

?!!
private group 内に存在しません。

念の為 channel.list を見ても、存在していないようです。

Slack.client.channels_list
=> {"ok"=>true,
 "channels"=>
  [{"id"=>"C2W9TBV8W",
    "name"=>"general",
...省略...
    "num_members"=>1},
   {"id"=>"C2W8A2RNF",
    "name"=>"random",
...省略...
    "num_members"=>1}]}

部屋の ID は URL からも調べることができるので、メッセージのリンクを取得して調べてみると、C スタートのようでした。

部屋の ID が分かれば他の API も叩けるので、いくつか試して見ました。

https://api.slack.com/methods/groups.info
見つからず

Slack.client.groups_info(channel: "CEV6FGT6G")
=> {"ok"=>false, "error"=>"channel_not_found"}

https://api.slack.com/methods/groups.history
見つからず

Slack.client.groups_history(channel: "CEV6FGT6G")
=> {"ok"=>false, "error"=>"channel_not_found"}

https://api.slack.com/methods/groups.archive
true で返ってきます、が、実際には archive されていません。(この記事内で一番ヤバい挙動)

Slack.client.groups_archive(channel: "CEV6FGT6G")
=> {"ok"=>true}

https://api.slack.com/methods/groups.unarchive
archive されていない状態での unarchive では、 channel_not_found ではなく not_archived

Slack.client.groups_unarchive(channel: "CEV6FGT6G")
=> {"ok"=>false, "error"=>"not_archived"}

archive された状態での unarchive では、 true で返ってきますが実際には unarchive されていません。

Slack.client.groups_unarchive(channel: "CEV6FGT6G")
=> {"ok"=>true}

https://api.slack.com/methods/chat.postMessage
メッセージの投稿は普通に通りました。

Slack.chat_postMessage(text: "test", channel: "CEV6FGT6G")
=> {"ok"=>true, "channel"=>"CEV6FGT6G", "ts"=>"1545028789.000600", "message"=>{"type"=>"message", "subtype"=>"bot_message", "text"=>"test", "ts"=>"1545028789.000600", "username"=>"Slack API Tester", "bot_id"=>"BEWCY01E3"}}

private 化したチャンネルを public に戻せば正常な状態に戻ってくれないかなと思ったのですが、残念ながら public => private は一方通行で戻せないようです (登録画面に注意書きもありました)
皆様もどうかお気を付け下さい。

標準入力からSlackに通知するツール

マルチチャンネルゲストからシングルチャンネルゲストへの変更可能なSlackユーザーを洗い出す

$
0
0

まえおき

で「マルチチャンネルゲストからシングルチャンネルゲストへの自動降格」の箇所を読んでいて確かにそうだなと思ったので、本記事&コードを書こうと思います。

※トークンなど秘匿箇所は「@@@@@」で明記しています。

やりたいこと

  1. マルチチャンネルゲストで、参加しているチャンネル数が1つだけのユーザーを洗い出す
  2. 洗い出されたユーザーをマルチチャンネルゲストからシングルチャンネルゲストに変更する

SlackのAPIでシングルチャンネルゲストに変更する方法が見当たらなかったので、今回は1のみ対応しようと思います。

実践

実行環境

Node.js環境で実行しようと思います。
2018年12月19日現在で、実際に実行した環境のバージョンは次の通りです。

  • Node.js ---> 10.8.0
  • npm ---> 6.1.0

ソースコード

実際に使用した package.jsonindex.js は下記の通りです。

package.json
{
  "name": "slack-guest",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "check": "node index.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "async": "^2.6.1",
    "slack-node": "^0.1.8"
  }
}
index.js
const Slack = require('slack-node');
const async = require('async');

const SLACK_TOKEN='@@@@@ここにSlackから払い出したトークン@@@@@';
const slack = new Slack(SLACK_TOKEN);

slack.api("users.list", function (err, res) {
    async.each(res.members, function(member, callback){
        if(!member.deleted && member.is_restricted && !member.is_ultra_restricted){
            let channel_list = member.profile.guest_channels.split(',');
            if(channel_list.length == 1){
                console.log(`${member.real_name}さんです。(${member.profile.email}|${member.profile.guest_channels})`);
            }
        }
    });
});

※SLACK_TOKENの箇所は、ご自身のSlackワークスペースから払い出したトークンを設定してください(xoxb-〜〜〜のトークンです)

  1. 上記2つのファイルを任意のディレクトリに格納した後で npm install コマンドを実行してください。 ※これは初回に1度だけで大丈夫です。
  2. node index.js でメイン処理を実行してください。 ※チェックを繰り返す際は、このコマンドだけを再実行してください。

実行結果

参加しているチャンネルが1つだけのマルチチャンネルゲストが存在した場合は、下記のような結果になります。対象者が複数人いれば、複数行結果が返ってきます。

名前さんです。(メールアドレス|["参加チャンネルID"])

console.logの出力内容はお好みで変更しても問題ありませんが、同姓同名や類似の名前で間違えないために、メンバー管理画面でアカウント変更する際はメールアドレスで絞り込んでから変更することをオススメします。

やってみて気付いたこと

使用するAPIは users.list だけで完結できた

SlackのAPIの users.listchannels.list のメソッドを使って、ユーザーと参加チャンネルの突き合わせをしようと試みたところ、 users.list のレスポンスの中に guest_channels という項目があり、そこにゲスト参加しているチャンネルIDが配列で格納されていることに気付きました。

users.listDocumentation には guest_channels の記載がなかったので、実際に Tester にてテスト実行して存在をご確認いただければと思います。

これにより使用するAPIは users.list だけで完結できました。

ただし guest_channels はゲストアカウントのユーザーにしか表示されないので、アカウントの種別を見分ける必要があります。(後述)

ゲストアカウントの見分け方

こちらも users.list の結果から見分けることが可能です。

users.listのレスポンス結果
{
    "ok": true,
    "members": [
        {
            "id": "@@@@@@@@@@",
            "team_id": "@@@@@@@@@@",
            "name": "@@@@@@@@@@",
            "deleted": false,
            ・・・(略)・・・
            "is_admin": false,
            "is_owner": false,
            "is_primary_owner": false,
            "is_restricted": true,
            "is_ultra_restricted": false,
            "is_bot": false,
            "is_app_user": false,
            ・・・(略)・・・
        },
        ・・・(略)・・・
項目 ①シングル ②マルチ 説明
is_admin false false アドミンかどうか
is_owner false false オーナーかどうか
is_primary_owner false false プライマリーオーナーかどうか
is_restricted true true ゲストアカウントかどうか
is_ultra_restricted true false シングルチャンネルゲストかどうか
deleted false false 無効化ユーザーかどうか

有効なマルチチャンネルゲストのみに絞り込みたいときは、下記を条件に絞り込みます。

  • "is_restricted": true でかつ "is_ultra_restricted": false であること
  • また無効化済みユーザーは除外したいため、 "deleted": falseであること

↓↓↓

if(!member.deleted && member.is_restricted && !member.is_ultra_restricted){

参考情報

あとがき

実際に仕事先のSlackワークスペースに対して実施してみたところ、【5人】見つかりました :innocent:
@mollinaca さん、新しい気付きをありがとうございました :relaxed:

Slackを導入していて気付いたいくつかの小さなこと(2018冬)

$
0
0

この記事は『Slack Advent Calendar 2018』の19日目の記事です。

更新:本稿で触れている「マルチチャンネルゲストからシングルチャンネルゲストへの自動降格」について、 @will_meaning さんがAPIを用いた対応に関する記事を書いてくださいました。:clap_tone2:
マルチチャンネルゲストからシングルチャンネルゲストへの変更可能なSlackユーザーを洗い出す

はじめに

2017年に企業向けプランである「EnterpriseGrid」が開始され、その年9月には日本法人も設立されたSlack。
エンジニア界隈では単なるチャットツールに留まらず、API連携や多数のシステム連携が可能なインテグレーションなどなど、特に現場の声に押されて企業向け採用を検討しているみなさまも多いのではないでしょうか。
国内でもDMMYahooでの大規模な採用事例、MercariDeNAでの素敵な活用事例など、導入事例も増えてきているようです。

僕自身、個人的にSlackの大ファンで、シャドーITを防ぐ立場の情報システム部員でありながらひそかに勝手にSlackを使っており(コラ)、さらにちょうど社内でもSlackを使いたい(なんなら使ってる)というケースもぼちぼち出始めて、ようやくちゃんと社として契約を考えねばならぬという空気感がでてきたこともあり、自社へのSlack導入の推進を担当し、今年からPlusプランでの契約を開始することができました。

当たり前ですが、いくら便利なツールであっても、導入して終わりではありません。その後も運用は続きます。
現在自分たちでも社内での運用ポリシやルールを絶賛アップデートしながら策定中ではありますが、そんな中で気付いた点を何点かこの場で共有できればと思います。また、一部内容はヘルプなどを通じてアップデートリクエストも行っています。:thumbsup:

情報システム部員として考えなければならないこと

社内ルールの策定と明文化

社内ユーザに利用してもらうにあたり、いくつかの取り決めが必要になります。例えばアカウントの発行やゲストの招待、チャンネルのプライベート化やインテグレーションの追加など。
これらのルールをまとめ、また利用申請や必要な手続きをフローに起こしドキュメント化、利用希望ユーザへ配布を行えるようにしました。

セキュリティやコンプライアンス

なんらかの情報流出やセキュリティインシデントがあった場合に、迅速にログイン中のユーザ追い出し(強制ログアウト)や、調査/監査ができなければいけません。また、二段階認証を必須にしたり、デバイスコントロールなども行いたいです。便利なツール、スマホデバイスで使えるツールは、それ自体が情報流出の危険性を孕んでいます。適切なデバイス管理、アクセス元の管理が必要です。

残念ながら、デバイス管理は現時点ではEnterpriseGridでしか使えません。今回はここは断念しました。

社外ユーザをゲストとして自社Slackへ招く場合、そのゲストユーザがアクセスできる情報レベルも適切にコントロールできる必要があります。会社単位でNDAを結んでいても、無関係なプロジェクトの情報にアクセスできることは防がねばなりません。

アカウントの管理、棚卸

一つは費用の問題。予算は無限ではないので、不要な費用は発生しないように、使われていないアカウントは定期的に棚卸、不要であれば削除を行わなければありません。この点に関しては フェアビリングポリシーがとても有効でした。
もう一つはセキュリティ。使われておらず放置されたアカウントはそれ自体がセキュリティホールになる危険性を孕んでいます。

上記2点の観点より、定期的に全アカウントの利用状況の確認と、使われていなければ解除、削除を実施する必要があります。

SLA/緊急対応体制

何%のダウンタイムを社として許容するか?またそれをユーザにどう伝えるか?
実際にダウンがあった場合に、どういう対応が必要になるか?

たとえば夜間メンテ作業時に、コミュニケーションに使っていたSlackが突如使えなくなった!という場合情シスへは緊急連絡がかかってくる可能性があります。であれば、そういった事態に備えた体制づくりも必要になります。

ちなみに結論的には何もできません。おとなしくSlack Statusを確認して復旧することを祈りましょう。
※SLAに応じた返金はありますが、返金ではなくてクリティカルなタイミングのダウンタイムを回避したい。。

ちょっと困ったことや改善してほしいこと

本題。いくら今流行りのチャットツールだからといってすべてが完璧なわけではありません。
気付いたところは是非サポートやヘルプへ投げて、ユーザの声を伝えましょう。

プライベートチャンネルの監査

これは厳密には「できないこと」ではありません。

Slackではプライベートチャンネルやダイレクトメッセージで会話される内容は、たとえそのSlackのプライマリオーナーであっても確認することはできません。監査したい場合は、コーポレートエクスポートを利用する必要があり、これはPlusプラン以上でないと利用できません。

この、「プライベートチャンネルの利用状況の確認ができない」ということを発端に、問題が生じることになりました。

マルチチャンネルゲストからシングルチャンネルゲストへの自動降格

Slackのフェアビリングポリシーでは、要するに「14日以上使ってないユーザの費用は発生しないよ」っていうことを説明しています。
Slackでは「通常メンバー」と「マルチチャンネルゲスト」の場合1ユーザとしての費用が発生しますが、「シングルチャンネルゲスト」は無料です。なので、なるべく費用を抑えるためには、ゲストはなるべくシングルチャンネルゲストにしたいし、マルチチャンネルゲストで参加していてもその後不要なチャンネルが廃止/アーカイブされ利用チャンネルが1チャンネルだけになった場合は、マルチからシングルへ降格させたいです。

ところが、上記のケースにおいて、残念ながら自動降格はしてくれず、ユーザ(つまりぼくたち)で作業を行う必要があります。自動化しましょう。:muscle_tone2:

Aさんをマルチチャンネルゲストへ招待し、チャンネル1とチャンネル2へ参加させる
↓
チャンネル2は不要になったためアーカイブされる。Aさんはチャンネル1のみに所属するマルチチャンネルゲストとなる

上記を情シスが正しく捕捉できていないと、本来シングルチャンネルゲスト(=無料ユーザ)であったはずのAさんの分の費用が発生することになります。そして、チャンネル1やチャンネル2がプライベートの場合、情シスはこれを監査できません。

使われていないチャンネルの棚卸し

上記のケースと同様に、不要なチャンネルであれば積極的にアーカイブし、マルチチャンネルゲストはシングルへ降格させたいですが、こちらもプライベートだと棚卸ができません。実態として使われているかどうかわからないプライベートチャンネルにマルチチャンネルゲストが参加していた場合、不要な費用が発生するケースが想定されます。

上記の対応策

今回は対策として、以下の運用ルールを制定しました。

  • 一般ユーザのプライベートチャンネル作成権限をはく奪
  • プライベートチャンネル作成(チャンネルのプライベート化)はすべて情シスの担当者が行う(依頼はSlackの専用チャンネルで @admin 宛てに呟くだけ)
  • 情シス担当者は、作業時にプライベートチャンネルへ情シス管理用アカウントを参加させる
  • 情シス管理用アカウントを通じて、プライベートチャンネルの把握と棚卸を行う

実際、プライベートであるにも関わらず情シス管理用みたいな気持ち悪いアカウントに参加されるのは気持ち悪いという意見はありましたが、これは正直止む無しでした。もしよい解決方法をご存じの方いたら教えてください。。

あるいは、手続きが必要ですがコーポレートエクスポートを活用するという手もあると思います。これは僕たちはまだ試せていませんが、いざというときに必要なものなので確認はしておこうと思います。

グループ機能

ゲストがグループ関連機能を使えない

これめっちゃ不便。。
Slackには任意のユーザグループを作りメンションを飛ばせる機能がありますが、ゲストについては

  • グループへの参加
  • グループ宛てのメンション

どちらもできません。 参考:マルチチャンネルゲスト & シングルチャンネルゲスト
グループへの参加だけでなく、グループへのメンションもできないのです。ちょっとびっくり。

ユースケースとして、外部協力会社のメンバーを招待し、会社単位でのグループを作成することができないことや、ゲストとして参加しているメンバーはこちらのチーム宛てのメンションが使えない(メンションとして@をつけて発言しても、メンション扱いにならない)という状況になります。

これについては暫定的な対応として、メンションの文字列を@込みでキーワードに登録することで自分のチームとしては対応しましたが、グループ機能は使えてほしかったなぁ。。

ユーザグループの削除

しかも、グループは削除ができません。無効化はできます。
外部会社のメンバーをいれるグループを作ってみたものの、参加させることができず、むなしく無効化されたグループが永遠に残り続けることになります。。

アカウントの削除がけっこう大変

Slackではアカウントを無効化する場合に、解除削除の2段階で明確に内容を分離しています。

解除

通常無効化はこちらで十分。解除されたアカウントはログインができなくなりますが、発言したチャットやアップロードしたファイルには影響ありません。また、解除されたアカウントは簡単に有効化することで、再度利用可能になります。

削除

こちらはより重たい処理になります。説明にも記載がありますが、GDPR等のデータ保護、プライバシー保護の観点から実装されている機能で、管理者でも実行できずSlackへの処理リクエストになります。日常運用でここまでを対応するのはちょっとつらいでしょう。
削除されたユーザは @deactivateduser と表記されますが、発言内容までは削除されないようです。

想定される問題

これはレアなケースだとは思いますが、Slackはワークスペース毎にユーザのメールアドレスをユニークな識別子として登録されます(実際にはSlackが自動で割り当てるIDがユニークな識別子ですが、同様にメールアドレスもユニークである必要があります)。
ここで、不要になった解除済みメンバーと同じメールアドレスを持つメンバーを登録する必要が生じた場合(例えば退職済みで社内アカウントは抹消されたが、その後入社したメンバーがシステムにアカウントを再割り当てされたケースなど。あまりないとは思いますが。)、やはりSlackアカウントは削除する必要があります。

これはまぁあっても極めてまれなケースだと思うので、1年に1回、解除済みのアカウントを一斉に削除申請するなどでよいでしょう。

ファイル操作

ファイルの一括削除

Slackのチャンネル上にアップロードされたファイルは、「そのチャンネルにアップロードされたファイル」ではなく、「そのワークスペースに(パブリック/プライベートで)アップロードされたファイル」になります。なにが言いたいのかというと、チャンネルを削除しても、チャンネルにアップロードされたファイルは削除されないということです。
files.listfiles.deleteで「指定チャンネルのタグがついたファイルを一括削除」できるようにしておきましょう。

ファイルの公開範囲

ファイルが「チャンネル」ではなく「ワークスペース」に対してPOSTされる、というのは上記の通りですが、これに関連してもう一つ。
パブリックチャンネルへファイルをアップロードしたあと、そのチャンネルをプライベート化しても、アップロードされたファイルはパブリックのままになります。 これほんと重要。
パブリックチャンネルをプライベートチャンネルに変換するにも記載がありますが、

パブリックチャンネルで変更以前に共有されたファイルは、変更後に非公開になるということはありません。これらのファイルへは、引き続きワークスペースのメンバーによるアクセスが可能です。

いやいやいや、ファイルがアップロードされたチャンネルがプライベート化されたら、そのファイルもあわせてプライベートにしてほしいよ!ってのが後から気づいたことでした。。

表記ゆれ(?)

気になったので。

Slackでは、作成できるチャンネルの区分に、「パブリックチャンネル」「プライベートチャンネル」の2種類があります。また、任意のユーザをグループ化するグループ機能があります。

WebAPIでは:
パブリックチャンネルの操作 → channels.*
プライベートチャンネルの操作 → groups.*
ユーザグループの操作 → usergroups.*

SCIM APIでは:
ユーザグループの操作 → /scim/v1/Groups?
というエンドポイントに対するAPI実行で操作します。どうしてこうなった。
WebAPIでプライベートチャンネルを操作するMethodがgroups.*なのはなにかの悪い冗談だと思いますので、これはきっといずれ修正されるでしょう。

ちなみに、workspaceの情報が知りたければ、 team.*というWebAPIが使えます。
このteam.*におけるteamはワークスペースそのものを指しているようですが、その一方でSlack って何?という文書では

ワークスペースのオーナーが Slack ワークスペースを作成し、チームの管理を手伝ってもらうために管理者を任命します。

と記載されており、ワークスペースとチームは別の概念として捉えられているようです。
また、ワークスペース上で「課金が有効」なユーザ一覧は、team.billableinfoというWebAPIで取得できます。users.*では確認できません。

ちなみに、SCIM APIは管理を自動化したいのであればかなりオススメです。
フェアビリングポリシーで「非アクティブ」となったユーザ検出や、APIでのアカウントの無効化などのインテグレーションは、運用の自動化、運用工数の削減に大きく貢献してくれるでしょう。

まとめ

大規模な導入事例や、いれてよくなった!系の記事はよく見ますが、その一方で当たり前ですがツールとしてはまだまだブラッシュアップが必要な面もあるとは思います。それでも、素晴らしいアプリケーションだと思うので、企業導入で興味ある方は是非営業へコンタクトを取ってみたり、イベントへ参加するなどしてみてください。ぼくは今年SlackFrontiers@SFOに参加しました:relaxed:
missionの買収、機能の公式実装はおそらく2019年には何らかのアナウンスがあるでしょう。

EG導入が前提であれば、Customer Success Managerという特別な営業担当がサポートしてくれるケースもあるようです。※CSM、ポジションは募集してるみたいですが特に存在の説明はないみたい。

情シスの運用担当のみなさま、よいSlackライフを! Slack_icon-icons.com_67081.png

Slack + NuxtでMarkdown対応のブログを作る

SlackからGoogle Calendarを操作するBotを社内で運用してみた

$
0
0

この記事は 「Slack Advent Calendar 2018」 の21日目の記事です。

はじめに

皆さん、Slackは活用していますでしょうか。
私の所属している会社では毎日(平日)Slackでの会話が盛り上がっていてお前らいつ仕事してるんだ?と感じる程です。
また独自の文化やBotもどんどん社員が勝手に導入してカオスな空間になっています。

話変わって、独自の文化という文脈で弊社では全社員が編集権限を持つGoogle CalendarのCalendarに他の人も自由に参加してほしい予定を作り参加してもらうと言う文化(?)があります。
勉強会だったり、またちょっとした社内の催しがカレンダーに追加されたら、そのゲストに自分を追加して…みたいな運用が行われています。

しかしこの自分をゲストに追加するという作業が地味にひと手間であったり、オペレーションミスでイベントを削除してしまったり、ゲストから他の人を除外してしまったりという事故が起こったりします。
こういったオペレーションミスを起こさないために必要なのはユーザーに対して一定の操作規制をかけるのが手っ取り早いです。
しかし外部サービスのUIを変えたり、各ユーザの操作権限を制御するなんてことは出来ません。

ということで、社内でコミュニケーションツールとして、またある種のインフラとして活用されているSlackのBotにその操作を行わせることで解消できるのではないかと考えたため、Google Calendarを操作するBotを作ってみました。

制作から完成まで

当初Google CalendarのUI上で行っていた、自分をゲストに加える操作をそのまま形にしようと実装を行いました。
SlackのOutgoing Webhooksを使ったもので、イメージとしては
コマンド名 add カレンダーのイベントURL
を対象のイベントに参加したい人が叩いてもらう運用です。

しかしこの方法では対象のイベントのURLを参加する各個人が知る必要があり、それを知っているのであればGoogle Calendarでミスが生じるかもしれなくても操作するほうが、タイプ数も少なくて済むという本当に使われるのか?みたいな状態になっていました。

そのため更にユーザの負担を減らし、楽にイベントに参加申請をする方法を模索しました。
そこで目をつけたのがMessage Attachmentsと、そのActionをハンドルすることでした。
Message AttachmentsはSlackのメッセージをカスタマイズする機能で、装飾を行ったり、メッセージに対してボタンを付与したりすることが出来る機能です。
この機能を使いイベントを作った人にSlackでコマンドを叩いてもらって、そのコマンドに反応したBotが送信したMessageのボタンからイベントに参加申請するというフローを作りました。

以下が実際に使われている様子です。
スクリーンショット 2018-12-21 23.29.37.png

この様にユーザに行わせる操作を限定することで、特定の目的を果たすことが容易になりました。
またこれを普段から使っていて馴染みのあるツールに導入するということで、様々なハードルが下がるのではないかと考えられます。

技術に関すること

今回はAzure FunctionsとC#を使用しました。
この技術の選定理由としては、

  • Google CalendarやSlackなど複数のAPIの結果を組み合わせて使う場合、SQLの様にテーブルをガッチャンコする感じでListの操作が出来るLINQがあると便利だなと思った
  • Azure Application Insightsのログ機能が優秀で、エラーで落ちた場合どのリクエストが走ってどこで落ちたのかがわかりやすい

という点があったからです。 (本音を言うとC#が大好きだし会社でC#はやらせられないかなーとか思ったから)

もちろん今回行ったことはC#に限らず、様々な言語でSlackやGoogleの提供しているAPIのライブラリが提供されているため好きな言語で、また好きなホスティング環境で実施することが出来ます。

参考までに今回使用したコード(の一部)を公開いたします。
https://github.com/yamachu/calman

READMEなど一切なく、またボタンを押して参加する機能が社内リポジトリのコードにしか無くて不完全なところもありますが、参考にしてみてください。

おわりに

SlackはBotを作って遊んでみる、というのが本当に簡単にできるプラットフォームであるので、是非とも活用していってほしいです。

依頼したら勝手に遠隔ナンパしといてくれるslack botの開発

$
0
0

経緯

ある日出会い系アプリ中毒に陥っている大学の友達からある依頼をされました。

「 tinder自動化してくんね? 」

この一言がタイトルに書いたbot誕生の経緯です。

機能要求

1. 指定したスポットでLIKEしたい

通常tinderでは現在地から近いユーザーがリコメンドされます。
しかし、課金ユーザーに限っては現在位置以外のスポットを自由に登録してそれを基準位置に設定することが可能です。
つまり、どんな地方の田舎者でも課金すれば渋谷や表参道、日本を飛び出してニューヨークなんかでもマッチングすることができます。

実際にそれを実装している人もたくさんいそうですね。
【Python】TinderのAPIを使って、青山大学周辺で遠隔ナンパする

2. ある程度プロフィール画像でフィルタリングしたい

依頼者は自動化と言ってもちゃんと選別したいらしいです。欲の塊ですね。
まあ頼まれなくてもそうするつもりでしたが

最低限として、「プロフィール画像に顔が写ってない人は除外する」とかは顔認識系が充実しているので実装も容易そうです。

3. Slackから自動スワイプを発火したい

今回の最大の肝です。Slackからbotに依頼する形式でTinderの自動スワイプを行いたいそうです。依頼者は一体どこまで怠慢なんでしょう? というのも...

「tinder自動スワイプ」でググると如何に世の男性が同じことを考えてるのかがわかると思いますが、紹介されている記事ではローカルでpythonスクリプトを実行する形式がほとんどです。
頻繁に自動スワイプしたい人からするとこの点がかなりのネックになります。

tinderの自動化では当然tinderのAPIを利用するわけですが、そのためにはFacebookのアクセストークンが必要で、そのトークンを取得するのに結構手間がかかります。
さらに残酷なことにこのアクセストークンの有効期限が2時間なんですね。こればっかりはどうしようもなく、ローカルで頻繁に実行するのはかなりめんどくさいというわけです。

また、依頼者と共に利用しているSlackのワークスペースには筆者がhubotで作成したbotがいたので、そいつに自動スワイプの機能を追加すればいいじゃんという発想に至りました。

スクリーンショット 2018-12-21 11.44.58.png

↑課題の提出締切をリマインドしてくれるなどの割と便利なbotです。
(ちなみに「けびん」というネーミングと癖のある口調は実在する同級生がモデルになってます。)

以上3つの要求をまとめると以下のような完成イメージになるのかなと考えました。

bot.png

実装

先に完成したbotの流れをまとめるとこんな感じです。

bot2.png

自動スワイプのスクリプト

まずメインとなる自動スワイプのスクリプトです。
tinderAPIを利用している事例ではPythonのpynderというパッケージを利用している例が多いですが、普段から使い慣れているnodeで書きたかったのでnpmパッケージ探したところ、案の定tinder-clientというのがありました。

(async () => {
  const {TinderClient, GENDERS, GENDER_SEARCH_OPTIONS} = require('tinder-client')
  const Crypt = require('./util/crypt') // 暗号化モジュール
  const TINDER_NUM = process.env.TINDER_NUM || 10

  // hubotから発火する際にユーザーIDとアクセストークンが環境変数として渡される
  if(!(process.env.FB_USER_ID && process.env.FB_ACCESS_TOKEN)) {
    console.log('undefined parameter')
    return
  }

  // tinderAPIの認証
  const tinder_client = await TinderClient.create({
    facebookUserId: Crypt.decrypt(process.env.FB_USER_ID),
    facebookToken: Crypt.decrypt(process.env.FB_ACCESS_TOKEN)
  })

  // tinderユーザー設定更新
  await tinder_client.updateProfile({
    userGender: GENDERS.male,
    searchPreferences: {
      maximumAge: 30,
      minimumAge: 18,
      genderPreference: GENDER_SEARCH_OPTIONS.female,
      maximumRangeInKilometers: 30
    }
  })

  // 以下お好みのロジックでスワイプ
  let count = 0
  while(count < TINDER_NUM) {
    // リコメンドを取得し0人だったら終了
    let recommendations = await tinder_client.getRecommendations()
    if(recommendations.results.length === 0) break
    // リコメンドのセットをループ(約15人ずつくらい)
    for(let girl of recommendations.results) {
      await tinder_client.like(girl._id)
      if(++count >= TINDER_NUM) break
    }
  }

  // 以下結果通知等の処理
})

上記コードは説明のため簡潔化したものになります。実際はちゃんと機能要求①②に対応しました。

①指定したスポットでLIKEしたいについては、tinder-clientに用意されているメソッドchangeLocationに緯度経度を渡せばAPIで基準位置を設定可能(課金ユーザーに限る)です。しかし、毎回緯度経度を調べるのは面倒なのでaxiosとGeocoding APIを利用しました。
これでSlackからは地名やスポット名を投げればうまいことやってくれるようにしました。"青学"のような略称でもOKなのでお手軽です。

②プロフィール画像でフィルタリングしたいについては、Face++を利用しました。顔が写っているかが判定できるだけでなく、100点満点のスコアまで出してくれるんで比較的顔のレベルが高い人だけを選んでLIKEすることも可能です。もちろん当てにならないこともありますが、機械学習で顔のスコアが導き出されてしまうなんて恐ろしい世の中ですね...

具体的な使い方は以下の記事が参考になります。
写真を送ると顔を検出して、顔面偏差値まで教えてくれるFace++APIのご紹介

結果通知に関しては@slack/clientを利用するなりしてお好みでどうぞ。

Slackで依頼をリッスン → Travisのビルド発火

botはhubotでの実装が前提となっているのでcoffeescriptになってます。
redisにFacebookのユーザーIDとアクセストークンが保存されていることを想定しています。

tinder.coffee
request = require('request')
# xxxはgithubユーザ名とリポジトリ名
TRAVIS_CI_API_URL = 'https://api.travis-ci.com/repo/xxx%2Fxxx/requests'

module.exports = (robot) ->
  robot.respond /((.+)で)?(めっちゃ)?((かわいい)|(可愛い))子((\d*)人)?ナンパしてきて/, (res) ->
    defalut_target = { location: '渋谷', border: 60, num: 100 }
    user_id = res.message.user.id

    # redisから依頼者のfacebookユーザーIDとアクセストークンを取得
    tinder_users = robot.brain.get('tinder_users') or {}
    unless tinder_users[user_id]
      res.reply "お前のTinderは未登録だぞ"
      return

    fb_uid = tinder_users[user_id].fb_uid
    fb_token = tinder_users[user_id].fb_token

    unless fb_uid and fb_token
      res.reply "まだ準備中だわ"
      return

    # 依頼文からパラメータ抽出
    location = res.match[2] || defalut_target.location
    border = res.match[3] ? 75 : defalut_target.border
    num = res.match[8] || defalut_target.num

    # TravisCI APIオプション
    options =
      url: TRAVIS_CI_API_URL
      method: 'POST',
      headers:
        'Content-Type': 'application/json'
        'Accept': 'application/json'
        'Travis-API-Version': '3'
        'Authorization': "token #{process.env.TRAVIS_TOKEN}"
      json: true
      body:
        request:
          message: 'tinder automation'
          branch: 'master'
          config:
            # 環境変数をパラメータとして利用する
            env:
              REQUESTER_ID: user_id       # 結果通知用として依頼者のslackユーザーIDも渡す
              TINDER_NUM: num             # 人数
              TINDER_LOCATION: location   # 場所
              TINDER_BORDER: border       # フィルタリングするレベル 
              FB_USER_ID: fb_uid          # facebookユーザーID
              FB_ACCESS_TOKEN: fb_token   # facebookアクセストークン

    # TravisCIにpostして発火
    request.post options, (error, response, body) ->
      if !error and response.statusCode >= 200
        res.reply "仕方ねえな。ちょっと待ってろ"
      else
        res.reply "わりぃ。今やる気ねえわ"

これでSlackから自動スワイプを発火したいという要求を実現できました。

ただしこれでは2時間経過したらアクセストークンが無効になってしまうので、トークンを更新してredisに保存しなければなりません。

アクセストークン更新処理

ちなみに実際に手動でアクセストークンを取得する場合はこちらの手順になります
やってみると意外とめんどくさい手順ですが、あくまでブラウザ操作なのでpuppeteerで自動化できます。

TinderAuth.js
const puppeteer = require('puppeteer')
const axios = require('axios')
const FB_AUTHENTICATION_URL = 'https://www.facebook.com/dialog/oauth?client_id=464891386855067&redirect_uri=fb464891386855067://authorize/&&scope=user_birthday,user_photos,user_education_history,email,user_relationship_details,user_friends,user_work_history,user_likes&response_type=token'

module.exports = class TinderAuth {
  constructor(email, password) {
    this.email = email
    this.password = password
  }

  getAccessToken() {
    return new Promise(async (resolve, reject) => {
      // headless chrome起動 & セットアップ
      const params = process.env.CI ? {args: ['--no-sandbox', '--disable-setuid-sandbox']} : {headless: false, slowMo: 50}
      const browser = await puppeteer.launch(params)
      const page = await browser.newPage()

      // facebook login
      try {
        await page.goto(FB_AUTHENTICATION_URL)
        await page.waitForSelector('#email')
        await page.type('#email', this.email)
        await page.type('#pass', this.password)
        await page.click('#loginbutton')
        await page.waitForSelector('button[name="__CONFIRM__"]')
      } catch(e) {
        console.log(e)
        reject(new Error('Puppeteer browsing error in login phase'))
      }

      // AccessTokenのレスポンスをリッスン
      page.on('response', async response => {
        const urlRegex = /\/v[0-9]\.[0-9]\/dialog\/oauth\/(confirm|read)/
        if (response.url().match(urlRegex)) {
          try {
            const body = await response.text()
            const [, token] = body.match(/access_token=(.+?)&/)
            this.access_token = token
            await browser.close()
            resolve(token)
          } catch(e) {
            console.log(e)
            reject(new Error('Puppeteer browsing error in confirm response phase'))
          }
        }
      })

      // ログイン成功すれば確認ダイアログのOKをクリック
      try {
        await page.click('body')
        await page.click('button[name="__CONFIRM__"]')
      } catch(e) {
        console.log(e)
        reject(new Error('Failure to login'))
      }
    })
  }

  async getUserId() {
    if(!this.access_token) return false
    try {
      let {data: {id: uid}} = await axios.get(`https://graph.facebook.com/me?access_token=${this.access_token}`)
      return uid
    } catch(e) {
      console.log(e)
      throw new Error('invalid access token or API error')
    }
  }
}
index.js
const axios = require('axios')
const TinderAuth = require('./TinderAuth')
const Crypt = require('./util/crypt') // 独自の暗号化モジュール
const HUBOT_API_URL = 'https://xxxxx.xxxx/xxxxx'

(async () => {
  // ユーザーデータ(暗号化されたもの)をHubotから取得
  let response = await axios.get(HUBOT_API_URL)
  let users = response.data

  // 全員分アクセストークンを取得
  for(uid in users) {
    try {
      let email = Crypt.decrypt(users[uid].email)
      let password = Crypt.decrypt(users[uid].password)
      const tinderauth = new TinderAuth(email, password)
      let access_token = await tinderauth.getAccessToken()
      let user_id = await tinderauth.getUserId()
      users[uid].fb_token = Crypt.encrypt(access_token)
      users[uid].fb_uid = Crypt.encrypt(user_id)
    } catch(e) {
      console.log(e)
      continue
    }
  }

  // Hubotに投げてredisのデータを更新
  await axios.post(HUBOT_API_URL, users)
  console.log('Succeeded to update access token')
})

hubotにはexpressが組み込まれているので、GETとPOSTでredisのユーザーデータを管理できるようにしています。あとはこれを2時間毎に実行できればOKです。

Puppeteer(headless chrome)が動作して、スケジューリングできて、無料枠で利用できるという条件で考えると以下のような候補があります。

  • CloudWatch + Lambda
  • Google Apps Script + Cloud Functions
  • EC2、GAE

オンプレでも大丈夫ですがヘッドレスブラウザの設定がめんどくさそうです。

理想は自動スワイプと同じTravisでやりたかったのですが、Travisのcronの最低単位がdailyだったので断念しました。

逆になぜ自動スワイプがTravisなのかというと、、、

  • Slackで複数ユーザーから依頼される → 非同期(キューイング)で呼び出す必要がある
  • スワイプ人数が数百人以上の場合がある → スクリプトの実行時間がLambda等の1回の実行時間制限を超えてしまう
  • 月間でめちゃくちゃ利用するやつがいる → 月間のビルド時間制限があるCIとかは使えない

という理由です。Travisはpublicリポジトリであれば無料枠でもビルド時間無制限なのでありがたいです。

完成したbot

slack APIの Attaching content Interactive messages を利用していろいろとカスタマイズした結果こんな感じになりました。

res.jpg

ボタンを設置することで別のslackユーザーもslackからLikeやSuper Likeできるようになりました。

友人のふざけたアイデアから想像以上に凝ったbotが出来上がりましたが、作っててかなりおもしろかったです。
もっと面白いアイデアあればぜひコメントで教えてください!!


某先輩botの開発 ~前編~

$
0
0

Merry Christmas!!

この記事は何?

円滑なコミュニケーションのため, 多機能なSlack botを作ったので雑に紹介します
スクリーンショット 2018-07-15 18.33.54.png

先輩には画像検索やYahoo!ニュースの表示, 電車の遅延情報の表示, 語録を喋る, サイコロを振る, arxivURLを展開する等の機能が搭載されています.

コード

ソースコードはこ↑こ↓
https://github.com/motacapla/hubot-slack-docker

Slack上で登録したスタンプもbotから投稿できます, その他のdemoは記事の最後にあります:
parrot-party.mov.gif

動作環境

git cloneされているものとしてお話を進めます.
あとポータビリティを高くするためdockerを使います(ローカル→AWSインスタンス上へスムーズに移行出来ました)

  • docker
  • docker-compose

ローカルで検証したい場合は, node.jsとnpmはnodebrewを導入すると楽です:
https://qiita.com/taketakekaho/items/dd08cf01b4fe86b2e218

Slack側でhubotを追加する必要があります:
https://qiita.com/mochidamochiko/items/29c2d77715d8a1ff062a

また, arxivのURL展開機能を使うためにはWeb 着信 フックのAPIも追加する必要があります:
https://qiita.com/vmmhypervisor/items/18c99624a84df8b31008

画像検索の機能を持たせるためには, 以下の導入が必要です(in English):
https://github.com/hubot-scripts/hubot-google-images
(検索にはGoogle Custom Search APIを使います)

dockerのインストールからslack上でのhubot動作確認までは以下の記事が参考になりました:
https://qiita.com/miyamiya/items/91d1bac25764a24e820e
https://qiita.com/tubone/items/11a7ceb3e7013139abab

Dockerを使う

色々インストールするのですが, いちいち覚えておくのも面倒くさいので, dockerを使っています. あと, docker-composeが便利なのでコチラを使うこととします.
https://qiita.com/youtanagai/items/ff67ceff5497a0e0b1af

Dockerfile

nodeのイメージからhubot用のimageを作ります.
usernameなどは適宜変更してね
後で知ったけど, このADDを使っているところはCOPYのほうが望ましい?らしい

Dockerfile
FROM node
MAINTAINER tikeda

ENV TZ JST-9

RUN npm install -g yo generator-hubot
RUN npm list -g yo generator-hubot
RUN useradd bot
RUN mkdir /home/bot && chown bot.bot /home/bot

USER bot
WORKDIR /home/bot
RUN  yo hubot --owner "tikeda" --name "yaju-bot" --description "ikisugiii" --adapter slack

CMD cd /home/bot/hubot
RUN npm install cheerio-httpcli --save
RUN npm install cron --save
RUN npm install hubot-google-images --save
RUN npm install hubot-slack-attachment --save

ADD ./scripts/* ./scripts/
ADD package.json package.json

CMD ["bin/hubot", "--adapter", "slack", "-n", "yaju"]

docker-compose

arxivのURLを展開する機能を使うため, 着信WebフックのWebhook URLを使っています.(マークダウンでかっこよくPOSTする必要がない場合は不要です.)
あと環境によってはversionを3にしろとかポートが使われてるとか怒られますので, 適宜変更してやります.
ローカル↔コンテナにおけるファイルのやり取りを実現するため, ./mnt以下をマウントしてやります.こうすると例えばTODOリストであったり, 勤怠管理の機能なども作ったりできる(作るとは言ってない)

docker-compose.yml
version: '2.2'
services:
  yaju-bot:
    container_name: hubot
    build: . 
    image: yaju-bot
    volumes:
      - ./mnt:/mnt  
    ports:
      - "8888:8888"
    environment:
      - HUBOT_SLACK_TOKEN=Slackに追加したhubotAPIのトークン(xoxb-*のやつ)
      - HUBOT_GOOGLE_CSE_ID=Google Custom Search APIのID
      - HUBOT_GOOGLE_CSE_KEY=Google Custom Search APIのKEY
      - HUBOT_SLACK_INCOMING_WEBHOOK=Slackに追加した着信 Web フック
のWebhook URL

動作確認

適切にdocker-composeの環境変数を埋めてあげれば, 動作すると思います.
基本的に, 以下のコマンドだけ使えば良いです.

ビルドからコンテナとbot起動まで
$ docker-compose up -d --build

起動したら, slackのbotを招待してpingしてみましょう.
PONGが帰ってくれば疎通確認okです.

コンテナとイメージ削除
$ docker-compose down --rmi all

イメージごと消すときはオプション--rmi allをつけます. 基本的に毎回付けていいと思います.

機能の追加

自作した関数は./scripts/配下のyaju-bot.coffeeに追加してあげれば良いです. 僕はcoffee-scriptで書いてます(というか人のをパクっていい感じに改良してます)が, node.jsなども動きます.

yaju-bot.coffee
cron = require('cron').CronJob
path = require('path')
cheerio = require('cheerio-httpcli')
exec = require('child_process').exec
msgs = require('../mnt/meigen.json')
arxiv = require('arxiv')
module.exports = (robot) ->
        #淫夢語録を返す機能. respondの場合, メンション付きのmeigen*に反応する. *はワイルドカード.
        robot.respond /meigen(.*)/i, (msg) ->     
                #もしmeigen-allであったら, 名言リストのファイルの中身を全て吐き出す
                if msg.match[1] == '-all'
                        msg.send "@#{msg.message.user.name} *全名言リスト* \n #{msgs}"
                #違うのであればランダムにsend
                else                                
                        message = msgs[Math.floor(Math.random() * msgs.length)]
                        msg.send "#{message}"

        #サイコロを振る機能. hearの場合, メンションなし roll という文字に反応する.
        robot.hear /roll/i, (msg) ->
                max = 100
                min = 1
                range = Math.floor(Math.random() * (max - min) + min)
                msg.send "[#{min}-#{max}]: #{range}"

関数を作りたい場合はここに追加して, どうぞ
あとTab文字にはくれぐれも気をつけてください!インデント云々で怒られ続けてハマりました

demo

例えばYahoo!ニュースは @botの名前 newsで取得できます:
news.mov.gif

arxivのURLはポストされたら勝手に展開してくれます(ただし着信Webフックを使う場合は, 特定のチャンネルでしかsend出来ない?)
arxiv-url.mov.gif

Google Custom Search APIを使うと, animateとimageが使えます. それぞれgifとjpg(or png)を適当に拾ってくるようです:
animate.mov.gif
imageme.mov.gif

疲れたのでプルリクお願いします!何でもしますから!

某先輩botの調教 ~後編~

$
0
0

この記事は何?

ぬああああん疲れたもおおおおおおん
68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f3135333438372f30646432633661622d633636612d613137392d376363392d6635616132336535393130622e706e67.png
性なる夜、皆様いかがお過ごしでしたでしょうか?(私は秋葉原の虎の穴にいました( ^ω^))
♰悔い改めて♰ Slack Advent Calendar 2018 25日目です!

この記事は、某先輩ボットの生みの親で、現在は弊チームを離れてしまった@tikeda_meuの意思を引き継ぎ、Hubot素人の私が社内の円滑なコミュニケーションのために先輩を調教するサクセスストーリー(の道半ば)です。
Slack Advent Calendar 2018 24日目 某先輩botの開発 ~前編~ も合わせてご覧くださいませ。

勉強する際に参考になるページ

真夏の夜のなんとか

何を隠そうシェイクスピアの戯曲『真夏の夜の夢』のこと。詳細は下記。

真夏の夜の淫夢
https://dic.nicovideo.jp/a/真夏の夜の淫夢
真夏の夜の淫夢wiki
https://wiki.yjsnpi.nu/メインページ
「真夏の夜の淫夢」の面白さの要因、徹底解説
https://matome.naver.jp/odai/2141285850773581901

某先輩について

つまり野獣先輩のこと。数々の徳のある名言を後世に残された偉大な先輩。
深夜の研究室で夜な夜な開催された、某先輩や某教団や某泡沫候補の作品の鑑賞会は青春の思い出。

野獣先輩
https://dic.nicovideo.jp/a/野獣先輩
野獣先輩の消息
https://matome.naver.jp/odai/2145823204015925901
野獣先輩の甘くて強引な命令に困っています
https://www.amazon.co.jp/野獣先輩の甘くて強引な命令に困っています-夢中文庫クリスタル-藍川せりか-ebook/dp/B07BNDYCDN

某先輩新説シリーズ

2011年10月、2ちゃんねる半角二次元板に立てられたスレ「野獣先輩女の子説exit」により、野獣先輩の正体に関する論説が増え始めた。この説は、後に登場する数々の仮説のテンプレートとなった。

野獣先輩新説シリーズ
https://dic.nicovideo.jp/a/野獣先輩新説シリーズ
野獣先輩○○説まとめ紹介
https://matome.naver.jp/odai/2137769722627170001
「りゅうちぇる野獣先輩」説に本人驚愕 「これぼくなの!?」
http://news.livedoor.com/article/detail/14767635/

coffee scripts 使用上のポイント

アイスティーしかなかったんだけどいいかな?
yaju-senpai-4.jpg

メリット

  • Python同様インデントブロックで見やすい…?????

デメリット

  • 言うてそんなに見やすくない!「:」の有無で気の持ち用が案外違う
  • インデントに厳しすぎイィ!特にTabを使用する際は注意
  • グローバル変数の作り方に難あり!thisを上手く使いましょう
  • エラー箇所が分かりにくい!エラーは変換後のJavaScript上で返される
  • 人気低すぎイィ!人気が低いとオラに力を分けてくれる記事も少ない

結論

アレはダメみたいですね(食い気味)
メリットあれば教えてください。早急にPythonへの移植を行いたい…

機能の追加

オッスお願いしまーす!!
今後も鋭意追加していきます!とりあえず現状報告分です!
CRgXq0VU8AAmlnn.jpg

calculate & convert

王道を征く
関数電卓先輩&単位変換先輩。
偉い人のコードを写経(git clone)して、初心者でも簡単実装!
そのわりに、多機能でちょっとした計算に使えて便利。
ezgif-4-235e1ecba434.gif

2048

やっぱ好きなんすねぇ
皆んな大好き2048。
こちらも偉い人のコードを参考に若干手直しを加えて簡単実装!
行き帰りの通勤や、気分転換にどうぞ。
ezgif-4-605ed41d66d3.gif

todo

やりますねぇ!
やることリスト。
coffeeに慣れてきた頃に、ゴリゴリ実装。
Slackなら/remindで良いのではとか言わないで。
ezgif-4-ce41c78b6526.gif

YouTube

Foo↑気持ちぃ~
GoogleのAPIは控えめに言って神。
coffeeに慣れてきたところで、リクエストとJSONの練習もかねて実装。
Slackに動画を埋め込めるので、満足度が高い。
ezgif-4-33fa539fdb23.gif

Google MAP

はっきりわかんだね
同じ要領でMapも見れます。
今後も積極的に拡張していきたいAPI。
先輩がグッと賢くみえます。
ezgif-4-6423741a176c.gif

docomoAPI

ブッチッパ
既にチラチラ出てきてますが、先輩はdocomoのAPIで雑談が可能です。
メンションで飛ばしたメッセージは全て雑談処理がされるので、コマンドを実行する際にも雑談が発動します。
一定確率で先輩の語録のオマケつき!先輩と対話できる夢の仕様!
ezgif-4-bf52978706ea.gif

DoS Attack

ホラホラホラホラ
最後にゴリゴリ実装したのは、通称「野獣砲」。
オプションで、チャンネル、ユーザ、回数を設定して、メンション爆撃を仕掛けます。
先輩が招待されているチャンネルならどこでも爆撃できるので、DMで先輩に直接爆撃対象を設定すれば、当該チャンネル当該ユーザに犯人がバレることなく荒らすことができます。
ただし、爆撃中は先輩が機能不全になるのと、非常に重くなるので、場合によっては強制リブートが必要な諸刃の剣です。でも楽しかった。
ezgif-4-4a4fa913d90c.gif

参考文献

非常にお世話になったGitHubはこ↓こ↑
https://github.com/hubot-scripts
雑談対話の先輩の思考力はド↓コ↑モ♡
https://dev.smt.docomo.ne.jp

?????

ezgif-4-b0a6ce8f3266.gif