tag:crieit.net,2005:https://crieit.net/tags/Slack/feed 「Slack」の記事 - Crieit Crieitでタグ「Slack」に投稿された最近の記事 2021-06-27T09:05:46+09:00 https://crieit.net/tags/Slack/feed tag:crieit.net,2005:PublicArticle/17450 2021-06-27T09:05:46+09:00 2021-06-27T09:05:46+09:00 https://crieit.net/posts/RSS-Slack スーパー完全無料でRSSをSlackに投稿できるやつを作った <h2 id="皆さん、どうやって技術ネタ、キャッチアップしてますか?"><a href="#%E7%9A%86%E3%81%95%E3%82%93%E3%80%81%E3%81%A9%E3%81%86%E3%82%84%E3%81%A3%E3%81%A6%E6%8A%80%E8%A1%93%E3%83%8D%E3%82%BF%E3%80%81%E3%82%AD%E3%83%A3%E3%83%83%E3%83%81%E3%82%A2%E3%83%83%E3%83%97%E3%81%97%E3%81%A6%E3%81%BE%E3%81%99%E3%81%8B%EF%BC%9F">皆さん、どうやって技術ネタ、キャッチアップしてますか?</a></h2> <p>皆さんはどうやって日々日進月歩な技術ネタをキャッチアップしてますか?</p> <p>私はよく企業や個人が書いている技術ブログから情報を得ることが多いです。本当に技術ブログって手軽なのにすごい勉強になりますよね。</p> <h2 id="皆さん、どうやってブログ記事を通知してますか?"><a href="#%E7%9A%86%E3%81%95%E3%82%93%E3%80%81%E3%81%A9%E3%81%86%E3%82%84%E3%81%A3%E3%81%A6%E3%83%96%E3%83%AD%E3%82%B0%E8%A8%98%E4%BA%8B%E3%82%92%E9%80%9A%E7%9F%A5%E3%81%97%E3%81%A6%E3%81%BE%E3%81%99%E3%81%8B%EF%BC%9F">皆さん、どうやってブログ記事を通知してますか?</a></h2> <p>ブログ記事確認はもちろん定期的にブログに訪問するのが一番ですが、なかなか時間の取れない中でそれは酷なので何かしら皆さん工夫していると思います。</p> <p>ブログの更新にあわせてTwitterを更新してくれる企業様であれば、Twitterのフォローをすればいいかもしれませんが、必ずしもそうでもないかもしれませんし、Twitterのフォローには技術以外の話題も飛び交うので、集中して記事を確認することも難しいかもしれません。</p> <p>そういったときに役立つのがRSSです。RSSとは<strong>R</strong>ich <strong>S</strong>ite <strong>S</strong>ummaryの略で、ニュースやブログなど各種のウェブサイトの更新情報を配信するための仕組みやXMLフォーマットのことです。</p> <p>RSSの更新を定期的に取得し、記事更新を教えてくれるRSSリーダーは皆さんお世話になっている人も多いのではないでしょうか?</p> <p>私もGoogle Chromeに拡張としてRSSリーダーを入れていた時期もありました。</p> <h2 id="問題点"><a href="#%E5%95%8F%E9%A1%8C%E7%82%B9">問題点</a></h2> <p>RSSリーダーを使って技術ブログの更新を検知する方法はおそらくデファクトスタンダードだと思いますが、個人的にちょっと問題点がありました。</p> <p>それは、<strong>通勤時間の時間をうまく使ってキャッチアップするのが面倒ということです。</strong></p> <ul> <li>携帯にPCと同じRSSを登録するのがめんどくさい</li> <li>RSSリーダーを開かない <ul> <li>電車に乗っているとTwitterやSlackを開いている時間がほぼ全て</li> <li>Kindleで読書するのも細かく乗り換えがあって中断が多く発生するためストレス</li> </ul></li> <li>(RSSリーダーによって違うのかもしれませんが)タイトルを見て中身を判断するのが難しい</li> </ul> <p>このような悩みがあるため、私は<strong>Slack</strong>の<strong>/feed</strong>機能を使ってRSSを購読してました。</p> <p>が、しかしこれもまたもや問題点。Slackの無料ワークスペースには、Appsが10個までしか登録できないのです。(/feedもAppsを消費します)</p> <p>Slackには他にもAppsをいくつか作って入れているため、実際登録できるRSSは5個くらいになってしまいちょっと心もとない感じになってしまいました。</p> <h3 id="IFTTTはどうなの?"><a href="#IFTTT%E3%81%AF%E3%81%A9%E3%81%86%E3%81%AA%E3%81%AE%EF%BC%9F">IFTTTはどうなの?</a></h3> <p>ちょっと詳しい人だと「じゃあIFTTT」はどうなんです?という意見が聞こえてきそうですが結果的にこちらも不採用。</p> <p>理由は上記とほぼ同じで、無料版だと設定できる数に制限があるためこちらもあえなく不採用。</p> <p>というより、お金出せよって声が聞こえてきますね。</p> <h2 id="じゃあ作ろっか"><a href="#%E3%81%98%E3%82%83%E3%81%82%E4%BD%9C%E3%82%8D%E3%81%A3%E3%81%8B">じゃあ作ろっか</a></h2> <p>ということで、作ります。</p> <p>要求は次の通りのことを満たす必要があります。</p> <ul> <li>無制限にRSSを登録できること</li> <li>更新がある場合のみSlackに投稿すること</li> <li>SlackもAppsを消費しないこと(Custom Integration)</li> <li>できれば内容を要約したものや、OGP画像も一緒に投稿して記事の選別に役立てられる付加機能を作ること</li> </ul> <h2 id="feedparser"><a href="#feedparser">feedparser</a></h2> <p>今回は時間もない中だったのでサクッとPythonで作っていきます。</p> <p>RSSの購読には<a target="_blank" rel="nofollow noopener" href="https://pythonhosted.org/feedparser/">feedparser</a>を使うと便利です。</p> <p>RSS2.0だけでなく、Atomや古いRSSの形式でも難なく読み込んでくれます。</p> <pre><code class="python">import feedparser entries = feedparser.parse('http://feedparser.org/docs/examples/rss20.xml') for e in entries: print(e.title) print(e.link) print(e.summary) </code></pre> <p>Entry Itemへのアクセスはイテレーターになっているので取り出しもかんたんです。</p> <p>RSSのEntry Itemの取り出しはこれで進めます。本当にかんたんでありがたい。</p> <p>さらに便利なのは<strong>published_parsed</strong>という項目がEntry Itemから取れます。</p> <p>こちら、RSSのpublished_dateをdatetimeオブジェクトにパースしてくれます。</p> <p>おかげで、フォーマット差分をあまり意識することなく、更新差分チェック実装ができました。</p> <h2 id="ステート管理"><a href="#%E3%82%B9%E3%83%86%E3%83%BC%E3%83%88%E7%AE%A1%E7%90%86">ステート管理</a></h2> <p>RSSには記事の作成日付(Publish Date)があり、RSSの取得のたびに差分チェックとして活用することができます。</p> <p>なので、以前取得した記事のPublish Dateを記憶して、更新があった場合のみ記事を取得するようにしたいのですが、それには何かしらのDB、もしくはデータ保存する仕組みが必要となります。</p> <p>今回は無料という縛りがあるため、当初はGitHubのレポジトリ上にステートファイルをコミットするようにしようとも思ったのですが、コミットが伸び過ぎてしまうのは色々問題なのでやはりDBを使いたいです。</p> <h3 id="HarperDB"><a href="#HarperDB">HarperDB</a></h3> <p>HarperDBは、データ管理を容易にすることに重点を置いた分散型データベースで、ジョインを含むNoSQLとSQLをサポートしています。</p> <p>NoSQLでSQLがかけるのは便利ですね!!</p> <p>日本ではあまり聞きませんが、<a target="_blank" rel="nofollow noopener" href="https://dev.to/">dev.to</a>とかだとちょこちょこ話題に上がっております。</p> <p>こちらのHarperDB、HarperDB Cloud Instanceというマネージドサービスも提供されており、インスタンスタイプを選ぶだけで、手軽にHarperDBを使うことができるようになっております。</p> <p><img src="https://i.imgur.com/CA1sLCU.png" alt="harperdb" /></p> <p><img src="https://i.imgur.com/48qXVQw.png" alt="img" /></p> <p>え?でもお高いんじゃない?そんな声が聞こえてきますね。</p> <p>なんと、今だけかもしれませんがHarperDB Cloud Instanceの一番最小のInstance構成だと無料で使うことができます!これは嬉しいですね。</p> <div class="table-responsive"><table> <thead> <tr> <th>Name</th> <th>Value</th> </tr> </thead> <tbody> <tr> <td>RAM</td> <td>0.5GB</td> </tr> <tr> <td>DISK</td> <td>1GB</td> </tr> <tr> <td>VERSION</td> <td>3.0.0</td> </tr> <tr> <td>IOPS</td> <td>3000</td> </tr> </tbody> </table></div> <p>正直今回の使い方ではこのレベルで十分です。</p> <p>Python上でのHarperDB操作も<a target="_blank" rel="nofollow noopener" href="https://pypi.org/project/harperdb/">専用のライブラリ</a>が用意されているためかんたんに実装できます。</p> <pre><code class="python">HARPERDB_URL = os.getenv("HARPERDB_URL") HARPERDB_USERNAME = os.getenv("HARPERDB_USERNAME") HARPERDB_PASSWORD = os.getenv("HARPERDB_PASSWORD") HARPERDB_SCHEMA = os.getenv("HARPERDB_SCHEMA", "prd") FILEPATH = "entry.csv" db = harperdb.HarperDB( url=HARPERDB_URL, username=HARPERDB_USERNAME, password=HARPERDB_PASSWORD,) test = db.search_by_hash(HARPERDB_SCHEMA, "last_published", [name], get_attributes=["time"]) for t in test: print(t["time"]) </code></pre> <p>このようにNoSQLライクにHash Attributeを使って検索する感じで実装できます。もちろんValue引きも可能です。(遅くなるのかは不明だがNoSQLなら全走査になりそうなので多分遅い)</p> <p>UpdateやInsertも同様な感じで実施できます。</p> <pre><code class="python">ef insert_last_published(name: str): db.insert(HARPERDB_SCHEMA, "last_published", [{"name": name, "time": 123456789}]) return 123456789 def update_last_published(name: str, time: int): result = db.update(HARPERDB_SCHEMA, "last_published", [{"name": name, "time": time}]) return result </code></pre> <p>また、便利だなと思ったのはやはりSQLでの走査です。</p> <pre><code class="python">def get_entry_urls(): return [{"name": x["name"], "url": x["url"], "icon": x["icon"]} for x in db.sql(f"select * from {HARPERDB_SCHEMA}.entry_urls")] </code></pre> <p>といった具合にテーブルの*Selectやジョインなんかも書くことができます。テーブル全体をなめたいとき、これは楽でいいですね。</p> <p>また、CSV load機能もあり、CSVをHarperDBに食わせることもできちゃったりします。</p> <p>今回はこちらの機能はRSSのEntryURL登録機能として便利に使用させていただきました。</p> <pre><code class="python">import os import harperdb HARPERDB_URL = os.getenv("HARPERDB_URL") HARPERDB_USERNAME = os.getenv("HARPERDB_USERNAME") HARPERDB_PASSWORD = os.getenv("HARPERDB_PASSWORD") HARPERDB_SCHEMA = os.getenv("HARPERDB_SCHEMA", "prd") FILEPATH = "entry.csv" db = harperdb.HarperDB( url=HARPERDB_URL, username=HARPERDB_USERNAME, password=HARPERDB_PASSWORD,) db.csv_data_load(HARPERDB_SCHEMA, "entry_urls", FILEPATH, action="upsert") </code></pre> <p>無料開発で一番ネックになるのがDBですが、正直これだけで大概のアプリは作れてしまうのではないでしょうか?</p> <h2 id="OGP画像を得るには?"><a href="#OGP%E7%94%BB%E5%83%8F%E3%82%92%E5%BE%97%E3%82%8B%E3%81%AB%E3%81%AF%EF%BC%9F">OGP画像を得るには?</a></h2> <p>OGPとは<strong>O</strong>pen <strong>G</strong>raph <strong>P</strong>rotocolの略で、TwitterやFacebookにURLリンクを貼り付けると出てくるあれです。</p> <p><img src="https://i.imgur.com/4LAaL3b.png" alt="img" /></p> <p>実際OGP作成を実装された方ならわかりますが、OGPはHTMLのHeaderに決まりきったmetaタグを記載して表現しております。</p> <pre><code class="html"><meta property="og:type" content="article" data-react-helmet="true"> <meta property="og:url" content="https://blog.tubone-project24.xyz/2021/01/01/mqtt-nenga" data-react-helmet="true"> <meta property="og:title" content="MQTTと電子ペーパーを使って年賀状を作る" data-react-helmet="true"> <meta property="og:description" content="年賀書きたくないマン Table of Contents 一年の計は元旦にあり 注意 年末年始はやってみようBOX MQTT React Hooks Tailwind CSS 電子ペーパー やらないことにしようBOX アーキテクチャー 辛かったこと Hooks…" data-react-helmet="true"> <meta property="og:image" content="https://i.imgur.com/tmkmoVA.png" data-react-helmet="true"> <meta name="twitter:title" content="MQTTと電子ペーパーを使って年賀状を作る" data-react-helmet="true"> <meta name="twitter:description" content="年賀書きたくないマン Table of Contents 一年の計は元旦にあり 注意 年末年始はやってみようBOX MQTT React Hooks Tailwind CSS 電子ペーパー やらないことにしようBOX アーキテクチャー 辛かったこと Hooks…" data-react-helmet="true"> <meta name="twitter:image" content="https://i.imgur.com/tmkmoVA.png" data-react-helmet="true"> </code></pre> <p>Slackのattachmentsに入れる画像はOGPのImageから取るようにします。</p> <h3 id="opengraph-py3"><a href="#opengraph-py3">opengraph-py3</a></h3> <p>PythonでOGPを解析するなら<a target="_blank" rel="nofollow noopener" href="https://pypi.org/project/opengraph_py3/">opengraph</a>ライブラリが便利です。ただし、</p> <pre><code>pip install opengraph </code></pre> <p>でインストールするとPython2用のライブラリがインストールされてしまいまともに動かないので、</p> <pre><code>pip install opengraph_py3 </code></pre> <p>でインストールするようにします。</p> <p>使い方もかんたんで、<strong>opengraph_py3.OpenGraph</strong>でインスタンスを作ってあげれば、<strong>ogp["image"]</strong>にOGPイメージURLが保存されます。</p> <p>一点注意としてopengraphは裏でBeautifulSoapが動いているようで、Headerのないページに対してOGPを取得しようとするとAttributeErrorが出てしまうので例外処理を入れております。</p> <p>本家にPR出すか迷いましたが、2017年から更新がないので骨折り損になりそうなので、やめておきます。</p> <pre><code class="python">import opengraph_py3 def get_ogp_image(link: str): try: ogp = opengraph_py3.OpenGraph(url=link) if ogp.is_valid(): return ogp["image"] else: return "" except AttributeError as e: logger.debug(f"No Head contents: {e}") return "" </code></pre> <h2 id="Favicon"><a href="#Favicon">Favicon</a></h2> <p>できれば、Slack投稿するときに技術ブログのアイコンをブログごとに変えたいなと思ったので、Faviconを取る実装も入れます。</p> <p>Pythonにはfavicon取るためのライブラリ<a target="_blank" rel="nofollow noopener" href="https://pypi.org/project/favicon/">favicon</a>があります。</p> <p>使い方も超かんたんで、<strong>favicon.get</strong>で取得したオブジェクトの配列0番目が一番大きなfaviconなのでそれを取るだけです。</p> <pre><code class="python">import favicon def get_favicon(link): icons = favicon.get(link) if len(icons) == 0: return "" else: return icons[0].url </code></pre> <h2 id="キーワード抽出"><a href="#%E3%82%AD%E3%83%BC%E3%83%AF%E3%83%BC%E3%83%89%E6%8A%BD%E5%87%BA">キーワード抽出</a></h2> <p>さて、今回の醍醐味のキーワード抽出ですがこちらもかんたんに実装できます。</p> <p><a target="_blank" rel="nofollow noopener" href="http://gensen.dl.itc.u-tokyo.ac.jp/pytermextract/">pytermextract</a>という専門用語抽出ツールと形態素解析ライブラリ<a target="_blank" rel="nofollow noopener" href="https://mocobeta.github.io/janome/">janome</a>を組み合わせることでかんたんに実現できます。</p> <p>janomeは本当に便利で、特にCIに乗っけてぐるぐるしたい人にはmecabをインストールする必要も辞書をコンパイルする必要もなく、pipで一発入れれば使えるので重宝しています。</p> <p>pytermextractはPyPI登録されているライブラリではないのでインストールは公式サイトから落としたZIPを展開しsetup.pyから行います。</p> <p>また、janomeもpipでインストールします。</p> <pre><code class="shell">unzip pytermextract-0_01.zip cd pytermextract-0_01 python setup.py install pip install janome </code></pre> <p>まずは、キーワード抽出したいテキストをjanomeのTokenizerにかけて、結果を頻出度から単名詞の左右の連接情報スコア(LR)を算出し、</p> <p>重要度スコアとしてはじき出す、という仕組みらしいです。とは言っても私にはよくわからなったのでサンプルコード丸パクリです。</p> <p>得られる結果は<strong>{"単語": スコア}</strong>となってますので、こちらをスコア順にリバースソートして上位6位を取得する形にしました。</p> <p>しょうもない知識ですが、janomeのTokenizerインスタンス作るところは処理コストがちょっと高いので、リファクタでモジュールトップレベルでの宣言にしてます。</p> <pre><code class="python">from janome.tokenizer import Tokenizer import termextract.janome import termextract.core t = Tokenizer() def extract_keyword(text): tokenize_text = t.tokenize(text) frequency = termextract.janome.cmp_noun_dict(tokenize_text) lr = termextract.core.score_lr( frequency, ignore_words=termextract.janome.IGNORE_WORDS, lr_mode=1, average_rate=1) term_imp = termextract.core.term_importance(frequency, lr) score_sorted_term_imp = sorted(term_imp.items(), key=lambda x: x[1], reverse=True) logger.debug(f"keywords: {score_sorted_term_imp}") return score_sorted_term_imp[:6] </code></pre> <h3 id="RSSのSummaryTextでは精度がでない、そりゃそうじゃ。"><a href="#RSS%E3%81%AESummaryText%E3%81%A7%E3%81%AF%E7%B2%BE%E5%BA%A6%E3%81%8C%E3%81%A7%E3%81%AA%E3%81%84%E3%80%81%E3%81%9D%E3%82%8A%E3%82%83%E3%81%9D%E3%81%86%E3%81%98%E3%82%83%E3%80%82">RSSのSummaryTextでは精度がでない、そりゃそうじゃ。</a></h3> <p>見出し通りですが、当初はfeedparserから取得できるEntry ItemのSummaryをpytermextractに食わせてましたが、SummaryTextが短すぎて全く期待する動作になりませんでしたので、BeautifulSoupを使って、実際の記事の本文を取得しpytermextractに食わせる実装に変更しました。</p> <pre><code class="python">from bs4 import BeautifulSoup import urllib.request as req def extract_html_text(url): res = req.urlopen(url) soup = BeautifulSoup(res, "html.parser") p_tag_list = soup.find_all("p") return " ".join([p.get_text() for p in p_tag_list]) </code></pre> <p>本文はpタグと判断しfind_allするちんけな実装です。ごめんなさい。</p> <h2 id="Slack投稿"><a href="#Slack%E6%8A%95%E7%A8%BF">Slack投稿</a></h2> <p>いよいよSlack投稿部分の作成です。</p> <p>Slack投稿はCustomIntegrationのIncoming Webhookで作ります。</p> <p>なので、<a target="_blank" rel="nofollow noopener" href="https://api.slack.com/reference/messaging/attachments">Slack attachment</a>が使えます。</p> <p>特質したことはないのですが、OGP画像はimage_urlに、faviconはauthor_imageにキーワードはfieldsに入れてます。</p> <h2 id="GitHub Actions化"><a href="#GitHub+Actions%E5%8C%96">GitHub Actions化</a></h2> <p>最後にGitHub Actionsに載せて、定期実行させます。</p> <p>その前にの<a href="#harperdb">#harperdb</a>でも書いたとおり、RSS追加時のHarperDBへのEntry追加の定義を書いていきます。</p> <p>特定のファイルに更新があった場合のみ動くGitHub Actionsを作る場合は、 on_pushなどの条件にpathsを入れることで実現できます。これだけです。</p> <pre><code class="yml">on: push: branches: - main paths: - "entry.csv" pull_request: branches: - main paths: - "entry.csv" </code></pre> <p>また、定期実行にはschedule cronが便利です。</p> <pre><code class="yml">on: push: branches: - main pull_request: branches: - main schedule: - cron: "*/30 * * * *" </code></pre> <h2 id="完成"><a href="#%E5%AE%8C%E6%88%90">完成</a></h2> <p>ということでできました。</p> <p><img src="https://i.imgur.com/Ip4IaYs.png" alt="img" /></p> <p>entry.csvに書いたRSS feedを30分ごとに確認しにいき、前回よりpublish_dateの更新があったばあいはOGP, favicon, キーワード付きでSlack投稿します。</p> <p>レポジトリはこちらです。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/tubone24/tech_blog_spider">https://github.com/tubone24/tech_blog_spider</a></p> <p>ForkするとGitHubA ctionsがうまく発火しないっぽいので、もし利用する際はgit cloneして自身のレポジトリに再Pushして使っていただければと思います。</p> <h2 id="結論"><a href="#%E7%B5%90%E8%AB%96">結論</a></h2> <p>HarperDBを使って何でもつくれそうな予感がするこの頃です。</p> tubone24 tag:crieit.net,2005:PublicArticle/16495 2021-01-03T18:57:47+09:00 2021-02-08T12:50:39+09:00 https://crieit.net/posts/Slack-remind 【Slack】/remindで平日に繰り返しリマインドする方法 <p>slackで平日の16:50 に 「日報書いてね!」周知をgeneralチャンネル全体に繰り返し行いたい場合 の設定方法メモ</p> <h1 id="1. リマインド設定メッセージの書き方"><a href="#1.+%E3%83%AA%E3%83%9E%E3%82%A4%E3%83%B3%E3%83%89%E8%A8%AD%E5%AE%9A%E3%83%A1%E3%83%83%E3%82%BB%E3%83%BC%E3%82%B8%E3%81%AE%E6%9B%B8%E3%81%8D%E6%96%B9">1. リマインド設定メッセージの書き方</a></h1> <pre><code>/remind #general 日報書いてね! at 16:50 every weekday </code></pre> <p>送信ボタンを押すと設定されます</p> <h2 id="実際の画面"><a href="#%E5%AE%9F%E9%9A%9B%E3%81%AE%E7%94%BB%E9%9D%A2">実際の画面</a></h2> <h3 id="メッセージ"><a href="#%E3%83%A1%E3%83%83%E3%82%BB%E3%83%BC%E3%82%B8">メッセージ</a></h3> <p><a href="https://crieit.now.sh/upload_images/9ee454d649a6ca9032e6cd240313a5b25ff194861c380.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/9ee454d649a6ca9032e6cd240313a5b25ff194861c380.png?mw=700" alt="1a5f0e6b1d8f3c4a2291d9ef2664826c.png" /></a></p> <h3 id="メッセージ送信後"><a href="#%E3%83%A1%E3%83%83%E3%82%BB%E3%83%BC%E3%82%B8%E9%80%81%E4%BF%A1%E5%BE%8C">メッセージ送信後</a></h3> <p><a href="https://crieit.now.sh/upload_images/d8a8524a0153b67f1e2046eab4ae66035ff19497504a4.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/d8a8524a0153b67f1e2046eab4ae66035ff19497504a4.png?mw=700" alt="02ccf531952d3a44ea6a87ce0b7e36c6.png" /></a></p> <p>ちなみに、以下のようにgeneralチャンネルにログが出るので、こっそり設定はできない模様</p> <p><a href="https://crieit.now.sh/upload_images/8a31c8dae8767de3881a663e22021bf75ff194abe4087.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/8a31c8dae8767de3881a663e22021bf75ff194abe4087.png?mw=700" alt="23e341e27c997fc6e4970fdfe5b1ba96.png" /></a></p> <h1 id="2. 現在設定されているリマインドを確認するメッセージの書き方"><a href="#2.+%E7%8F%BE%E5%9C%A8%E8%A8%AD%E5%AE%9A%E3%81%95%E3%82%8C%E3%81%A6%E3%81%84%E3%82%8B%E3%83%AA%E3%83%9E%E3%82%A4%E3%83%B3%E3%83%89%E3%82%92%E7%A2%BA%E8%AA%8D%E3%81%99%E3%82%8B%E3%83%A1%E3%83%83%E3%82%BB%E3%83%BC%E3%82%B8%E3%81%AE%E6%9B%B8%E3%81%8D%E6%96%B9">2. 現在設定されているリマインドを確認するメッセージの書き方</a></h1> <pre><code>/remind list </code></pre> <p>送信ボタンを押すと設定状況が表示されます</p> <h2 id="実際の画面"><a href="#%E5%AE%9F%E9%9A%9B%E3%81%AE%E7%94%BB%E9%9D%A2">実際の画面</a></h2> <h3 id="メッセージ"><a href="#%E3%83%A1%E3%83%83%E3%82%BB%E3%83%BC%E3%82%B8">メッセージ</a></h3> <p><a href="https://crieit.now.sh/upload_images/03aa3effd798fbc84584a47d2382a59a5ff194d0815b1.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/03aa3effd798fbc84584a47d2382a59a5ff194d0815b1.png?mw=700" alt="aa7151fc1e23e6e881f55e96d3ff0c0f.png" /></a></p> <h3 id="メッセージ送信後"><a href="#%E3%83%A1%E3%83%83%E3%82%BB%E3%83%BC%E3%82%B8%E9%80%81%E4%BF%A1%E5%BE%8C">メッセージ送信後</a></h3> <p><a href="https://crieit.now.sh/upload_images/d114e010398c898e33c3af2c9924deef5ff1950050159.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/d114e010398c898e33c3af2c9924deef5ff1950050159.png?mw=700" alt="bf59c834829804c5afa5a8777e6da35e.png" /></a></p> <p>リマインダーを消すときは横の削除リンクをクリックするだけ</p> arohajiro tag:crieit.net,2005:PublicArticle/16481 2021-01-03T18:13:22+09:00 2021-03-01T07:49:01+09:00 https://crieit.net/posts/Slack-Simple-Poll 【Slack】Simple Pollを使って簡易的な投票をする方法 <p>Slackで 「今日のランチ何を食べた?」という質問と、その選択肢に「カレー」「ナポリタン」「ラーメン」「その他」をSimple Pollで作成して、みんなに投票させたい場合 の設定方法についてのメモ</p> <p><strong>Simple PollはすでにSlackのワークスペースに追加されているものとします。追加方法はググればすぐに出てきます。</strong></p> <h1 id="Simple Pollの簡単な使い方"><a href="#Simple+Poll%E3%81%AE%E7%B0%A1%E5%8D%98%E3%81%AA%E4%BD%BF%E3%81%84%E6%96%B9">Simple Pollの簡単な使い方</a></h1> <h2 id="1. 質問の作成"><a href="#1.+%E8%B3%AA%E5%95%8F%E3%81%AE%E4%BD%9C%E6%88%90">1. 質問の作成</a></h2> <p>質問したいメンバーやチャンネルにて以下のように/コマンドを記述したメッセージを送信するだけ</p> <p>コマンド記述例)</p> <pre><code>/poll "今日のランチ何を食べた?" "カレー" "ナポリタン" "ラーメン" "その他" </code></pre> <p>実際の画面だとこのような感じ↓<br /> <a href="https://crieit.now.sh/upload_images/57b4f3ba168aa2edfc67ec753d3c1f465ff18a5d8c39c.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/57b4f3ba168aa2edfc67ec753d3c1f465ff18a5d8c39c.png?mw=700" alt="d79b53a2c044ebe472efa6337e428f86.png" /></a><br /> 送信ボタンを押すとすぐに投票画面が作成されます</p> <p>初めて使う場合は、以下のようにプライバシーポリシーの受け入れ確認画面が出るかもしれません。<br /> その時は、「Accept」で同意しないと投票画面は作成されません。<br /> <a href="https://crieit.now.sh/upload_images/afb140856b3856982159a6d61f7684295ff18a6de6958.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/afb140856b3856982159a6d61f7684295ff18a6de6958.png?mw=700" alt="3ac1b40e80d295d6cb3ff66e94f2443a.png" /></a></p> <h2 id="2. 投票の仕方"><a href="#2.+%E6%8A%95%E7%A5%A8%E3%81%AE%E4%BB%95%E6%96%B9">2. 投票の仕方</a></h2> <p>選択肢の横にある数字をクリックするだけ<br /> なお、選択肢を修正したい場合は、もう一度数字をクリックすると取り消されます</p> <p><a href="https://crieit.now.sh/upload_images/099230ac4ac389ff3203818f2ee128885ff18a80bafde.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/099230ac4ac389ff3203818f2ee128885ff18a80bafde.png?mw=700" alt="f5ce9420c99c0f8a72fb8fa70da2e5f5.png" /></a></p> <p>クリックするとリアルタイムに集計されます</p> <p><a href="https://crieit.now.sh/upload_images/90bba1b6ca6c1359ad396b91e3d3ea795ff18a8e1b8a1.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/90bba1b6ca6c1359ad396b91e3d3ea795ff18a8e1b8a1.png?mw=700" alt="058c152edb64f9c4aff659ae0d89d414.png" /></a></p> arohajiro tag:crieit.net,2005:PublicArticle/15830 2020-04-15T12:01:05+09:00 2020-04-15T12:01:05+09:00 https://crieit.net/posts/nippo-command 浜辺美波が日報を投稿してくれるSlackコマンドをつくりました <p>私は日報を忘れてしまう人間です。<br /> もう少し正しく言うとめんどくさく思ってしまう人間です。</p> <p>今日閉じたIssueと取り組んでいるIssueを全部コピペしてきたりとか。</p> <p>今働いているチームのSlackには #nippo チャンネルがあり、メンバーはみんなここにその日やったこととかを投稿しています。</p> <p>今回つくったのはそんな毎日の日報をもう少し楽ちんにして、本質である"振り返る"という行為だけに留めたいと言うものです。<br /> 言い換えると何をやったかは自動で出力できるようにしたい。</p> <p>その名も「浜辺美波」です。</p> <p><code>/nippo 今日は足が冷えて冷えて大変でした。</code></p> <p>とうつと#nippoにこのように投稿してくれます。</p> <p><img src="https://imjn.me/images/nippo-command.png" alt="screenshot" /></p> <p>仕組みとしてはSpreadSheetにそれぞれのメンバーのSlackのユーザーIDとGitHubIDを保存してあり、GASでSlack commandから送られてくるユーザー情報と照会してGitHubから関連のIssueを取得してきています。<br /> GitHub GraphQL API初めて使いましたが便利すぎてびっくりしました。<a target="_blank" rel="nofollow noopener" href="https://developer.github.com/v4/explorer/">Explorer</a>が本当に便利。</p> <h2 id="メリット"><a href="#%E3%83%A1%E3%83%AA%E3%83%83%E3%83%88">メリット</a></h2> <h3 id="日報を書くのが楽ちんで少し楽しい"><a href="#%E6%97%A5%E5%A0%B1%E3%82%92%E6%9B%B8%E3%81%8F%E3%81%AE%E3%81%8C%E6%A5%BD%E3%81%A1%E3%82%93%E3%81%A7%E5%B0%91%E3%81%97%E6%A5%BD%E3%81%97%E3%81%84">日報を書くのが楽ちんで少し楽しい</a></h3> <p>明らかに日報にさく時間が減りました。振り返りコメントに全神経を集中させることができるので最高に面白いコメントができるようになりそうです。</p> <h3 id="Issueを書く文化"><a href="#Issue%E3%82%92%E6%9B%B8%E3%81%8F%E6%96%87%E5%8C%96">Issueを書く文化</a></h3> <p>私以外のエンジニアメンバーは今の会社が初めてのエンジニアとしての勤務先でなかなかチーム開発に不慣れな人が多いです。その中でひとつ大変なのがIssueをきちんと書いてもらうこと。更新してもらうこと。<br /> 今回の<code>/nippo</code>コマンドによってIssueの役に立っている感がすごくあるので多少Issueをきちんと更新する文化に貢献してくれるのではないかと期待しています。</p> <h2 id="コード Google App Script"><a href="#%E3%82%B3%E3%83%BC%E3%83%89+Google+App+Script">コード Google App Script</a></h2> <pre><code>function doPost(e) { var spreadsheet = SpreadsheetApp.openById('SpreadSheetのID'); var sheet = spreadsheet.getActiveSheet(); var username = e.parameter.user_name; var comment = comment = e.parameter.text; var row = findRowForSlackUsername(sheet,username); var githubId = sheet.getRange(row, 2).getValue(); var freeeId = sheet.getRange(row, 3).getValue(); if(comment.length === 0) { return ContentService.createTextOutput("⚠️`/nippo 振り返りコメント`の形式でコメントも書いて!"); } if(githubId.length === 0 || freeeId.length === 0) { return ContentService.createTextOutput("⚠️あなたのアカウント情報がまだちゃんと用意されてないみたい。超絶イケメンなimjnに聞いてみて!"); } var resForClosed = fetchIssues(githubId, "closed"); var resForWIP = fetchIssues(githubId, "open"); var closedIssues = JSON.parse(resForClosed.getContentText()).data.search.edges; var wipIssues = JSON.parse(resForWIP.getContentText()).data.search.edges; var issuesClosedToday = getIssuesClosedToday(closedIssues); var outputString = `今日もおつかれさまでした。 ${githubId}の日報です💙\n\n`; outputString += `💪 *今${githubId}が取り組んでいるIssue* ↓\n` if(wipIssues.length > 0) { var wipIssueString = ""; for(var i=0;i<wipIssues.length;i++){ var wipIssue = wipIssues[i]; wipIssueString += `${wipIssue.node.title} (${wipIssue.node.url})\n`; } outputString += "```" + wipIssueString + "```"; } else { outputString += `> ${githubId}がアサインされているタスクはありません。タスク待ち!`; } outputString += `\n\n🤗 *今日${githubId}がCloseしたIssue* ↓\n` if(issuesClosedToday.length > 0) { var issueString = ""; for(var i=0;i<issuesClosedToday.length;i++){ var theIssue = issuesClosedToday[i]; issueString += `${theIssue.node.title} (${theIssue.node.url})\n`; } outputString += "```" + issueString + "```"; } else { outputString += `> ${githubId}が今日CloseしたIssueはありません`; } outputString += `\n\n✍️ *振り返りコメント* ↓\n` outputString += '```' + comment + '```'; const webhookUrl = "Slackのwebhook URL"; const data = { 'attachments': [{ 'color': '#0086CC', 'text' : outputString, }] }; const payload = JSON.stringify(data); const options = { 'method' : 'POST', 'contentType' : 'application/json', 'payload' : payload }; UrlFetchApp.fetch(webhookUrl, options); return ContentService.createTextOutput("#nippoに投稿したよ!❤️"); } function findRowForSlackUsername(sheet,val){ var lastRow=sheet.getDataRange().getLastRow(); for(var i=1;i<=lastRow;i++){ if(sheet.getRange(i,1).getValue() === val){ return i; } } return 0; } function fetchIssues(githubId, status) { const query = 'query {\ search(last: 10, query: "org:オーガニゼーションID is:issue is:' + status + ' assignee:' + githubId + ' sort:updated-desc", type: ISSUE) {\ edges {\ node {\ ... on Issue {\ title\ url\ closedAt\ }\ }\ }\ }\ }'; const option = buildGraphqlRequest(query); return UrlFetchApp.fetch("https://api.github.com/graphql", option); } function buildGraphqlRequest(graphql) { return { method: "post", contentType: "application/json", headers: { Authorization: "bearer GitHubのアクセストークン", }, payload: JSON.stringify({ query: graphql }), }; } function getIssuesClosedToday(issues) { var now = new Date(); var closedIssues = []; for(var i=0;i<issues.length;i++){ var issue = issues[i]; var closedAt = new Date(issue.node.closedAt); if(closedAt.getFullYear() === now.getFullYear() && closedAt.getMonth() === now.getMonth() && closedAt.getDate() === now.getDate()) { closedIssues.push(issue); } } return closedIssues; } </code></pre> <p>せっかくfreeeも使ってるのでまた時間がある時に勤務時間とかも含められるようにしたいと思います。</p> <p>今月中に旅行領域で新規プロダクトを出します。こんなご時世ですが、がんばる。</p> imjn tag:crieit.net,2005:PublicArticle/15360 2019-08-28T18:18:13+09:00 2019-08-28T18:18:13+09:00 https://crieit.net/posts/CloudFunctions-SlackAPI CloudFunctions+SlackAPIを使って定期的にユーザ数を通知できるように自動化してみた <p>最近、<a target="_blank" rel="nofollow noopener" href="https://tsundoku.site">作ったWebサービス</a>で手動でユーザ数などのデータを取得していたけど、<br /> めんどくさくなったので、いまさらながらSlackAPIを使ってみた。</p> <h2 id="こんな感じで通知されます!"><a href="#%E3%81%93%E3%82%93%E3%81%AA%E6%84%9F%E3%81%98%E3%81%A7%E9%80%9A%E7%9F%A5%E3%81%95%E3%82%8C%E3%81%BE%E3%81%99%EF%BC%81">こんな感じで通知されます!</a></h2> <p>ユーザ数とか登録データ数とかを通知♪<br /> いまは毎日0:00に通知するようにしているので、毎日たのしみ(<em>´ω`</em>)</p> <p><img width="535" alt="スクリーンショット 2019-08-28 16.46.28.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/d0af6b44-cc9c-e3b7-afc8-6c3d616d3038.png"></p> <h2 id="全体の流れ"><a href="#%E5%85%A8%E4%BD%93%E3%81%AE%E6%B5%81%E3%82%8C">全体の流れ</a></h2> <p>全体の流れはこんな感じ</p> <ol> <li>Slack APIで<strong>Slack App</strong>を作成</li> <li>作成したAppで<strong>Incoming Webhooks</strong>を有効にする</li> <li>Incoming Webhooksに<strong>Webhook URL</strong>を追加する</li> <li>取得したWebhook URLを使ってFunctionsからメッセージを送る</li> </ol> <h3 id="1. Slack APIでSlack Appを作成"><a href="#1.+Slack+API%E3%81%A7Slack+App%E3%82%92%E4%BD%9C%E6%88%90">1. Slack APIでSlack Appを作成</a></h3> <p>まずは、「 https://api.slack.com/apps 」にアクセス</p> <p><img width="800px" alt="スクリーンショット_2019-08-28_15_42_59.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/5648c760-d1f2-c934-3c55-866e6fb1cb0f.png"></p> <p>「Create New App」をクリックすると、作成ダイアログが表示されるので、<br /> アプリ名を入力して、「Create App」をクリック</p> <p><img width="428" alt="スクリーンショット_2019-08-28_15_46_27.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/1b38367b-7a5c-4c22-7e35-0f94c643b8fa.png"></p> <p>これでSlack Appが作成できた(<em>´ω`</em>)</p> <h3 id="2. 作成したAppでIncoming Webhooksを有効にする"><a href="#2.+%E4%BD%9C%E6%88%90%E3%81%97%E3%81%9FApp%E3%81%A7Incoming+Webhooks%E3%82%92%E6%9C%89%E5%8A%B9%E3%81%AB%E3%81%99%E3%82%8B">2. 作成したAppでIncoming Webhooksを有効にする</a></h3> <p>Web APIでメッセージを投稿できるように設定するために、<br /> 作成したAppのSettingsのBasic Infomarionにある「Incoming Webhooks」を有効にする</p> <p><img width="718" alt="スクリーンショット_2019-08-28_15_46_58.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/2b926c23-de70-3d7c-0bbc-6577b288fd8a.png"></p> <p>クリックするとこんな感じ。デフォルトはOFFなので、ONに切り替える。</p> <p><img width="748" alt="スクリーンショット_2019-08-28_15_47_11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/36688dc6-3ca7-0c66-a839-9d482354c132.png"></p> <p>これで有効になった(<em>´ω`</em>)</p> <h3 id="3. Incoming WebhooksにWebhook URLを追加する"><a href="#3.+Incoming+Webhooks%E3%81%ABWebhook+URL%E3%82%92%E8%BF%BD%E5%8A%A0%E3%81%99%E3%82%8B">3. Incoming WebhooksにWebhook URLを追加する</a></h3> <p>有効にしたので、どのチャネルに通知するかを設定していく</p> <p>有効すると、下の方に「Add New Webhook to Workspace」があるので、それをクリック</p> <p><img width="714" alt="スクリーンショット_2019-08-28_15_47_38.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/b0288e59-47c9-9c06-1d18-8f729eefbf67.png"></p> <p>クリックすると、チャネルの選択画面が出るので、投稿先を設定して、「許可する」をクリック。<br /> 投稿先はチャネルだけじゃなく、個人宛も設定できます(<em>´ω`</em>)<br /> <img width="360" alt="スクリーンショット 2019-08-28 15.48.15.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/d2cb445a-b787-5ec6-3214-c9bceb731c4c.png"></p> <p>許可すると、こんな感じの画面に。<br /> WebhookURLが追加されるので、「Copy」を押して、コピーしておく<br /> <img width="712" alt="スクリーンショット_2019-08-28_15_48_39.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/f51efaa9-c8e5-1e24-69f4-a63f799796ee.png"></p> <h3 id="4. 取得したWebhook URLを使ってFunctionsからメッセージを送る"><a href="#4.+%E5%8F%96%E5%BE%97%E3%81%97%E3%81%9FWebhook+URL%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6Functions%E3%81%8B%E3%82%89%E3%83%A1%E3%83%83%E3%82%BB%E3%83%BC%E3%82%B8%E3%82%92%E9%80%81%E3%82%8B">4. 取得したWebhook URLを使ってFunctionsからメッセージを送る</a></h3> <p>取得したWebhookURL宛にメッセージを送ればOK</p> <h4 id="CloudFunctionsのコードはこんな感じ。"><a href="#CloudFunctions%E3%81%AE%E3%82%B3%E3%83%BC%E3%83%89%E3%81%AF%E3%81%93%E3%82%93%E3%81%AA%E6%84%9F%E3%81%98%E3%80%82">CloudFunctionsのコードはこんな感じ。</a></h4> <pre><code class="javascript">const functions = require("firebase-functions"); const admin = require("firebase-admin"); admin.initializeApp(); /** * Slackにメッセージ(text)を送る関数 */ const axios = require("axios"); async function sendSlack(text) { const webhookURL = "https://YOUR_WEBHOOK_URL"; const param = { text: text }; const headers = { "Content-Type": "application/x-www-form-urlencoded" }; try { await axios.post(webhookURL, param, { headers: headers }); } catch (error) { console.error(`Error occuered in sendSlack: ${error}`, error); } } /** * Slackへの通知を定期実行するFuncitons */ exports.slackNotify = functions.pubsub .schedule("0 0 * * *") .timeZone("Asia/Tokyo") .onRun(async context => { // Slack通知 await sendSlack("こんにちは"); }); </code></pre> <p><code>sendSlack(text)</code>の<code>webhookURL</code>に、<br /> 上でコピーしたWebhook URLをはりつけてください。<br /> こんな感じで「こんにちは」される(<em>´ω`</em>)</p> <p><img width="536" alt="スクリーンショット 2019-08-28 16.58.32.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/2efce0de-20f4-821b-526b-ee6375f938f1.png"></p> <p>あとは、Function内でFirestoreとかを呼び出して、データを収集・集計すればOKヽ(=´▽`=)ノ</p> <p>メッセージ送るだけならすごく簡単(<em>´ω`</em>)</p> <h3 id="ほかにもいろいろ"><a href="#%E3%81%BB%E3%81%8B%E3%81%AB%E3%82%82%E3%81%84%E3%82%8D%E3%81%84%E3%82%8D">ほかにもいろいろ</a></h3> <p>ほかにもパラメタを変えれば、色を変えたり、画像を送れたりできるよう!!<br /> - <a target="_blank" rel="nofollow noopener" href="https://qiita.com/ik-fib/items/b4a502d173a22b3947a0">SlackのIncoming Webhooksを使い倒す - Qiita</a></p> <p>公式ドキュメントだとこちら<br /> - <a target="_blank" rel="nofollow noopener" href="https://api.slack.com/incoming-webhooks">Incoming Webhooks | Slack</a></p> <p>また、こちらの記事でも書いたようにジョブの無料枠は、<strong>Googleアカウントごと</strong>...<br /> 遊びやテスト用にいくつも作ると課金されはじめるので注意が必要です...<br /> - <a target="_blank" rel="nofollow noopener" href="https://qiita.com/kira_puka/items/1f164dd8d1a5a281d9c1">Cloud Functions for Firebaseのcronみたいな定期実行を試したら簡単だった - Qiita</a></p> <p>以上!!</p> <h2 id="こんなのつくってます!!"><a href="#%E3%81%93%E3%82%93%E3%81%AA%E3%81%AE%E3%81%A4%E3%81%8F%E3%81%A3%E3%81%A6%E3%81%BE%E3%81%99%EF%BC%81%EF%BC%81">こんなのつくってます!!</a></h2> <p>最近、積読用の読書管理アプリ「積読ハウマッチ」をリリースしました!<br /> <a target="_blank" rel="nofollow noopener" href="https://tsundoku.site">積読ハウマッチ</a>は、Nuxt.js+Firebaseで開発してます!</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/572d4947-f40b-e4dc-1c9c-bc584cd2a66c.png" width="25%"/></p> <p>もしよかったら、遊んでみてくださいヽ(=´▽`=)ノ</p> <p>要望・感想・アドバイスなどあれば、<br /> 公式アカウント(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/MemoryLoverz">@MemoryLoverz</a>)や開発者(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/kira_puka">@kira_puka</a>)まで〜</p> きらぷか@積読ハウマッチ/SSSAPIなど tag:crieit.net,2005:PublicArticle/14891 2019-03-29T23:00:19+09:00 2019-03-29T23:00:19+09:00 https://crieit.net/posts/OGP-Twitter-LINE-8 OGP取得用クローラのユーザーエージェントを、TwitterやLINEなど8サービスで調べてみた <p>SNSなどで、投稿されたWebサイトのリンクがリッチに表示される「OGP (Open Graph Protocol)」機能。<br /> Webサイトに簡単なメタタグを組み込むだけでOGP表示されるため、対応するサイトは増えています。<br /> しかし、OGP用のメタタグは一般ユーザーからは見えないし、サムネイル動的生成などでサーバーに大きな負荷がかかることもあるので、できればOGP取得用クローラ以外にはメタタグを生成しないようにしたいもの。<br /> そこで、UA(ユーザーエージェント)からクローラによるアクセスか判定するために、実際にOGP取得用クローラのUAを記録して調べてみました。</p> <h4 id="結果"><a href="#%E7%B5%90%E6%9E%9C">結果</a></h4> <p>2019年3月28日に記録された文字列をそのまま掲載しています。</p> <div class="table-responsive"><table> <thead> <tr> <th>サービス</th> <th>User Agent</th> </tr> </thead> <tbody> <tr> <td>Twitter</td> <td>Twitterbot/1.0</td> </tr> <tr> <td>Facebook</td> <td>facebookexternalhit/1.1 (+http://www.facebook.com/externalhit_uatext.php)</td> </tr> <tr> <td>LINE</td> <td>facebookexternalhit/1.1;line-poker/1.0</td> </tr> <tr> <td>メッセージ(iOS)</td> <td>Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_1) AppleWebKit/601.2.4 (KHTML, like Gecko) Version/9.0.1 Safari/601.2.4 facebookexternalhit/1.1 Facebot Twitterbot/1.0</td> </tr> <tr> <td>Discord</td> <td>Mozilla/5.0 (compatible; Discordbot/2.0; +https://discordapp.com)</td> </tr> <tr> <td>Skype</td> <td>Mozilla/5.0 (Windows NT 6.1; WOW64) SkypeUriPreview Preview/0.5</td> </tr> <tr> <td>Slack</td> <td>Slackbot-LinkExpanding 1.0 (+https://api.slack.com/robots)</td> </tr> <tr> <td>Plurk</td> <td>Mozilla/5.0 (compatible; PlurkBot/1.0; +https://www.plurk.com/) Firefox/61.0</td> </tr> </tbody> </table></div> <p>もし他にもOGP表示に対応しているサービスをご存知でしたら、<a target="_blank" rel="nofollow noopener" href="https://twitter.com/barley_ural">@barley_ural</a>までご連絡いただければ調査して追記していきます。</p> <p>おわり</p> ウラル tag:crieit.net,2005:PublicArticle/14859 2019-03-06T00:48:44+09:00 2019-03-28T12:23:25+09:00 https://crieit.net/posts/Slack-GAS SlackのファイルをGASで自動削除する <p>Unityゲーム開発者ギルドというコミュニティを運営しているんだけど、<br /> 無料版で使っているSlackのファイルストレージがギリギリになってた。</p> <p><a href="https://crieit.now.sh/upload_images/9ba5a50ad15a3c66d2ef0e24bb8b8c485c7e990fd0111.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/9ba5a50ad15a3c66d2ef0e24bb8b8c485c7e990fd0111.png?mw=700" alt="スクリーンショット 2019-03-06 0.28.01.png" /></a></p> <blockquote> <p>Unityギルドについてはこちら -> https://twitter.com/naichilab/status/1061929632037892096</p> </blockquote> <p>このままじゃファイルアップロードできなくなっちゃうので、古いファイルは自動削除されるようにした。<br /> とりあえず30日以上経過したファイルは問答無用で消していこう。</p> <p>(Slackのメッセージも今のペースだと2週間たらずで消えちゃうから30日経過したら削除で十分だろうと判断)</p> <p>GASで下記スクリプトを作成。プロジェクトのプロパティでレガシートークンをセットして1時間トリガーを仕掛けた。</p> <blockquote> <p>1時間ごとに起動して、30日以上経過したファイルを10件検索&削除。</p> </blockquote> <p>削除後の反映は最大48時間かかるらしいので様子見〜。</p> <pre><code class="js">function deleteOldFiles() { var res = filesList(30, 10); if(res.files.length == 0){ Logger.log("削除対象ファイルは見つかりませんでした。"); return; } res.files.forEach(function(file){ Logger.log("削除します。 ID=" + file.id + ", Size=" + file.size); filesDelete(file.id) Utilities.sleep(50);//sleep 50msec }); } //ref. https://api.slack.com/methods/files.list function filesList(days, count){ var params = { token: PropertiesService.getScriptProperties().getProperty("SLACK_ACCESS_TOKEN"), ts_to: elapsedDaysToUnixTime(days), count: count } return execute('files.list', params); } //ref. https://api.slack.com/methods/files.delete function filesDelete(id){ var params = { token: PropertiesService.getScriptProperties().getProperty("SLACK_ACCESS_TOKEN"), file: id } return execute('files.delete', params); } function execute(apiName, params){ var options = { 'method': 'POST', 'payload': params } var res = UrlFetchApp.fetch('https://slack.com/api/' + apiName,options); return JSON.parse(res.getContentText()); } function elapsedDaysToUnixTime(days){ var date = new Date(); var now = Math.floor(date.getTime()/ 1000); // unixtime[sec] return now - 8.64e4 * days + '' // 8.64e4[sec] = 1[day] 文字列じゃないと動かないので型変換している } </code></pre> naichi tag:crieit.net,2005:PublicArticle/14708 2019-01-04T00:01:00+09:00 2019-01-13T12:18:34+09:00 https://crieit.net/posts/slack slack流量計の新バージョンをリリースしました(1/13追記) <p><a href="https://crieit.now.sh/upload_images/08434b799d790cbc047b581b1a07cf185c2e11b63d2d0.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/08434b799d790cbc047b581b1a07cf185c2e11b63d2d0.png?mw=700" alt="image" /></a><br /> 「<a target="_blank" rel="nofollow noopener" href="https://scrapbox.io/admin-guild-pr/運営者ギルド">運営者ギルド</a>」で使用している、どのチャンネルで活発に議論されているかを可視化するツール「slack流量計」を年末にバージョンアップした話を書こうかと思います。</p> <h1 id="運営者ギルドって?"><a href="#%E9%81%8B%E5%96%B6%E8%80%85%E3%82%AE%E3%83%AB%E3%83%89%E3%81%A3%E3%81%A6%EF%BC%9F">運営者ギルドって?</a></h1> <p>Webサービスの運営に必要なあらゆる知見を共有できる場として、Webサービス運営者達によって作られたコミュニティです。主にslackを用いて情報交換をしています。</p> <h1 id="slack流量計とは?"><a href="#slack%E6%B5%81%E9%87%8F%E8%A8%88%E3%81%A8%E3%81%AF%EF%BC%9F">slack流量計とは?</a></h1> <p>slack APIから書き込みの履歴を取得して日別・チャンネル別に書き込みの数を集計してslackにグラフとテキストで投稿するアプリケーションです。<br /> (ギルドでは呼称が変わりました。)</p> <h2 id="旧バージョンで使っていた技術"><a href="#%E6%97%A7%E3%83%90%E3%83%BC%E3%82%B8%E3%83%A7%E3%83%B3%E3%81%A7%E4%BD%BF%E3%81%A3%E3%81%A6%E3%81%84%E3%81%9F%E6%8A%80%E8%A1%93">旧バージョンで使っていた技術</a></h2> <p>旧バージョンではSpringMVCのタスクスケジューラを使用してバッチでサーバサイドでグラフを生成していました。<br /> - Java<br /> - Spring MVCのタスクスケジューラ<br /> - <a target="_blank" rel="nofollow noopener" href="http://www.jfree.org/jfreechart/">JFreeChart(グラフ生成ライブラリ)</a><br /> - MySQL</p> <h2 id="新バージョンで使っている技術"><a href="#%E6%96%B0%E3%83%90%E3%83%BC%E3%82%B8%E3%83%A7%E3%83%B3%E3%81%A7%E4%BD%BF%E3%81%A3%E3%81%A6%E3%81%84%E3%82%8B%E6%8A%80%E8%A1%93">新バージョンで使っている技術</a></h2> <p>毎日定時に実行するのにnode-cronを使っています。今回はExpressでAPIを用意してクライアントサイド(chart.js)でグラフを生成し、puppeteerを使ってスクリーンショットを撮って画像を投稿するという形をとっています。<br /> - NodeJS<br /> - Express<br /> - TypeScript<br /> - <a target="_blank" rel="nofollow noopener" href="https://www.npmjs.com/package/node-cron">node-cron</a><br /> - chart.js<br /> - puppeteer<br /> - <a target="_blank" rel="nofollow noopener" href="https://www.npmjs.com/package/@slack/client">slack/client</a></p> <h1 id="バージョンアップ"><a href="#%E3%83%90%E3%83%BC%E3%82%B8%E3%83%A7%E3%83%B3%E3%82%A2%E3%83%83%E3%83%97">バージョンアップ</a></h1> <h2 id="バージョンアップ作業"><a href="#%E3%83%90%E3%83%BC%E3%82%B8%E3%83%A7%E3%83%B3%E3%82%A2%E3%83%83%E3%83%97%E4%BD%9C%E6%A5%AD">バージョンアップ作業</a></h2> <ul> <li>製造期間:12/28~30</li> <li>運用・改修:12/31~<br /> Javaからの移行なのでロジックは変えていませんが、ロジック以外はゼロから作っています。</li> </ul> <h2 id="バージョンアップした理由"><a href="#%E3%83%90%E3%83%BC%E3%82%B8%E3%83%A7%E3%83%B3%E3%82%A2%E3%83%83%E3%83%97%E3%81%97%E3%81%9F%E7%90%86%E7%94%B1">バージョンアップした理由</a></h2> <ul> <li>いくつかデプロイしているJavaアプリケーションからNodeJSアプリケーションに移行を終えたかった</li> <li>slackに投稿する処理で文字数制限を超えて失敗するケースがあるので改修したかった</li> </ul> <h2 id="苦労した点"><a href="#%E8%8B%A6%E5%8A%B4%E3%81%97%E3%81%9F%E7%82%B9">苦労した点</a></h2> <ul> <li>chart.jsの<a target="_blank" rel="nofollow noopener" href="https://nagix.github.io/chartjs-plugin-colorschemes/colorchart.html">プラグイン</a>でカラーセットを使っているが、実際に生成すると見辛い色が存在した。</li> <li>puppeteerの動作で描画待ちをしていなかったため、グラフ描画前にスクリーンショットを撮ってしまう現象が発生。</li> </ul> <h1 id="自動表彰機能追加しました(1/5追記)"><a href="#%E8%87%AA%E5%8B%95%E8%A1%A8%E5%BD%B0%E6%A9%9F%E8%83%BD%E8%BF%BD%E5%8A%A0%E3%81%97%E3%81%BE%E3%81%97%E3%81%9F%281%2F5%E8%BF%BD%E8%A8%98%29">自動表彰機能追加しました(1/5追記)</a></h1> <p><a href="https://crieit.net/users/ampersand_xyz">あんど</a>さんの「<a target="_blank" rel="nofollow noopener" href="https://hyou.show/">WEB表彰</a>」をお借りして自動で週1回表彰する機能を組み込んでみました。<br /> <a href="https://crieit.now.sh/upload_images/7a7957261387b24d52e646e155b1c2b15c30957b80170.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/7a7957261387b24d52e646e155b1c2b15c30957b80170.png?mw=700" alt="image" /></a></p> <ul> <li>puppeteerのpage.type()でフォーム入力できるから簡単♪<br /> →入力直後にフォーカスを奪われて入力できなくなる<br /> →page.evaluate()でDOM要素に直接書き込み。</li> <li>環境によるが、DOM書き換えだけでは描画が走らないことが判明<br /> →要素にフォーカスしてからkeyupイベントを送ることで対処</li> </ul> <pre><code class="typescript">await page.evaluate( (str)=>{ document.querySelector('input#receiver.input').setAttribute('value',str[0]) document.querySelector('#certificate_body.input').textContent = str[1] document.querySelector('input#sender.input').setAttribute('value','運営者ギルド勢いランキング') },str) await page.focus('#certificate_body.input') await page.keyboard.up('Shift') </code></pre> <ul> <li>1行22文字しか入力できないのでsubstring()</li> </ul> <h1 id="パンチカード実装しました(1/13追記)"><a href="#%E3%83%91%E3%83%B3%E3%83%81%E3%82%AB%E3%83%BC%E3%83%89%E5%AE%9F%E8%A3%85%E3%81%97%E3%81%BE%E3%81%97%E3%81%9F%281%2F13%E8%BF%BD%E8%A8%98%29">パンチカード実装しました(1/13追記)</a></h1> <p><a href="https://crieit.now.sh/upload_images/d60015b542b6c2ad882b6bafad229c155c3aacd0ee038.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/d60015b542b6c2ad882b6bafad229c155c3aacd0ee038.png?mw=700" alt="image" /></a><br /> 折れ線グラフと同じようにAPIを用意してchart.jsで描画しています。</p> <h1 id="今後の展望"><a href="#%E4%BB%8A%E5%BE%8C%E3%81%AE%E5%B1%95%E6%9C%9B">今後の展望</a></h1> <p><del>週1回、チャンネルごとではなくユーザごとの書き込み数で「表彰」したら面白そう?</del>(実装済み)<br /> <del>- GitHubのコミット履歴に昔あったパンチカードのグラフを実装したい</del>(実装済み)</p> <h1 id="ソース"><a href="#%E3%82%BD%E3%83%BC%E3%82%B9">ソース</a></h1> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/ckoshien/slack_stream_node">GitHub</a></p> ckoshien tag:crieit.net,2005:PublicArticle/14657 2018-12-14T02:28:39+09:00 2018-12-14T02:51:07+09:00 https://crieit.net/posts/100-Slack 100個入ってわかった! 人が集まるSlackコミュニティ運営術 <p>この記事はCrieitの<a href="https://crieit.net/advent-calendars/2018/crieit">「アドベントカレンダー Advent Calendar 2018」</a>の14日目の記事です。<br /> 昨日は<a href="https://crieit.net/users/manzyun">まんじゅ(´ん`)@CoderDojo駄Mentor</a>さんの<a href="https://crieit.net/posts/b2fa8f9298b905174336d05675775ed9">論理を煮詰めた正論は常に正しいだろうけど、時によっては鋭利なナイフにもちろんなるぞ</a>でした!</p> <h1 id="はじめに"><a href="#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB">はじめに</a></h1> <p>駆け出しエンジニアのかしい(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/rubys8arks">@rubys8arks</a>)です。<br /> 個人開発を始めた頃、情報をもとめて地域のもくもく会やネット上のオープンコミュニティなどのSlackに入っていたら、気づいたら100個ものSlackに参加していました…。<br /> いろんなSlackに参加する中で見えた、「こんなSlackは参加したい」という盛り上がっているコミュニティの特徴をまとめました。</p> <h1 id="1. Slack参加のハードルを低めにする"><a href="#1.+Slack%E5%8F%82%E5%8A%A0%E3%81%AE%E3%83%8F%E3%83%BC%E3%83%89%E3%83%AB%E3%82%92%E4%BD%8E%E3%82%81%E3%81%AB%E3%81%99%E3%82%8B">1. Slack参加のハードルを低めにする</a></h1> <p>だれでもつよいエンジニアとつながりたいですが、つよいエンジニアはそうはいません。<br /> 「rubyに詳しい人」「毎日ブログを更新できる人」「バズった経験がある人」など、母数が少ない人をターゲットにSlack参加者を募集すると集まりにくくなります。<br /> 「個人開発をはじめたい人」「プログラミングを勉強しはじめた人」など、初心者歓迎にした方がコミュニティの参加人数は多くなります。<br /> 基本的にはコミュニティ全体の参加人数が多い方が、コミュニティで積極的に発言してくれるメンバーも多くなります。<br /> ※ 有料サロンなど、全メンバーのコミットメントを高く管理したい場合は別</p> <h1 id="2. 分報はつくった方が定着率高め"><a href="#2.+%E5%88%86%E5%A0%B1%E3%81%AF%E3%81%A4%E3%81%8F%E3%81%A3%E3%81%9F%E6%96%B9%E3%81%8C%E5%AE%9A%E7%9D%80%E7%8E%87%E9%AB%98%E3%82%81">2. 分報はつくった方が定着率高め</a></h1> <p>分報とは、「自分が今かんがえてること」ならなんでもOKな個人チャンネルです。<br /> <a target="_blank" rel="nofollow noopener" href="http://c16e.com/1511101558/">Slackで簡単に「日報」ならぬ「分報」をチームで実現する3ステップ〜Problemが10分で解決するチャットを作ろう</a><br /> 「途中からつくる」ということはあまりないと思うので、最初に「分報を作ってください」というルールにした方がよいです。<br /> 分報チャンネルへの加入は任意なことが多いですが、全メンバーの割合に対して見る人が少なくなるので反応が少なくなる懸念もあります。<br /> 「反応ない」→「書かない」→分報つくった意味ない<br /> になるので、自己紹介に分報を貼ってもらうなど、新規チャンネルができた場合の導線があるとよいです。<br /> 反対に、コミュニティが盛況でメンバーが増えてくると、メンバー数の分だけ分報が作られるので全部追うのが難しくなります。<br /> その場合は、人気ランキング(絵文字のリアクション数などで判断)などをつくって、見るべき分報をある程度運営側でピックアップしてあげると、ライトなメンバーにもやさしいつくりとなります。</p> <h1 id="3. カスタム絵文字は随時追加して充実させる"><a href="#3.+%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%A0%E7%B5%B5%E6%96%87%E5%AD%97%E3%81%AF%E9%9A%8F%E6%99%82%E8%BF%BD%E5%8A%A0%E3%81%97%E3%81%A6%E5%85%85%E5%AE%9F%E3%81%95%E3%81%9B%E3%82%8B">3. カスタム絵文字は随時追加して充実させる</a></h1> <p>コメントするのはめんどくさくても、誰かが押したリアクションに追従するのはカンタンです。<br /> コミュニティにおいて「反応がない」→「人が来なくなる」なので、旬のアイコンやネットスラングの日本語リアクションを用意して、みんなが反応しやすくするのは大事です。<br /> <a href="https://crieit.now.sh/upload_images/af4f47081a0dbf600db7bdaa7770def95c06b6c905f6d.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/af4f47081a0dbf600db7bdaa7770def95c06b6c905f6d.png?mw=700" alt="スクリーンショット 2018-12-05 1.37.55.png" /></a></p> <h1 id="4. Slackボットを活用してコミュニケーションを盛り上げる"><a href="#4.++Slack%E3%83%9C%E3%83%83%E3%83%88%E3%82%92%E6%B4%BB%E7%94%A8%E3%81%97%E3%81%A6%E3%82%B3%E3%83%9F%E3%83%A5%E3%83%8B%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%82%92%E7%9B%9B%E3%82%8A%E4%B8%8A%E3%81%92%E3%82%8B">4. Slackボットを活用してコミュニケーションを盛り上げる</a></h1> <ul> <li>特定のワードがコメントされたら特定のリアクションをする (例:誰かが「おめでとう!」と言ったらくす玉のGIFが流れる)</li> <li>特定の絵文字がリアクションされたら特定のチャンネルに流す (例:誰かが「有益」絵文字をつけたら「有益」チャンネルに流す)</li> <li>新メンバー、新チャンネルができたら特定のチャンネルに流す (例:新メンバー、新チャンネルお知らせ用のチャンネルをつくっておく)</li> <li>特定のサイトからRSSでチャンネルにニュースを流す(ただこれは多すぎると結局読まなくなるので意味なし。記事を選別しつつ、手動でコメントしてあげると興味を惹かれる。)<br /> など、作業をSlackボットで自動化しておくとコミュニティ運営がラクになります。<br /> 自分ではSlack botを作れていないので、下記のサイトなどを参考にするとよいかと思います。<br /> <a target="_blank" rel="nofollow noopener" href="https://qiita.com/namutaka/items/233a83100c94af033575" target="_blank" rel="noopener">Slack Botの種類と大まかな作り方 #Slack - Qiita</a></li> </ul> <h1 id="5. Slackの創始者が積極的に発言する"><a href="#5.+Slack%E3%81%AE%E5%89%B5%E5%A7%8B%E8%80%85%E3%81%8C%E7%A9%8D%E6%A5%B5%E7%9A%84%E3%81%AB%E7%99%BA%E8%A8%80%E3%81%99%E3%82%8B">5. Slackの創始者が積極的に発言する</a></h1> <p>Slackワークスペースの作成者もしくは運営コアメンバーが顔を出してくれないと、「もうこのSlackワークスペースは動いてないのかな」「やる気なくなっちゃったのかな」という感じがします。<br /> 忙しい場合は一言「しばらく離れます」と言ったり、場合によっては他の人に管理を譲る、コミュニティを閉じるなど責任のある対応が必要です。</p> <h1 id="6. リアルイベントを開催する"><a href="#6.+%E3%83%AA%E3%82%A2%E3%83%AB%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88%E3%82%92%E9%96%8B%E5%82%AC%E3%81%99%E3%82%8B">6. リアルイベントを開催する</a></h1> <p>実際にメンバーと会って話すことができると、やはり格段に親近感が湧いてオンラインでも交流しやすくなります。<br /> イベントはconnpassに登録するようなテーマを決めた勉強会でもよいし、単純にメンバー同士の交流会やもくもく会でもよいと思います。<br /> ただ、「運営が大変・・・」「だんだん人が集まらなくて開催されなくなる…」というのもあるあるです。<br /> 開催頻度や開催日程を固定して参加しやすくしたり、テーマを設定してメンバーやコミュニティ外の人の興味を惹けるようなイベントを作っていくのが大切かと思います。</p> <h1 id="7. 内輪な雑談しかないのはツライ、励ましあいつつ知見を提供しよう"><a href="#7.+%E5%86%85%E8%BC%AA%E3%81%AA%E9%9B%91%E8%AB%87%E3%81%97%E3%81%8B%E3%81%AA%E3%81%84%E3%81%AE%E3%81%AF%E3%83%84%E3%83%A9%E3%82%A4%E3%80%81%E5%8A%B1%E3%81%BE%E3%81%97%E3%81%82%E3%81%84%E3%81%A4%E3%81%A4%E7%9F%A5%E8%A6%8B%E3%82%92%E6%8F%90%E4%BE%9B%E3%81%97%E3%82%88%E3%81%86">7. 内輪な雑談しかないのはツライ、励ましあいつつ知見を提供しよう</a></h1> <p>雑談チャンネルで盛り上がるのもコミュニティでは大切です。<br /> ただ、「○○さんが××らしい」といった内輪ネタや、アニメ・ゲームなどの「前提知識がないとわからないネタ」が多いと新規参加者には入りづらいです。<br /> 基本的には「有益な情報(ブログ記事だったりニュースだったり)」や「参加者の悩みや喜びに対する共感・励まし」が大事だと思います。<br /> <a href="https://crieit.now.sh/upload_images/f00e454a6902b006d11d98d065c49d715c06b6e493783.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/f00e454a6902b006d11d98d065c49d715c06b6e493783.png?mw=700" alt="sns_nakamahazure_woman.png" /></a></p> <h1 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h1> <p>キリストは「与えよ、さらば与えられん」と言いましたが、コミュニティ運営においても似たようなことが言えます。<br /> 自分がやってほしいこと(積極的な発言や反応、アドバイスなど)をまずメンバーに自分がやってあげると、「私もやってみようかな」という気にメンバーがなりやすいです。<br /> あなたのSlackコミュニティが活発になることを願っております!</p> <p>明日は<a href="https://crieit.net/users/yahsan2">yahsan2</a>さんが最近リリースしたサービス<a target="_blank" rel="nofollow noopener" href="https://poiit.me/">poiit(ポイート)</a>について書いてくれます!</p> かしい@お笑いSNS作成中 tag:crieit.net,2005:PublicArticle/14650 2018-12-12T17:46:44+09:00 2018-12-13T10:51:42+09:00 https://crieit.net/posts/15-Slack 15分でできる!Slackのオープンコミュニティの作り方 <p>この記事はCrieitの<a href="https://crieit.net/advent-calendars/2018/crieit">「アドベントカレンダー Advent Calendar 2018」</a>の12日目の記事です。<br /> 昨日は<a href="https://crieit.net/users/fk2000">fk2000</a>さんの<a href="https://crieit.net/posts/Hugo">Hugoでブログ付きのポートフォリオサイト</a>でした!</p> <h1 id="はじめに"><a href="#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB">はじめに</a></h1> <p>駆け出しエンジニアのかしい(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/rubys8arks">@rubys8arks</a>)です。<br /> 技術書典でアンソロジー(合同誌)を出す関係で、Slackのコミュニティを新しく作成しました!<br /> そのときに気づいたSlackのワークスペース作成の手順とTipsをまとめます。</p> <h1 id="1. ワークスペースの作成"><a href="#1.+%E3%83%AF%E3%83%BC%E3%82%AF%E3%82%B9%E3%83%9A%E3%83%BC%E3%82%B9%E3%81%AE%E4%BD%9C%E6%88%90">1. ワークスペースの作成</a></h1> <p>とにもかくにも<a target="_blank" rel="nofollow noopener" href="https://get.slack.help/hc/ja/articles/217626298-Slack-のはじめ方-ワークスペース作成者編">Slack公式</a>から新しいワークスペースを作成します。<br /> メールアドレスを入力すると確認コードが届くので、入力して作成を完了します。<br /> ワークスペース名・ワークスペースURLは後から変えられますが、わかりやすいものがよいです。<br /> <a href="https://crieit.now.sh/upload_images/9220040c10b757f955e15a049d46a4e95c10cc6b5f83b.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/9220040c10b757f955e15a049d46a4e95c10cc6b5f83b.png?mw=700" alt="スクリーンショット 2018-12-12 17.47.18.png" /></a></p> <h1 id="2. チャンネルの作成"><a href="#2.+%E3%83%81%E3%83%A3%E3%83%B3%E3%83%8D%E3%83%AB%E3%81%AE%E4%BD%9C%E6%88%90">2. チャンネルの作成</a></h1> <p>デフォルトではgeneral(一般)とrandom(雑談)チャンネルが作成されます。<br /> Gmailで作成する場合、ワークスペースの設定言語を日本語/英語にしておかないとデフォルトのチャンネル名や説明文が英語以外で作成されてしまうので注意。<br /> (私はGmailをフランス語設定にしていたので、チャンネルがフランス語で作成されてしまい焦りました)<br /> <a href="https://crieit.now.sh/upload_images/2f6cc6d15066b03acd15ae921997bb815c06b752099f9.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/2f6cc6d15066b03acd15ae921997bb815c06b752099f9.png?mw=700" alt="スクリーンショット 2018-12-05 1.35.12.png" /></a></p> <p>よくあるチャンネル<br /> - ランディングページ(readme):Slackの目的、コミュニティのガイドラインなどを説明<br /> - 自己紹介(member_profile):初めて来た人がまず挨拶する場所</p> <p>立ち上げの段階でチャンネルがありすぎても使い分けが難しいので、テーマに応じて3〜5つ程度あれば十分かと。<br /> チャンネルごとのトピックも念のため設定しておくとわかりやすいです。</p> <h1 id="3. デフォルトチャンネルの設定"><a href="#3.+%E3%83%87%E3%83%95%E3%82%A9%E3%83%AB%E3%83%88%E3%83%81%E3%83%A3%E3%83%B3%E3%83%8D%E3%83%AB%E3%81%AE%E8%A8%AD%E5%AE%9A">3. デフォルトチャンネルの設定</a></h1> <p>Slackに入ると同時にメンバーが自動的に入室できるチャンネルを設定しておきます。<br /> 後からチャンネルに個別に入室するのはめんどう&入り忘れる人が必ず出るので、メンバーをInviteする前にやっておくとよいです。<br /> 基本的には分報(メンバーの個人チャンネル)以外のメインのチャンネルを設定しておけばOKかと。<br /> モバイルアプリからだと設定できない?ので、デスクトップから設定します。<br /> もしくは、https://ワークスペース名.slack.com/admin/settings から設定画面にアクセスできます。<br /> <a href="https://crieit.now.sh/upload_images/74970601501f309ce92d4f6b126b75e15c06b7697064d.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/74970601501f309ce92d4f6b126b75e15c06b7697064d.png?mw=700" alt="スクリーンショット 2018-12-05 1.35.29.png" /></a></p> <h1 id="4. ワークスペースのアイコンの設定"><a href="#4.+%E3%83%AF%E3%83%BC%E3%82%AF%E3%82%B9%E3%83%9A%E3%83%BC%E3%82%B9%E3%81%AE%E3%82%A2%E3%82%A4%E3%82%B3%E3%83%B3%E3%81%AE%E8%A8%AD%E5%AE%9A">4. ワークスペースのアイコンの設定</a></h1> <p>設定しなくてもワークスペースのイニシャルが自動で設定されますが、ある方が愛着が湧きます。<br /> デザインに凝る必要はないので、フリーアイコンなどを使用すればOK。<br /> こちらもモバイルアプリからだと設定できない?ので、デスクトップから設定します。<br /> もしくは、https://ワークスペース名.slack.com/admin/settings から設定画面にアクセスできます。<br /> <a href="https://crieit.now.sh/upload_images/d8fcae68f4627ec8b82189dd9309adad5c06b779c4827.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/d8fcae68f4627ec8b82189dd9309adad5c06b779c4827.png?mw=700" alt="スクリーンショット 2018-12-05 1.37.23.png" /></a></p> <h1 id="5. カスタム絵文字(日本語リアクション)の設定"><a href="#5.+%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%A0%E7%B5%B5%E6%96%87%E5%AD%97%28%E6%97%A5%E6%9C%AC%E8%AA%9E%E3%83%AA%E3%82%A2%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3%29%E3%81%AE%E8%A8%AD%E5%AE%9A">5. カスタム絵文字(日本語リアクション)の設定</a></h1> <p>「すごい!」「ありがと」など、日本語リアクションのカスタム絵文字はSlack上のコミュニケーションを円滑にしてくれます。<br /> 1個1個設定するのは大変なので、既存のSlackグループから一括でコピーさせてもらうのがオススメ。<br /> 基本的にはこの記事のとおりにやれば、カスタム絵文字のExport/Importができます。<br /> <a target="_blank" rel="nofollow noopener" href="https://qiita.com/ne-peer/items/cbdef4f02b1bb6103e51">Slackの絵文字(emoji)を一括エクスポート&インポートする</a><br /> 記事内でオススメされているChrome拡張機能ですが、カスタム絵文字が0個の状態だと表示されなかったので、テストで1個絵文字をアップロードするとよいです。<br /> <a href="https://crieit.now.sh/upload_images/af4f47081a0dbf600db7bdaa7770def95c06b798ef259.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/af4f47081a0dbf600db7bdaa7770def95c06b798ef259.png?mw=700" alt="スクリーンショット 2018-12-05 1.37.55.png" /></a></p> <h1 id="6. メンバーをInviteする"><a href="#6.+%E3%83%A1%E3%83%B3%E3%83%90%E3%83%BC%E3%82%92Invite%E3%81%99%E3%82%8B">6. メンバーをInviteする</a></h1> <p>下準備完了!ということで、いよいよメンバーをInviteしてSlackコミュニティを立ち上げます。<br /> 自己紹介してもらったら、カスタム絵文字だけでなくコメントもしてあげるとウェルカム感が強くなります。</p> <h1 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h1> <p>Slackコミュニティの作成って、「Slackのワークスペースつくるだけでしょ?」と思いきや、いろいろ設定することがあります。<br /> 初めてSlackのワークスペースを作る場合は設定がこんがらがるので、余裕をもって作業した方がよいです。<br /> あなたのSlackオープンコミュニティが活発になることを願っています!</p> <p>明日は<a href="https://crieit.net/users/manzyun">まんじゅ(´ん`)@CoderDojo駄Mentor</a>の記事です!</p> かしい@お笑いSNS作成中 tag:crieit.net,2005:PublicArticle/14581 2018-10-24T17:18:45+09:00 2018-11-01T16:36:22+09:00 https://crieit.net/posts/Slack-app 初めてSlack appをつくって審査通すところまでやった知見を晒す <p>たまたま機会あってSlack appを作らせていただくことになったのですが、<br /> 思ってた以上に大変だったのでそこで得られた知見をいくつか簡単にシェアしておきます。</p> <p>ちなみに自分は<a target="_blank" rel="nofollow noopener" href="https://github.com/hubotio/hubot">hubot</a>とか<a target="_blank" rel="nofollow noopener" href="https://github.com/howdyai/botkit">botkit</a>とかで簡単なbot作ったことあるレベルで、<br /> とりあえずtoken発行して環境変数突っ込んでスクリプト動かしたらそれっぽいやつできるんでしょ?、くらいのノリからのスタートだったので正直いろいろつっこみどころありそうではあるのですが、何かあれば是非インプットいただければと思います :pray:</p> <h2 id="今回作ったもの"><a href="#%E4%BB%8A%E5%9B%9E%E4%BD%9C%E3%81%A3%E3%81%9F%E3%82%82%E3%81%AE">今回作ったもの</a></h2> <p><a href="https://crieit.now.sh/upload_images/5496cfe37fb88e924517bea41c4f73eb5bd02910e5154.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/5496cfe37fb88e924517bea41c4f73eb5bd02910e5154.jpg?mw=700" alt="slackapp.jpg" /></a><br /> <a target="_blank" rel="nofollow noopener" href="https://homerukun.atengagement.com/">ホメルくん</a></p> <p>詳細なコンセプトや利用方法は<a target="_blank" rel="nofollow noopener" href="https://atengagement.com/editors/homerukun/">こちらの記事</a>に移譲しますが、<br /> ざっくり三行で説明すると、</p> <ul> <li>Slackのカスタム絵文字って企業毎の文化を反映したものになってる</li> <li>上記のような絵文字がたくさんついたメッセージや発言者を組織内でもっとオープンにすることで、称賛された人はモチベーション上がるし、組織づくりにもよい影響与えられそう</li> <li>というわけで、特定の絵文字がついたメッセージを特定のチャンネルに転送してくれて、毎月絵文字ごとの発言者のTop3を表彰してくれるSlackアプリがこちら</li> </ul> <p>という感じになります。</p> <p>機能的にもかなりシンプルで、</p> <ul> <li>絵文字、チャンネルの登録</li> <li>指定絵文字がリアクションされた場合、指定チャンネルへのメッセージの転送</li> <li>毎月リアクション絵文字獲得数Top3を集計しメッセージを送る</li> </ul> <p>くらいで、まぁ正直こんなん半日くらいでさくっと終わるだろ、とそんなふうに考えていた時期が私にもありました :innocent:</p> <h2 id="実装編"><a href="#%E5%AE%9F%E8%A3%85%E7%B7%A8">実装編</a></h2> <h3 id="Slack appの作り方 (ざっくり)"><a href="#Slack+app%E3%81%AE%E4%BD%9C%E3%82%8A%E6%96%B9+%28%E3%81%96%E3%81%A3%E3%81%8F%E3%82%8A%29">Slack appの作り方 (ざっくり)</a></h3> <p>普通に自分のワークスペースでだけ動くbotつくる、というだけであれば、<a target="_blank" rel="nofollow noopener" href="https://slackapi.github.io/hubot-slack/#getting-a-slack-token">ここに書いてあるとおりに</a>Slack appつくって <code>Bot OAuth Access Token</code> コピーするだけで大体は事足りると思うのですが、<br /> (↑はhubot用の解説ですがtoken取得するという意味ではあまり変わらないはず)<br /> いろいろなワークスペースで利用されることを前提とする場合、ちゃんとOAuthを利用してtokenを取得できる必要があったり、<a target="_blank" rel="nofollow noopener" href="https://api.slack.com/slash-commands">Slash Commands</a>や<a target="_blank" rel="nofollow noopener" href="https://api.slack.com/interactive-messages">Interactive Components</a>(ボタンとかそういうやつ)利用したい場合は別途設定が必要だったりと、かつて<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>に比べてかなり設定が煩雑になっており、<br /> そのあたりの日本語のリソースもあまりみかけないので、正直このあたりどうするのがいいのかよくわからんという人も多いのではないでしょうか。</p> <p>一応Slack appの主な特徴として以下の6つがあるのですが、</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://api.slack.com/incoming-webhooks">Incoming Webhooks</a></li> <li><a href="">Interactive Components</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://api.slack.com/slash-commands">Slash Commands</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://api.slack.com/docs/oauth">OAuth & Permissions</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://api.slack.com/events-api">Event Subscriptions</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://api.slack.com/bot-users">Bot Users</a></li> </ul> <p>細かいことはおいておいて、とりあえず全体感掴むためには、</p> <ul> <li>OAuth周り <ul> <li><a target="_blank" rel="nofollow noopener" href="https://api.slack.com/tutorials/tunneling-with-ngrok">Using ngrok to develop locally for Slack</a></li> </ul></li> <li>Slash Commands / Interactive Components周り <ul> <li><a target="_blank" rel="nofollow noopener" href="https://api.slack.com/tutorials/intro-to-message-buttons">Message Buttons with Node.js</a></li> </ul></li> </ul> <p>上記チュートリアルを一通りやると、設定周りからコードの雰囲気までは大体理解できるのではと思います。(英語 + Node.jsになってしまいますが)</p> <p>Events APIも大体Slash CommandsとInteractive Componentsと同じような雰囲気なのですが、一点、URL設定するタイミングでそのURLがちゃんとレスポンス返さないとエラーになったりするので(refs: https://api.slack.com/events-api#subscriptions) ご注意ください。</p> <p>(その他詳細な解説は今回は時間の関係上割愛します:bow:)</p> <h3 id="実装でハマったところ"><a href="#%E5%AE%9F%E8%A3%85%E3%81%A7%E3%83%8F%E3%83%9E%E3%81%A3%E3%81%9F%E3%81%A8%E3%81%93%E3%82%8D">実装でハマったところ</a></h3> <h4 id="APIでかゆいところに手が届かない"><a href="#API%E3%81%A7%E3%81%8B%E3%82%86%E3%81%84%E3%81%A8%E3%81%93%E3%82%8D%E3%81%AB%E6%89%8B%E3%81%8C%E5%B1%8A%E3%81%8B%E3%81%AA%E3%81%84">APIでかゆいところに手が届かない</a></h4> <p>これ全体通して調査に時間かかったり手戻りする原因になったりしたのですが、そもそもの方針として、セキュリティ観点からなるべくアプリ側にデータ持たないで実装したいというのがあり、APIで完結できるものはなるべくAPIで完結させようと思っていたので、<br /> たとえば、毎月絵文字の集計をするという要件を満たす方法はいくつかあると思うのですが、APIで特定の絵文字がついたメッセージを取得してきてそれを集計できるのであればそれが楽かなと思い、</p> <p><a target="_blank" rel="nofollow noopener" href="https://api.slack.com/methods/reactions.list">reactions.list method | Slack</a></p> <p>ぱっと見ちょうどいいAPIがあるのでこれ使えばいけるだろ、と思ってよくよくドキュメントをみてみると、<br /> 絵文字や期間でフィルタリングできなさそうで、このAPIだけでやろうと思えばできなくはなさそうですが無駄にリクエスト投げまくらないといけないのと、APIのRate Limitや条件のハンドリングなどやたらと煩雑になりそうだったため、結局リアクションの追加/削除のイベントをsubscribeしてそれをDBにレコードとしてつっこんで集計するようにしてます。</p> <p>また、設定する絵文字の存在確認をするために絵文字一覧とか取得したくなると思うのですが、</p> <p><a target="_blank" rel="nofollow noopener" href="https://api.slack.com/methods/emoji.list">emoji.list method | Slack</a></p> <p>ぱっと見ちょうどいいAPIがあるのでこれ使えばいけるだろ、と思って実装に組み込んでみるとほとんどバリデーションで弾かれてしまい、何だこれと思ってよくよくドキュメント呼んでみると、どうもカスタム絵文字しか返してくれないらしく、そもそもコンセプト的にはカスタム絵文字設定してほしいのでカスタム絵文字限定とかにしても良かったのですが、一旦ここのバリデーションは特になしとしました (設定完了後にメッセージで絵文字返すのでそこでちゃんと絵文字になってなければ設定ミスってても気づくはず...)</p> <p>などなど、意外とAPIだけで完結させようと思うと難しく、アプリケーション側でなにかしらデータ持つなどしてうまいことやらないといけないパターンが多そうという印象でした。<br /> なんとなくで着手すると意外とそれできないんかいというのでハマったりするのでちゃんとドキュメント読んでから作業することをおすすめします:innocent: (当たり前)</p> <h4 id="絵文字エイリアス問題"><a href="#%E7%B5%B5%E6%96%87%E5%AD%97%E3%82%A8%E3%82%A4%E3%83%AA%E3%82%A2%E3%82%B9%E5%95%8F%E9%A1%8C">絵文字エイリアス問題</a></h4> <p>特定の絵文字でリアクションされたかどうか判別するために、 Events APIで<code>reaction_add</code>、 <code>reaction_remove</code> イベントをsubscribeして処理をハンドリングするようにしているのですが、<br /> 絵文字のエイリアスが設定されている場合かつエイリアスの絵文字文字列でリアクションした場合、エイリアスではなくオリジナルの絵文字文字列で返ってきます。<br /> (ex: <code>:ok_woman:</code> -> <code>:woman-gesturing-ok:</code>)<br /> せめてエイリアスあった場合にエイリアスも一緒に返してきてくれると嬉しかったりするのですが、そんな親切仕様ではないので、特定の絵文字に対してなにかしらハンドリングしたい場合は真面目にやるならきちんとこのエイリアスまわりのマッピングを何かしらアプリケーション側でもつ必要がありそうです。<br /> (cf: <a target="_blank" rel="nofollow noopener" href="https://stackoverflow.com/questions/39490865/how-can-i-get-the-full-list-of-slack-emoji-through-api">How can I get the FULL list of slack emoji through API? - Stack Overflow</a>)<br /> とりあえず今回の実装ではエイリアス問題には対応できてません:cry:</p> <h4 id="publicなチャンネルしかみれないようにscope設定してるのになぜか一部privateなチャンネルのメッセージが見れてしまう問題"><a href="#public%E3%81%AA%E3%83%81%E3%83%A3%E3%83%B3%E3%83%8D%E3%83%AB%E3%81%97%E3%81%8B%E3%81%BF%E3%82%8C%E3%81%AA%E3%81%84%E3%82%88%E3%81%86%E3%81%ABscope%E8%A8%AD%E5%AE%9A%E3%81%97%E3%81%A6%E3%82%8B%E3%81%AE%E3%81%AB%E3%81%AA%E3%81%9C%E3%81%8B%E4%B8%80%E9%83%A8private%E3%81%AA%E3%83%81%E3%83%A3%E3%83%B3%E3%83%8D%E3%83%AB%E3%81%AE%E3%83%A1%E3%83%83%E3%82%BB%E3%83%BC%E3%82%B8%E3%81%8C%E8%A6%8B%E3%82%8C%E3%81%A6%E3%81%97%E3%81%BE%E3%81%86%E5%95%8F%E9%A1%8C">publicなチャンネルしかみれないようにscope設定してるのになぜか一部privateなチャンネルのメッセージが見れてしまう問題</a></h4> <p>これマジで困りました。<br /> 具体的に言うと、</p> <p><a target="_blank" rel="nofollow noopener" href="https://api.slack.com/methods/channels.history">channels.history method | Slack</a></p> <p>このAPIでリアクションついたメッセージの詳細取りに行ってるのですが、</p> <blockquote> <p>This method returns a portion of message events from the specified public channel.</p> </blockquote> <p>↑のように、publicチャンネルのメッセージしか返さないよと言ってるのに何故かとあるprivateチャンネルのメッセージだけ取れてしまうという、しかも全然再現性なくて他のprivateチャンネルでは発生せず、おそらくSlack側の何かしらのバグなのではないかと思ってます。</p> <p>とはいえうっかりprivateチャンネルのセンシティブなメッセージがオープンになるみたいな事故は起きてもらっては困るので、あれこれ手を動かした結果、</p> <p><a target="_blank" rel="nofollow noopener" href="https://api.slack.com/methods/conversations.list">conversations.list method | Slack</a></p> <p>このAPIを叩いてpublicチャンネルの一覧を取得して、一覧に含まれていなければprivateチャンネルとみなして処理を通さない、という実装に落ち着きました。</p> <p>ちなみに似たような名前の</p> <p><a target="_blank" rel="nofollow noopener" href="https://api.slack.com/methods/channels.list">channels.list method | Slack</a></p> <p>こっちだと件のprivateチャンネルも一緒に返ってきてしまって使えません。<br /> (そもそもよくみると <code>Don't use this method. Use conversations.list instead.</code> って書いてあったりするのですが、もう少しわかりやすくしておいてくれても良い気がする...)</p> <h4 id="絵文字やチャンネルをどうやって登録させるか"><a href="#%E7%B5%B5%E6%96%87%E5%AD%97%E3%82%84%E3%83%81%E3%83%A3%E3%83%B3%E3%83%8D%E3%83%AB%E3%82%92%E3%81%A9%E3%81%86%E3%82%84%E3%81%A3%E3%81%A6%E7%99%BB%E9%8C%B2%E3%81%95%E3%81%9B%E3%82%8B%E3%81%8B">絵文字やチャンネルをどうやって登録させるか</a></h4> <p>どの絵文字をトリガーにして、どのチャンネルに転送するかをユーザーが設定できる必要があり、(デフォルトは絵文字: :+1:、 チャンネル: #general)<br /> 普通にSlash Commandsで登録するでも良かったのですが、<br /> ドキュメントいろいろ眺めていたところ <a target="_blank" rel="nofollow noopener" href="https://api.slack.com/dialogs">Dialogs</a> なるものを発見、簡単に言うとSlack上でフォーム的なUIを実現できるものです。</p> <p>バリデーション等考えるとUX的にはこちらのほうが良さそうな気がしたので(エラー表示とか)<br /> とりあえず実装してみたのですが、<br /> チャンネル選択をselectにした部分は良かったものの、絵文字入力用のテキストフォームで絵文字補完が効かず、これなら普通にSlash Commandsのほうが普通に補完効くし良さそうということで、結局Slash Commandsでの設定に落ち着きました。<br /> (正直なにをいってるか文字ベースだと超わかりにくいのですが、実例はホメルくんをインストールしてもらって <code>/homeru setting</code> とか叩いてもらえればわかるかと思います:bow:)</p> <p><a href="https://crieit.now.sh/upload_images/08533f3743341462ca134baceb0143695bd029cd3cc7f.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/08533f3743341462ca134baceb0143695bd029cd3cc7f.png?mw=700" alt="Screen Shot 2018-10-22 at 20.51.27.png" /></a></p> <p>Dialogs自体は使い所を見極めればかなり使える機能ではと思います。</p> <h4 id="Incoming Webhookの挙動"><a href="#Incoming+Webhook%E3%81%AE%E6%8C%99%E5%8B%95">Incoming Webhookの挙動</a></h4> <p>メッセージの転送をするために特定のチャンネルに対して何かしらメッセージ送れる機能を実現する必要がありますが、<br /> Legacyな方の<a target="_blank" rel="nofollow noopener" href="https://api.slack.com/custom-integrations/incoming-webhooks">Incoming Webhooks</a>は昔からよく使っていて、適当なサーバー側の処理結果をSlackに飛ばすみたいなことをよくやっており、チャンネルや発言者の設定等いろいろ細かく指定できたので、とりあえずIncoming Webhookつかえば大丈夫だろうと実装してみたのですが、<br /> どうもLegacyじゃない方のIncoming Webhookはチャンネル等の指定ができないことが判明し、</p> <blockquote> <p>You cannot override the default channel (chosen by the user who installed your app), username, or icon when you're using Incoming Webhooks to post messages. Instead, these values will always inherit from the associated Slack app configuration.</p> </blockquote> <p>(refs: https://api.slack.com/incoming-webhooks)</p> <p>チャンネル自体の変更もAPIではできなさそうだったので今回の用途としてはIncoming Webhookは使えないことが判明。<br /> あれこれ調べた結果、普通にbotであれば特にチャンネルにinviteしていなくてもメッセージ送信できるようだったのでそちらで回避しました。</p> <h4>メンション飛ばさずに <code>@username</code> をメッセージに含めたい</h4> <p>Slackでは基本的に <code>@username</code> や <code>#channel_name</code> などはそのままの文字列ではなくIDとして渡ってきます。 (ex: <code>U37UED2RL</code>)<br /> これらを利用してメッセージにユーザー名やチャンネル名を含めようとした場合、以下のような記法を利用すると、Slackがよしなに変換してくれます。</p> <p>ex)<br /> - <code><@U37UED2RL></code> => <code>@uehara</code><br /> - <code><#C7F5U1EKE></code> => <code>#general</code><br /> (refs: <a target="_blank" rel="nofollow noopener" href="https://api.slack.com/docs/message-formatting">Basic message formatting | Slack</a>)</p> <p>ホメルくんのメッセージ転送機能で、誰が発言したかもわかるようにユーザー名も転送メッセージに一緒に含めたかったのですが、単純に↑の機能を使うと毎回メンション飛んできてこれはうざいという結論になり、なんとかメンション飛ばさないでユーザー名表示できないかあれこれ調べた結果、</p> <p><a target="_blank" rel="nofollow noopener" href="https://api.slack.com/methods/users.info">users.info method | Slack</a></p> <p>このAPI叩けばユーザー名取得できることが判明、よしこれでいけると思ったのですが現実はそう甘くなく、</p> <pre><code>{ "ok": true, "user": { "id": "W012A3CDE", "team_id": "T012AB3C4", "name": "spengler", "deleted": false, "color": "9f69e7", "real_name": "Egon Spengler", "tz": "America/Los_Angeles", "tz_label": "Pacific Daylight Time", "tz_offset": -25200, "profile": { "avatar_hash": "ge3b51ca72de", "status_text": "Print is dead", "status_emoji": ":books:", "real_name": "Egon Spengler", "display_name": "spengler", "real_name_normalized": "Egon Spengler", "display_name_normalized": "spengler", "email": "[email protected]", "image_24": "https://.../avatar/e3b51ca72dee4ef87916ae2b9240df50.jpg", "image_32": "https://.../avatar/e3b51ca72dee4ef87916ae2b9240df50.jpg", "image_48": "https://.../avatar/e3b51ca72dee4ef87916ae2b9240df50.jpg", "image_72": "https://.../avatar/e3b51ca72dee4ef87916ae2b9240df50.jpg", "image_192": "https://.../avatar/e3b51ca72dee4ef87916ae2b9240df50.jpg", "image_512": "https://.../avatar/e3b51ca72dee4ef87916ae2b9240df50.jpg", "team": "T012AB3C4" }, "is_admin": true, "is_owner": false, "is_primary_owner": false, "is_restricted": false, "is_ultra_restricted": false, "is_bot": false, "updated": 1502138686, "is_app_user": false, "has_2fa": false } } </code></pre> <p>こんなレスポンス返ってくるのですが、 <code>user.profile.display_name</code> と <code>user.profile.real_name</code> の2種類があり、 <code>user.profile.display_name</code> が設定されている人は <code>display_name</code>、そうでない場合は <code>user.profile.real_name</code> を表示するというようにしないとSlack上の表示とずれるので注意が必要です。</p> <p>ちなみに小ネタとして、 <code>attachments</code> の <code>author_name</code> フィールドは <code><@U37UED2RL></code> でもメンションにならないのですが、今回は表示上の問題で利用は断念しました。<br /> (refs: <a target="_blank" rel="nofollow noopener" href="https://api.slack.com/docs/message-attachments">Attaching content and links to messages</a>)</p> <h4>同一チャンネルのメッセージは <code>ts</code> がユニーク</h4> <p>Slackでメッセージデータ取得する際、</p> <pre><code>{ "type": "message", "channel": "C2147483705", "user": "U2147483697", "text": "Hello world", "ts": "1355517523.000005" } </code></pre> <p>こんなかんじでデータが取れるのですが、 <code>ts</code> はtimestampの略なんだろうなとは思いつつ、ほぼ同時に同じメッセージ送った場合、かぶったりしないのか、判別可能なのか、仕様どうなってるんだと思って調べたときに地味に言及している部分見つけるの苦労したので一応ソースを貼っておきます。</p> <blockquote> <p><code>ts</code> is the unique (per-channel) timestamp.</p> </blockquote> <p>(refs: <a target="_blank" rel="nofollow noopener" href="https://api.slack.com/events/message">message event | Slack</a>)</p> <p>結論としては <code>ts</code> はチャンネルごとにユニーク性が担保されるとのこと。</p> <h2 id="アプリ公開編"><a href="#%E3%82%A2%E3%83%97%E3%83%AA%E5%85%AC%E9%96%8B%E7%B7%A8">アプリ公開編</a></h2> <p>さて、そんなこんなで思いの外時間かかりつつも一応動くようになったホメルくんですが、そのままでは他のワークスペースにシェアできません。</p> <h3 id="とりあえず公開してみる"><a href="#%E3%81%A8%E3%82%8A%E3%81%82%E3%81%88%E3%81%9A%E5%85%AC%E9%96%8B%E3%81%97%E3%81%A6%E3%81%BF%E3%82%8B">とりあえず公開してみる</a></h3> <p>とりあえず公開して他のワークスペースで利用してもらうというだけであれば、<br /> アプリ設定画面の左カラムに <code>Manage Distribute</code> というリンクがあるので、そのページ内の<br /> <code>Share Your App with Other Teams</code> ブロックの <code>Activate Public Distribution</code> ボタンを押すとシェア用のURLが発行され、これをクリックすると他のワークスペースにアプリをインストールすることが可能です。<br /> ちなみにURLに利用するスコープがパラメータとして付与されており、スコープを変更するとURLも都度変更になるので、パーミッション周りをいじったあとは注意が必要です。<br /> (実際の設定ではなくURLパラメータのスコープが優先される模様、不要なスコープ要求したり逆に足りないといったことが起こりえます)</p> <h3 id="審査に出す"><a href="#%E5%AF%A9%E6%9F%BB%E3%81%AB%E5%87%BA%E3%81%99">審査に出す</a></h3> <p>とりあえず上記だけで他のユーザーに使ってもらえる状態にはなったのですが、<br /> それだけの場合、アプリのページやアプリインストール時のページに以下のような警告文言が出ます。</p> <p><a href="https://crieit.now.sh/upload_images/08533f3743341462ca134baceb0143695bd029f80fb21.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/08533f3743341462ca134baceb0143695bd029f80fb21.png?mw=700" alt="Screen Shot 2018-10-21 at 13.57.02.png" /></a></p> <p>またApp Directoryのページで検索しても出てきません。<br /> <a href="https://crieit.now.sh/upload_images/08533f3743341462ca134baceb0143695bd02a2180459.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/08533f3743341462ca134baceb0143695bd02a2180459.png?mw=700" alt="Screen Shot 2018-10-24 at 11.41.57.png" /></a></p> <p>知り合いにだけ使ってもらうレベルであれば問題ないですが、<br /> ちゃんと一般にリリースしようと思った場合はSlackにアプリの審査してもらう必要があります。</p> <h4 id="審査について"><a href="#%E5%AF%A9%E6%9F%BB%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6">審査について</a></h4> <p>審査に出すためにはいくつか必須で設定必要な項目があり、アプリ設定のBasic Informationページで <code>Display Information</code> と <code>Your Contact Information</code> の入力、OAuth & Permissionsページでscopeを有効にしている場合はscope毎にそのscopeを利用する理由の入力、その上で、Manage Distributionページの <code>Submit to the Slack App Directory</code> ブロックにある鬼のような大量のチェックボックスをひたすらクリックしまくると晴れてSubmitできるようになります。<br /> (refs: <a target="_blank" rel="nofollow noopener" href="https://api.slack.com/tutorials/submitting-apps-to-the-directory">Submitting apps to the Slack App Directory | Slack</a>)</p> <h5 id="Display InformationのTips"><a href="#Display+Information%E3%81%AETips">Display InformationのTips</a></h5> <ul> <li>Background colorはあまり明るすぎると文字が見えにくいとの理由でエラーになる</li> <li>Long descriptionはSlack記法が使える(**で太字など)</li> </ul> <h4 id="審査指摘実例集"><a href="#%E5%AF%A9%E6%9F%BB%E6%8C%87%E6%91%98%E5%AE%9F%E4%BE%8B%E9%9B%86">審査指摘実例集</a></h4> <p>そんなこんなで審査に出すとその日のうちに結果が返ってきました (早い!)</p> <p><a href="https://crieit.now.sh/upload_images/08533f3743341462ca134baceb0143695bd02a4fadf78.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/08533f3743341462ca134baceb0143695bd02a4fadf78.png?mw=700" alt="Screen Shot 2018-10-22 at 13.47.15.png" /></a><br /> こんな感じで項目ごとに心温まるメッセージをいただけます。Thanks!</p> <p>以下、突っ込まれたところを列挙します</p> <ul> <li><code>s/slack/Slack/g</code> <ul> <li>説明文やLPに<code>Slack</code>文字列を使う場合、頭文字は大文字にしないと怒られます</li> </ul></li> <li>アイコンにSlackのロゴはNG <ul> <li>初期のホメルくんはキャップがSlackロゴだったのですが勝手にロゴ使うと怒られます<br /> <a href="https://crieit.now.sh/upload_images/a8b393207a34388e16ffb19ca62fd4ca5bd02a718fb68.jpeg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/a8b393207a34388e16ffb19ca62fd4ca5bd02a718fb68.jpeg?mw=700" alt="homerukun_favicon.jpg" width="320px"/></a></li> </ul></li> <li>プライバシーポリシーは英語で <ul> <li>中の人がレビューできないので英語にしてほしいとのこと、機械翻訳はNG、とりあえず今回は<a target="_blank" rel="nofollow noopener" href="https://www.36inkan.com/pc/post_205/">こちら</a>を参考に作成</li> </ul></li> <li>OAuth & Permissionsのscope利用理由はちゃんと書いて <ul> <li>最初気づかずに空欄にしてたら怒られました、とりあえず一行とかで簡単に書いておけば普通に通ります</li> </ul></li> <li>問い合わせURLはFacebook messengerだとNG <ul> <li>Slackアカウント以外のアカウントが必要になる動線は不適切なのでNG、とりあえずGoogle Formに変更して対応</li> </ul></li> <li>インストール後のリダイレクト先はDeep Link等利用して <ul> <li>当初はインストール後にとりあえず <code>https://my.slack.com</code> に飛ばすようにしていたのですが、ブラウザで利用してない人もいるので https://api.slack.com/docs/deep-linking#app_or_bot こちら参考にして適切な飛ばし先にリダイレクトしてあげてくれとのこと、とりあえず今回はThanksページ作って対応 (ほぼLPそのままですが)</li> </ul></li> <li><code>channels:read</code>スコープは<code>bot</code>スコープに含まれてるから不要 <ul> <li>というわけで削除、このスコープは転送用チャンネル設定のバリデーションや↑のprivateチャンネル除外する用で使ってます、ちなみにbotスコープで利用する場合はbot用のaccess_tokenでAPI叩く必要あり</li> </ul></li> </ul> <p>そんなこんなで2度ほど申請対応して晴れて審査通って今回リリースできました:tada:</p> <p><a href="https://crieit.now.sh/upload_images/08533f3743341462ca134baceb0143695bd02a9fb3085.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/08533f3743341462ca134baceb0143695bd02a9fb3085.png?mw=700" alt="Screen Shot 2018-10-24 at 10.00.45.png" /></a><br /> <a href="https://crieit.now.sh/upload_images/08533f3743341462ca134baceb0143695bd02ac7b93bf.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/08533f3743341462ca134baceb0143695bd02ac7b93bf.png?mw=700" alt="Screen Shot 2018-10-24 at 11.35.48.png" /></a></p> <p>(ちなみにThanksメッセージのフィードバック対応は当日わりとすぐにレスポンス返ってきたのですが、もろもろ対応したあとに再度申請して実際に承認されるまでは丸一日以上かかりました)</p> <h2 id="あとがき"><a href="#%E3%81%82%E3%81%A8%E3%81%8C%E3%81%8D">あとがき</a></h2> <p>いかがでしたでしょうか、本当はもっと細かい粒度で記事分けてスクショフル活用しながら詳細書きたいところも多々あるのですが、<del>めんど(ry</del>諸事情によりかなり端折ってしまったので正直わかりにくいところも多かったかもしれません。</p> <p>コードに関してはオープンにしてもOKと寛大なお言葉をいただいてるので、そのうちコード自体公開する予定ではあるのですが、時間制約の都合上かなり雑なコードに仕上がっているので流石にもうちょっと整理してから公開する予定です:bow:<br /> とりあえず興味ある方は<a target="_blank" rel="nofollow noopener" href="https://twitter.com/munky69rock">Twitter</a>でDMいただければ今の雑なやつでも個別にシェアしますので気軽にお声がけいただければと思います。</p> <p>Slack app、直近まだまだ開発しやすい環境が整っているとは言いにくい状態という印象ですが(scope変更を含むバージョンアップしたくなったときどうしたらいいんだろうとかとか)、<br /> Slackがかなり多くの企業やユーザーに利用されているプラットフォームではあるのは間違いないのと、まだまだアプリのマーケットプレイスとしては黎明期で何かしら参入できる余地かなり大きいと思うので、個人開発者にとっても今からiOS/Androidアプリ作るよりはワンチャンあるのではと思ってます。<br /> (ただし、真面目に課金とかしようと思うとSlack上で決済できる仕組みが今のところなく、何かしら自分で頑張らないといけないのが若干ハードル高そうですが...)<br /> 是非この機会にSlack appチャレンジしてみてはいかがでしょうか。</p> <p>そして是非皆様ホメルくんつかっていただいてフィードバックいただければと思います!<br /> <a target="_blank" rel="nofollow noopener" href="https://homerukun.atengagement.com/">https://homerukun.atengagement.com/</a><br /> <a target="_blank" rel="nofollow noopener" href="https://slack.com/apps/ADE8ADF6E--">https://slack.com/apps/ADE8ADF6E--</a></p> <p>Have a good Slack :tada:</p> <h2 id="Appendix"><a href="#Appendix">Appendix</a></h2> <h3 id="ホメルくんの環境について"><a href="#%E3%83%9B%E3%83%A1%E3%83%AB%E3%81%8F%E3%82%93%E3%81%AE%E7%92%B0%E5%A2%83%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6">ホメルくんの環境について</a></h3> <ul> <li>heroku</li> <li>PostgreSQL</li> <li>Ruby (2.5.1)</li> <li>Ruby On Rails (5.2.1)</li> </ul> <p>せっかくなので技術的チャレンジ込みで Go + Serverless な感じでやってみようと思ったのですが、思いの外時間かかりそうだったので手っ取り早く実装できるRailsに落ち着いてしまいました...<br /> 大体の雰囲気は掴んだので次回はそちらで挑戦してみたい。</p> <div class="footnotes" role="doc-endnotes"> <hr /> <ol> <li id="fn:1" role="doc-endnote"> <p>hubotとかが出始めた頃と比較して、当時の機能は今はほとんどDeprecatedになってますね... <a href="#fnref:1" class="footnote-backref" role="doc-backlink">↩︎</a></p> </li> </ol> </div> munky69rock tag:crieit.net,2005:PublicArticle/14477 2018-07-09T06:32:47+09:00 2019-11-29T13:15:18+09:00 https://crieit.net/posts/Slack-PHP SlackはPHPっぽいもので作られているらしい <p>(※2016年に書かれた記事の翻訳ですので今とは違う情報が含まれます)</p> <p>現在業務用としても一般のコミュニティとしてもよく使われているチャットサービスのSlack。サーバーサイドはどのプログラミング言語で作られていると思いますか?</p> <p>実はPHPで作られています。(現在は恐らくHack)</p> <p>詳しくはSlackのチーフアーキテクト職の方が書かれた下記の記事に書かれています。(2016年に書かれたものです)</p> <p><a target="_blank" rel="nofollow noopener" href="https://slack.engineering/taking-php-seriously-cf7a60065329">Taking PHP Seriously – Several People Are Coding</a></p> <p>ちょっと驚きですよね。今の時代だと日本だとスタートアップ界隈ではRailsが人気ですし、そもそもチャットシステムを作るのであればWebSocketを使うためにGo, Elixir, Node.jsで実装するのが普通のような気がします。</p> <h2 id="なぜPHPを選んだのか?"><a href="#%E3%81%AA%E3%81%9CPHP%E3%82%92%E9%81%B8%E3%82%93%E3%81%A0%E3%81%AE%E3%81%8B%EF%BC%9F">なぜPHPを選んだのか?</a></h2> <p>まず最初に語られているのは、大勢の人がPHPに抱く気持ちとしては悪いイメージなのではないか、ということです。日本でもよく聞く印象です。しかし、Facebook, Wikipedia, Wordpress, Etsy, Baidu, Box, そしてSlackがPHPを使って成功しています。更にSlack社内ではPHPによる新しいプロジェクトもスタートしたとのことです。</p> <p>他の言語を使っていればもっと成功していたのか? と言われると決してそうではありません。PHPには多くの欠陥がありますが、それを十分に補えるような良い所も多くあるということです。</p> <h2 id="PHPの良い所とは"><a href="#PHP%E3%81%AE%E8%89%AF%E3%81%84%E6%89%80%E3%81%A8%E3%81%AF">PHPの良い所とは</a></h2> <h3 id="独立したステート"><a href="#%E7%8B%AC%E7%AB%8B%E3%81%97%E3%81%9F%E3%82%B9%E3%83%86%E3%83%BC%E3%83%88">独立したステート</a></h3> <p>PHPはリクエスト毎に完全に挙動やステートが独立しています。あるリクエストがその次のリクエストに影響を与える、ということはありません。(開発者が生み出してしまうDB等との連携によって生まれる不具合等の話ではなく、言語自体の話になると思います)</p> <p>他の言語の様に他のリクエストに影響を与えてしまう可能性がある場合、DB、memcache、ファイルシステム等を混乱させる可能性が有ります。そのためリクエスト毎にヒープを分離してくれていることがプログラム上の問題の削減に役立っています。</p> <p>例えばNodeとかだと下記のようにすればリクエスト毎に出力を変えてしまう事が容易ですよね。しかも内部の値ですのでしっかりとログを出力などしないと問題が出た時の原因の把握すら困難です。</p> <pre><code class="javascript">var globalValue = 1; http.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end(globalValue++); }).listen(8000, '127.0.0.1'); </code></pre> <p>自分で作ったプログラムならまだしも、何かのライブラリの不具合だった場合、しかもサービスの本番環境に著しい問題が出てしまった場合など、確かに考えるだけでも恐ろしいですね…。</p> <h3 id="並行性"><a href="#%E4%B8%A6%E8%A1%8C%E6%80%A7">並行性</a></h3> <p>PHPにおいて、各リクエストは単一のPHPスレッドで実行されます。この制限はデメリットのように見えますが、Webサーバーのコンテキストでいい感じに処理してくれるため、他の言語に比べエラーに対しての復元力が強いです。</p> <p>たしかにWebサーバー自体が落ちなければ絶対動きますしね。</p> <h3 id="プログラマのワークフローが高速かつ効率的"><a href="#%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9E%E3%81%AE%E3%83%AF%E3%83%BC%E3%82%AF%E3%83%95%E3%83%AD%E3%83%BC%E3%81%8C%E9%AB%98%E9%80%9F%E3%81%8B%E3%81%A4%E5%8A%B9%E7%8E%87%E7%9A%84">プログラマのワークフローが高速かつ効率的</a></h3> <p>プログラムを修正したらブラウザ上でリロードすればすぐ確認できます。</p> <p>たしかに他の言語の場合、修正したファイルによってはサーバーリロードしたりしないといけなかったりしますよね。</p> <h2 id="PHPの問題点とそれについての考え方"><a href="#PHP%E3%81%AE%E5%95%8F%E9%A1%8C%E7%82%B9%E3%81%A8%E3%81%9D%E3%82%8C%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6%E3%81%AE%E8%80%83%E3%81%88%E6%96%B9">PHPの問題点とそれについての考え方</a></h2> <p>PHP自体はよく言われているように色々と問題点があります。元の記事にも色々書かれていますが、日本でもよく見かける話なのでとりあえず割愛します。</p> <p>これは他の言語でも同様かもしれませんが、大切なことは問題点をしっかり理解して抑えながら良い部分を活かしていくことです。</p> <h2 id="Hack、HHVMの存在"><a href="#Hack%E3%80%81HHVM%E3%81%AE%E5%AD%98%E5%9C%A8">Hack、HHVMの存在</a></h2> <p>HackというのはFacebookが開発した、PHPを拡張したPHPと同じ構文を使ったプログラミング言語です。HHVMはPHPやHackを動作させる仮想マシンです。Hackは下記のような特徴があります。</p> <ul> <li>PHPで書かれたスクリプトはHackでも動く</li> <li>型を使うことができる(一部は現在PHPにも取り入れられている)</li> <li>非同期処理(async, await)などもできる</li> </ul> <p>つまり、PHPのプロジェクトは必要に応じてHack & HHVMに移行することができる、というメリットがあるということです。</p> <p>FacebookではPHPインタープリタより11.6倍、Wikipediaは6倍のCPU効率の改善を報告しているとのことです。Slackでは既にWeb環境をHHVMに移行し、レイテンシを大幅に改善しているとのことです。</p> <p><a target="_blank" rel="nofollow noopener" href="http://karur4n.hatenablog.com/entry/2017/07/17/211508">PHPカンファレンス関西 2017 に行ってきたぞ - 大学生からの Web 開発</a></p> <p>にも書かれていますが、恐らくどんどん必要に応じてHackにも移行しているようです。</p> <p>(ちなみにHack, HHVMは実際に僕は使ったことがないので、どれだけ良いものか、今後も期待できるか、というのは不明です)</p> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>PHPの利点はPHPの欠点を超える価値がある、という結論のようです。</p> <blockquote> <p>翻訳しつつじっくり読んでいってるけど面白い。なんかPHP以外使うの怖くなってきた。</p> </blockquote> <p>僕が翻訳中にツイートした内容ですが、じっくり読んでいるとほんとに肌寒くなってきます。とはいえ他の言語も好きなので今後も色々使っていくと思いますが。</p> <p>あとWebSocketについての話がなかったので気になるところです。</p> <p>※当記事は元記事の筆者の許可を得て翻訳を行い取り入れています</p> だら@Crieit開発者