GoogleのOAuth 2.0で受け取ったトークンをrevokeする
はじめに
GoogleのOAuth 2.0を使ったアプリケーションで、認証によって得たユーザの情報を用いて、条件に合ったユーザにのみ、サービスの利用を許可したい、という場合があります。
- 例) 学生用メールアドレス(~ac.jp)を持つユーザにのみサービスを提供したい
このブログでは、この例の場合を想定して、話を進めていきます。
トークンを取り消すこと
Google OAuth 2.0では、認証された際に、アクセストークンが発行されます。このトークンを取り消す方法には、ユーザが自分で行う方法と、アプリケーション側で行う方法があります。
- ユーザがAccount Settingsにアクセスして、認証を取り消す
- アプリケーション側でリクエストを送信し、認証を取り消す
トークンをrevokeすることは、実際のところ、実装の必要性は低いかもしれません。今回は、以下の理由から、revokeを行います。
- サービスの利用を許可しないアカウントに対して、トークン*1を残しておく理由はない
- 認証後に、OAuth認証用のURL(例 : http://localhost/auth/login/google)へアクセスすると、Googleにログインしているアカウントが一つしかないとき*2、認証済みアカウントに対してトークンが再発行される。
2の場合について、例を思い出します。
例では、OAuth 2.0 を用いた認証後、認証に用いたGoogleのメールアドレスを確認し、条件に合わないユーザであれば、サービスの利用を許可せず、ユーザに通知します。
許可されなかったユーザのうち、学生用メールアドレスを持っているが、異なるアカウントでログインを試みてしまったというようなユーザは、学生アカウントで再認証を行います。
再認証を行うのですが、Googleにログイン中のアカウントが一つだけのとき、アカウントを追加し、切り替えることは少し面倒です。
OAuth 2.0 による認証後に、条件に合っているかどうかを判断しているということは、OAuth 2.0による認証は通っているということです*3。
つまり、アプリケーションがサービスの利用を許可していない場合でも、認証を取り消さない間は、期限が切れるまで、認証されている状態になっています。
Google OAuth 2.0の仕様により、ログイン画面にアクセスすると、新しいアカウントを追加する間もなく、以前認証されたものは、自動で認証されます*4。
この場合、ユーザが取る行動には、Googleのトップページからアカウントを追加する、Account Settingsから認証を取り消すなどがあります。
また、自動で認証が通るという仕様について、認可画面をスキップしないようにする方法があります。
OAuth 2.0のエンドポイント(https://accounts.google.com/o/oauth2/auth)に対し、
approval_prompt=force のクエリパラメータを付加することです。
しかし、この方法では、全ての(アカウントを一つしか持たない)ユーザが認可画面を毎回見る事になります。そのため、認可画面をスキップするか、しないか、どちらが良いかは状況によると思います。
次に、アプリケーション側でトークンを削除する方法について、書いていきます。
環境
- Go 1.13.0
- Echo 3.3.10 (Go web framework)
- MySQL 8.0.17
- Docker 19.03.5-ce
- Docker Compose 1.25.0
認証には、Gomniauthを、データの処理のためにobjxを利用しています。
また、本ブログでは、説明に最低限必要な部分のみ記述しています。認証部分のソースコードは、Go言語によるWebアプリケーション開発を参考にしています。
www.oreilly.co.jp
トークンを取り消す方法
プログラムでトークンを取り消すには、パラメータにトークンを付加した、次の形式のリクエストを送信します。
https://oauth2.googleapis.com/revoke?token={token}
アクセストークンの取得
認証後に呼び出される関数CallbackHandler*5の中で、accessTokenを取得します。
/* import( "net/http" "github.com/labstack/echo" "github.com/stretchr/gomniauth" "github.com/stretchr/objx" ) */ // CallbackHandler -- Provider called this handler after login func (u *userHandler) CallbackHandler(c echo.Context) error { provider, err := gomniauth.Provider(c.Param("provider")) if err != nil { return err } omap, err := objx.FromURLQuery(c.QueryString()) if err != nil { return err } creds, err := provider.CompleteAuth(omap) if err != nil { return err } accessToken := creds.Get("access_token").Str() return c.Redirect(http.StatusTemporaryRedirect, "/") }
トークンを取り消す関数
取得したアクセストークンを引数に受けとる、以下の関数を作成しました。
/* import ( "io" "io/ioutil" "net/url" "net/http" ) */ func revokeToken(accessToken string) error { const googleRevokeURL = "https://accounts.google.com/o/oauth2/revoke" u, err := url.Parse(googleRevokeURL) if err != nil { return err } q := u.Query() q.Set("token", accessToken) u.RawQuery = q.Encode() resp, err := http.Get(u.String()) if err != nil { return err } defer func() { io.Copy(ioutil.Discard, resp.Body) resp.Body.Close() }() return err }
関数CallbackHandler内で取得したaccessTokenを、関数revokeTokenに渡すことで、トークンを取り消すことができました。
これにより、Googleにログインしているアカウントが一つであったとしても、認証用URLにアクセスすると、認可画面が表示されます。
また、条件に合わないユーザが認証してきたら、関数revokeTokenを呼び出す、というような利用もできます。
たとえ、認証後にログインページにリダイレクトするようなコードを書いてしまったとしても、無限ループにはならないと思います(そういう実装は良くないと思いますが……)。
ありがとうございました。
参考
閉包テーブルに触れてみる
はじめに
これは、SLPアドベントカレンダー最終日の記事となっています。毎日異なる部員が執筆を行ってきました。私を含んだ、25人の記事をお楽しみください。
SQLアンチパターンを読み始めました。まだ読んでいる途中ですが、良い本です。
さて、この本の2章では、ナイーブツリーの話題が登場します。
そこでは、階層的なデータ設計が比較されます。
今回、この中から閉包テーブルを選択して、コメント管理機能を作成しました。その中から部分的にプログラムを載せています。
閉包テーブルは、ツリー全体のパスを格納する方法です。特徴には、ノードが複数のツリーへ所属することが挙げられます。メインのテーブルとは別に、木構造のパスを記憶するテーブルを持ちます。
開発環境
テーブルの作成
CREATE TABLE comments ( id BIGINT AUTO_INCREMENT NOT NULL PRIMARY KEY, body TEXT NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, deleted_at TIMESTAMP DEFAULT NULL, user_id INT NOT NULL REFERENCES user(id), question_id INT NOT NULL REFERENCES question(id) ); CREATE TABLE tree_paths ( ancestor BIGINT UNSIGNED NOT NULL REFERENCES comments(id), descendant BIGINT UNSIGNED NOT NULL REFERENCES comments(id), PRIMARY KEY (ancestor, descendant) );
comments
属性* | 概要* |
---|---|
id | コメントのid。コメントを投稿すると、1から自動的に割り振られる |
body | コメントの内容 |
created_at | コメントの作成時間 |
updated_at | コメントの編集時間 |
deleted_at | コメントの削除時間 |
user_id | コメントを投稿した人のid |
question_id | 質問した話題のid |
tree_paths
属性* | 概要* |
---|---|
ancestor | そのコメントの先祖となるコメントid |
descendant | そのコメントの子孫となるコメントid |
ancestorとdescendantは、組み合わせが主キーとなっています。
自分自身を示すためには、両方の属性が同じidを持ちます。
コメントの登録
コメントを登録するには、2つの手順が必要です。
- コメントの挿入
- コメントのパスの挿入
プログラム内で、構造体は下のように定義しました。
type Comment struct { ID uint64 `db:"id"` Body string `db:"body"` CreatedAt *time.Time `db:"created_at"` UpdatedAt *time.Time `db:"updated_at"` DeletedAt *time.Time `db:"deleted_at"` UID uint64 `db:"user_id"` QID uint64 `db:"question_id"` }
/questions/:id へPOSTリクエストが行われると、ハンドラが呼び出されます。
func (comment *commentHandler) PostCommentHandler(c echo.Context) error { uid := getUserID() id := c.QueryParam("cid") body := c.FormValue("comment") param := c.Param("id") qid, err := strconv.ParseUint(param, 10, 64) if err != nil { return err } cid, err := strconv.ParseUint(id, 10, 64) if err != nil { return err } message := model.Comment{Body: body, UID: uid, QID: qid} fmt.Println(uid, qid, cid, body) err = comment.commentModel.CreateComment(&message, cid) if err != nil { return err } route := "/questions/" + param fmt.Println(route) return c.Redirect(http.StatusSeeOther, route) }
コメントは、CreateCommentで作成されます。
CreateCommentの引数のparent_idは、コメントの親のIDです。
先祖を持たないコメントのとき、parent_idは0としています。
SELECT LAST_INSERT_ID()は、MySQLの情報関数です。
最後にINSERTされたコメントのIDを取得するために用いています。
type CommentModel struct { db *sqlx.DB } // CreateComment -- Insert comment data func (c *CommentModel) CreateComment(comment *model.Comment, parent_id uint64) error { var id uint64 _, err := c.db.Exec(`INSERT INTO comments (body, user_id, question_id) VALUES (?, ?, ?)`, comment.Body, comment.UID, comment.QID) if err != nil { return err } err = c.db.Get(&id, "SELECT LAST_INSERT_ID()") if err != nil { return err } if parent_id == 0 { parent_id = id } _, err = c.db.Exec(`INSERT INTO tree_paths (ancestor, descendant) SELECT tree_paths.ancestor, ? FROM tree_paths WHERE tree_paths.descendant = ? UNION ALL SELECT ?, ?`, id, parent_id, id, id) return err }
上のプログラムから、TreePathへのINSERT文を抽出しました。
INSERT INTO tree_paths (ancestor, descendant) SELECT tree_paths.ancestor, id FROM tree_paths WHERE tree_paths.descendant = parent_id UNION ALL SELECT id, id;
理解を深めるために、実際に挿入しながら考えていきます。
ここに、おすすめのテキストエディタを尋ねる質問があります。
CommentIDが7の人はVS Codeを、8の人はVimを勧めました。
ここで、8の人に対して学生R(CommentID: 9)がEmacsを勧めると、どうなるでしょうか。
図で書くと、このようになるはずです。
属性には、先祖となるコメントIDと、子孫となるコメントIDを持ちます。
そのため、tree_pathsには、(8, 9), (9. 9)の二つがINSERTされるはずです。
mysql> SELECT tree_paths.ancestor, 9 FROM tree_paths -> WHERE tree_paths.descendant = 8 -> UNION ALL SELECT 9, 9; +----------+---+ | ancestor | 9 | +----------+---+ | 8 | 9 | | 9 | 9 | +----------+---+
> SELECT tree_paths.ancestor, 9
1列目にtree_paths.ancestor、2列目に9を射影
> FROM tree_paths WHERE tree_paths.descendant = 8
子孫のIDが、挿入するIDの親と等しいtree_pathsのテーブルを選択
> UNION ALL SELECT 9, 9;
自分自身を指す9, 9の行と結合
コメント情報の取得
質問に対しての全てのコメントをデータベースから取得するのなら、質問番号で選択すれば良いです。
func (c *CommentModel) All(question_id uint64) ([]model.Comment, error) { comments := []model.Comment{} err := c.db.Select(&comments, "SELECT * FROM comments WHERE question_id = ?", question_id) if err != nil { return nil, err } return comments, nil }
次の関数では、指定されたコメントIDの子孫を全て取得します。tree_pathsで、指定されたコメントIDが先祖となっている行を選択します。
func (c *CommentModel) GetDescendant(id uint64) ([]model.Comment, error) { comments := []model.Comment{} err := c.db.Select(&comments, "SELECT comments.* FROM comments INNER JOIN tree_paths ON comments.id = tree_paths.descendant WHERE tree_paths.ancestor = ?", id) if err != nil { return nil, err } return comments, nil }
おわりに
二つのテーブルを用いているため、どちらか一方のみを間違えないよう、一つのテーブルを用いるとき以上に気を使う必要があると感じました。コメントを登録するCreateComment関数では、データを二度INSERTします。しかし、現時点では、二番目のINSERTが失敗した際の、ロールバックを行う処理は未実装です。そのため、優先度高めで対応したいと思います。
また、tree_pathsに深さの属性を追加することで、直近の親子関係を簡潔に調べることができます。今回、その部分の設計は行わなかったので、今後、その部分の設計が必要かどうか、考えたいです。
今回、プログラムの全てをここに記述したわけではありません。またリファクタリングを行った際、記事にする予定です。
VMを使ってUSB経由でNFCを読み取る話
こんにちは。
現在、私の加入しているサークルでは、チーム開発の一つとして、
PaSoRiを使った出席管理システムを作成中です。
このシステムは、Python3で開発しています。
学生証をPaSoRiにタッチすると、サークルの活動に参加したことになり、その日の議事録に名前が載るシステムです(※開発中)。
NFCタグの読み取りのために、nfcpyのモジュールを使用しています。
現状、開発環境はWindows10となっています。
しかし、Windows10での開発は、正直言って面倒です。
今回、開発環境の構築までの手間を比較するために、
VirtualBoxで仮想マシンを作成し、PaSoRiを使用するための設定を行いました。
Windowsでの開発
Windowsで開発を行うためには、下の作業が必要です。
- WinUSB(またはZadig)とlibusbの手動インストール
- nfcpyをpipでインストール
今回、私のチームでは、Zadigとlibusbを手動インストールしました。
ダウンロードしてきたDLLファイルを、手動で C:\Windows\System32 や、C:\Windows\SysWOW64 にCopyすることは、置き間違えなどのリスクがあります。
VirtualBoxでの開発
- Linuxでは、libusbは通常インストール済み
- VM上でUSBデバイスを使うための設定を行う必要がある
- 権限や、ドライバーの設定のために、少しコマンドを叩く必要がある
- nfcpyをpipでインストール
Windows版と違い、手動でCopyするようなものはありません。
仮想マシンでUSBを認識するための設定や、一般ユーザが使用する際の権限の設定などをする必要があります。
環境
今回、動作確認のために用意したものは以下の通りです。
- ホストOS : Windows10 Pro 1909
- ゲストOS : ArchLinux ( Kernel 4.19.88-1-lts x86_64)
- NFCデバイス : Sony PaSoRi RC-S380
- Python 3.8.0
今回、新しいものが使いたいという理由と、約一年前に友人から布教を受けたこともあり、ArchLinuxを使いましたが、Ubuntuなどでも動くと思います。
設定の流れ
設定の流れは、次のようになります。
- 仮想マシンの用意
- VirtualBoxの仮想マシンの設定でUSBの設定
- PaSoRiの認識確認
- nfcpyのインストール
- PaSoRiの接続設定
ここでは、2以降の手順を記述します。
VirtualBoxの仮想マシンのUSB設定
まず、手元のVirtualBoxに設定済みの仮想マシンを用意します。
そして、仮想マシンの状態が電源オフであることを確認します。次に、PaSoRiをUSBポートに挿します。
使用する仮想マシンの設定から、USBを選択、USB2.0コントローラーを有効にします。次に、右にある新規のUSBフィルターの追加から、目的の物(今回の場合はSONY RC-S380/P)を選択します。
nfcpyのインストール
venvを使用し、仮想環境内にインストールします。
venvを使用する理由
venvとは、軽量な仮想環境の作成をサポートしてくれるものです。Python3の標準の機能の一つです。
メリット
- 仮想環境の中に、パッケージ群を独立してインストールできる
- 仮想環境ごとに、使用するパッケージ群のバージョンを分けられる
- それぞれの仮想環境は独立しているため、仮想環境間では競合しない
nfcpyは、現在のプロジェクトでのみ使用し、頻繁に使用するものではないため、仮想環境の中で使用します。
nfcpyのインストールの流れ
$ python -V Python 3.8.0
$ cd [WORKDIR] # 作業ディレクトリへ移動 $ python -m venv [ENV_DIR] # 環境の作成 (ENV_DIRをvenvとする例が多いらしい) $ source [ENV_DIR]/bin/activate (venv)$ pip install nfcpy (venv)$ pip freeze # インストールしたパッケージの確認
PaSoRiの接続設定
接続確認のために、下のコマンドを実行します。
(venv)$ python -m nfc
いくつかエラーが出ることがあるので、確認したものについて順に対処していきます。
This is the 1.0.3 version of nfcpy run in Python 3.8.0 on Linux-5.4.2-arch1-1-x86_64-with-glibc2.2.5 I'm now searching your system for contactless devices ** found usb:054c:06c3 at usb:002:004 but access is denied -- the device is owned by 'root' but you are 'amakuchi' -- also members of the 'root' group would be permitted -- you could use 'sudo' but this is not recommended -- it's better to adjust the device permissions sudo sh -c 'echo SUBSYSTEM==\"usb\", ACTION==\"add\", ATTRS{idVendor}==\"054c\", ATTRS{idProduct}==\"06c3\", MODE=\"0666\" >> /etc/udev/rules.d/nfcdev.rules' sudo udevadm control -R # then re-attach device I'm not trying serial devices because you haven't told me -- add the option '--search-tty' to have me looking -- but beware that this may break other serial devs Sorry, but I couldn't find any contactless device
このように出ることがあります。
要するに、デバイスを "plugdev" というグループへ追加することを推奨しています。
言われた通りに作業を行います。
$ sudo sh -c 'echo SUBSYSTEM==\"usb\", ACTION==\"add\", ATTRS{idVendor}==\"054c\", ATTRS{idProduct}==\"06c3\", MODE=\"0666\" >> /etc/udev/rules.d/nfcdev.rules' $ sudo udevadm control -R
PaSoRiの再接続を行い、もう一度、実行します。
(venv)$ python -m nfc
This is the 1.0.3 version of nfcpy run in Python 3.8.0 on Linux-5.4.2-arch1-1-x86_64-with-glibc2.2.5 I'm now searching your system for contactless devices ** found usb:054c:06c3 at usb:002:005 but it's already used -- scan sysfs entry at '/sys/bus/usb/devices/2-2:1.0/' -- the device is used by the 'port100' kernel driver -- this kernel driver belongs to the linux nfc subsystem -- you can remove it to free the device for this session sudo modprobe -r port100 -- and blacklist the driver to prevent loading next time sudo sh -c 'echo blacklist port100 >> /etc/modprobe.d/blacklist-nfc.conf' I'm not trying serial devices because you haven't told me -- add the option '--search-tty' to have me looking -- but beware that this may break other serial devs Sorry, but I couldn't find any contactless device
今度は、デバイスのport100は、既に使われているというメッセージが出ました。
そのため、port100のカーネルドライバを取り外します。
$ sudo modprobe -r port100
また、今後も停止させ続けておきたい場合は、ブラックリストに入れます。
$ sudo sh -c 'echo blacklist port100 >> /etc/modprobe.d/blacklist-nfc.conf'
もう一度、実行します。
(venv)$ python -m nfc
This is the 1.0.3 version of nfcpy run in Python 3.8.0 on Linux-4.19.88-1-lts-x86_64-with-glibc2.2.5 I'm now searching your system for contactless devices ** found SONY RC-S380/P NFC Port-100 v1.11 at usb:002:003 I'm not trying serial devices because you haven't told me -- add the option '--search-tty' to have me looking -- but beware that this may break other serial devs
このように出たら、成功です!最後に、きちんと終了させます。
(venv)$ deactivate # 仮想環境から出る
おわり!
仮想マシンのUSBデバイスフィルターの設定は、GUIでしましたが、操作ミスの起こりにくい場所であり、ファイルの手動コピーよりは心理的安全性がありました。また、仮想マシン内での操作は、コマンドをぽちぽち貼り付けるだけなので、個人的には、Windows版でやるより、仮想マシンの中でやるほうが楽だと感じました。
それから、やはりWindowsの方のドライバはあまり触りたくないなーという気持ちがあります。libusbの導入と、Zadigのインストールをしなくて良いというだけで、仮想マシンでの開発を選ぶメリットがある気がします。
なお、今回一番時間がかかったのは、ArchLinuxの設定です……。しばらく環境を壊さないように気を付けたいです……。また、ArchLinuxのデスクトップ環境は作っていません。Webアプリケーション以外のGUIアプリケーションを作る予定がない間は、デスクトップ環境は作らないと思います。作ったらまたブログに書くと思います。
それから、昨年の誕生日プレゼントにPaSoRiをくださった先輩、ありがとうございました☺
WLSのUbuntu16.04を18.04にした話
はじめに
ずっと、WSLでUbuntu16.04を使っていたので、そろそろ18.04にしようという話。
今後、後輩に18.04を入れさせて、自分は16.04というようなことになったら困るので……。
バージョン確認 (1)
amakuchi@curry:~$ lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 16.04.4 LTS Release: 16.04 Codename: xenial
現在、16.04であることを確認。
lxdとlxd-clientのパッケージを削除する
WSLでは、systemdがなく、snapによるインストールができない。
そのため、この作業を行わずに do-release-upgrade を行うと、lxdに関連する場所で、タイムアウトまで約10分間待つことになる。
sudo apt purge lxd && sudo apt purge lxd-client
更新
sudo apt update && sudo apt dist-upgrade
/etc/update-manager/release-upgrades の中で、
Prompt=lts
となっていることを確認する。
amakuchi@curry:~$ cat /etc/update-manager/release-upgrades # Default behavior for the release upgrader. [DEFAULT] # Default prompting behavior, valid options: # # never - Never check for a new release. # normal - Check to see if a new release is available. If more than one new # release is found, the release upgrader will attempt to upgrade to # the release that immediately succeeds the currently-running # release. # lts - Check to see if a new LTS release is available. The upgrader # will attempt to upgrade to the first LTS release available after # the currently-running one. Note that this option should not be # used if the currently-running release is not itself an LTS # release, since in that case the upgrader won't be able to # determine if a newer release is available. Prompt=lts
ここで一旦WSLを閉じます。
バージョン確認 (2)
amakuchi@curry:~$ lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 16.04.6 LTS Release: 16.04 Codename: xenial
少し新しくなりました。
do-release-upgradeの確認
amakuchi@curry:~$ do-release-upgrade -c Checking for a new Ubuntu release New release '18.04.2 LTS' available. Run 'do-release-upgrade' to upgrade to it.
'18.04.2' にupgradeできることがわかる。
do-release-upgradeを行う
amakuchi@curry:~$ sudo do-release-upgrade
一回Enterキーを押した後、適所でyを押せばOK
暫く待つと、次のような表示になる。
ssh-server として使用する予定はないので、
install the package maintainer's version を選択。
引き続き作業が行われる。
※ ここで、lxdのパッケージを削除していなかったら、タイムアウトまで待つことになる
最後に、Restartをするかを聞かれるが、yを押してもWSLの再起動はされない。
その後、Enterキーを押して、
Press x to destroy or r to resurrect window
と出るので、xを押して閉じる。
amakuchi@curry:~$ exit
アップグレードが完了したことの確認
amakuchi@curry:~$ lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 18.04.2 LTS Release: 18.04 Codename: bionic
できた
MESHを触った話
はじめに
MESH:小さな便利を形にできる、ブロック形状の電子ブロック|ソニー をつかいました。
MESHには、温度、光などのセンサーや、スイッチなどのブロックがあります。
センサーはMicro Bで充電します。
今回はApp StoreからMESHをインストールし、人感センサーを用いました。
やってみる
人感センサーが反応したら、Slackにタイムスタンプを送ります。
まずは、人感センサーブロックをiPadとペアリングします。
右上の追加ボタンから追加。人感センサーを長押しすることで、センサーの電源が入ります。
ペアリングできたら、ブロックを右から移動してマップに配置します。
配置したslackアイコンをタップします。
データを追加から、タイムスタンプを選択しました。
スクロールすると、IFTTTアプレット設定があります。IFTTTの設定をするように求められます。
MESHとIFTTTにメールアドレスを登録し、パスワードを設定します。
ワークスペースを決め、設定したら、チャンネルを選んだりします。
写真を撮ってみる
センサーが動きを検知したら、現場の写真を撮っておきたいと思いました。監視カメラっぽくて楽しそう。
画像はちゃんとiPadに保存されました。
先日サークルの活動場所の入り口に人感センサーブロックを置いておいたところ、100枚以上の写真が集まりました(消しました)。
今後の話
RaspberryPi使ってカメラを独立させたい。
このリポジトリでやる予定です。
github.com
slackのWebAPIを使ってみた話【トークン入手編】
メモについて
みなさん、思いついたこととか、忘れないでおきたいことがあるとき、どこにメモしますか?
私は、よくSlackにメモします。理由としては、PC、iPad、スマートフォンどれからでも確認ができるからというのが大きいです。
しかし、Slackというのは沢山ワークスペースや、チャンネルがありますよね。間違えそうになること、無いですか。
一回だけ、自分以外の人がいるgeneralにメモをするということをやりました。夜中にぼーっとするのは危ないです。
自分だけがいるワークスペースがあったとしても、間違えない可能性ってゼロじゃないですよね。私はそうです。というわけで、Slackを使わずにSlackにメモをすれば、適切な場所にメモを残すことができるのでは?と考えました。
それから、毎回メモしたいときにSlackを開くのも手間なので、もっと手軽にメモできるようにしたいですね。夕食にナンが食べたいなあと思ったとき、"ナン"の2文字をメモするためにSlackをアクティブにして、送信するのは、ちょっと大変です。
WebAPIを使おう
メッセージをチャンネルに送信することが目的なので、WebAPIを使用します。
Slack API 推奨Tokenについて - Qiita
こちらに沿ってアプリを作成し、トークンを発行します。
- https://api.slack.com/appsにアクセスし、右上のCreate New Appを選択
- アプリの名前と、使用するワークスペース名を選択
- permission scopeの設定(今回は、Send message as {アプリ名}を選択)
- 上にある緑のボタン[Install App to Team]からInstallを行う
- Authorizeすると、トークンが表示される
permission scopeが見つからない場合は、OAuth & Permissionsの項目を確認すると良い
今回は、チャンネルにメッセージを送信できれば良いので、
chat.postMessage method | Slack
を用います。推奨メソッドはPOSTです。
https://api.slack.com/methods/chat.postMessage/test
こちらで、ボタン[Test Method]を押すと、URLの見本を作ることができます。
token: xxxx-xxxx-xxxx-xxxx channel: everyone text: Hello
を入力したとすると、このように出力されます。
https://slack.com/api/chat.postMessage?token=xxxx-xxxx-xxxx-xxxx&channel=everyone&text=Hello&pretty=1
実際に自分の持っているトークン、チャンネル、メッセージを入力すると、送信されたことが確認できると思います。
curlで送信したいとき
curl -XPOST -d 'token={token}&channel={channel}&text={text}' https://slack.com/api/chat.postMessage
初めて学生LTに参加した話
はじめに
お久しぶりです。最近サボタージュしてました甘口です。アウトプットもかなりお休みして、のんびりしていました。今日は新元号が発表されるんですかね。ああ、エイプリルフールのネタ考えてないです。
何かするでもなく、フラフラしてたら丁度良いタイミングで、近場で学生LT(in 愛媛)を見つけたので、これだと思い参加してきました。
何枠で応募するか
最所の関門ですね。応募枠には、LT枠と参加枠がありました。
やっぱり、せっかくならLTしてみようと、もちろんLT枠で応募を……というわけではなく、最初は参加枠で応募しました。その後、話す内容も思い浮かんだので、LT枠にひっそりと変更しました。
LTの内容
簡単な自己紹介を行った後に、Pyxel(ピクセル)というPython向けのレトロゲームエンジンの紹介をしました。Pyxel面白いです。私の制作物の話はそのうち記事にまとめる予定です。
Pyxelの特徴
- 使える色は16色のみ
- 同時に再生できる音は4音まで
- レトロゲーム機を意識したシンプルな仕様
個人的にPyxelで面白いと感じているのは、タイルマップエディタという、マップをタイルで配置することができるところです。
(右側のイメージバンクの画像を左に配置できる)
懇親会
おいしかったです。
今後
楽しかったので、また参加したいなと思いました。
とある大学では、2年次にRaspberryPiとアセンブリ言語を使った授業があるという情報を入手したので、今年度はその辺やってみたいですね。