TypeScriptをwebpackでビルドしてみる
はじめに
名前はよく聞くけど直接使ったことなかったwebpack。
いい加減ちゃんと勉強してみようと思った回。
簡単なアプリを作って動かすとこまで。
TL;DR
webpackとは
複数のファイルをまとめてくれるモジュールバンドラー。
よく使われるのはJS(TS)を1つのファイルにまとめたりで使われてる印象。
ちなみにTypeScriptのトランスパイルにはデフォルトのtsc
があるが、
複数のファイルを纏められない(ES Modules(import,export)をまとめる機能が無い)からwebpackを使うっぽい。
インストール
必要なものをインストールしておく。
お好みで-g
なり、-D
なりを付けてください。
npm install webpack webpack-cli typescript ts-loader
ディレクトリ構成
以下のファイルとフォルダを作成する。
作った結果は下記の通り。
. ├── package.json ├── src ├── tsconfig.json └── webpack.config.js
アプリ作成
の前に……
先程作ったtsconfig.json
とwebpack.config.js
の中身を記載する。
// tsconfig.json { "compilerOptions": { "sourceMap": true, "target": "es5", // TSはECMAScript 5に変換 "module": "es2015" // TSのモジュールはES Modulesとして出力 } }
// webpack.config.js // output.pathに絶対パスを指定する必要があるため、pathモジュールを読み込んでおく const path = require('path'); module.exports = { // モードの設定、v4系以降はmodeを指定しないと、webpack実行時に警告が出る mode: 'development', // エントリーポイントの設定 entry: './src/main.ts', module: { rules: [ { test: /\.ts$/, use: 'ts-loader' } ] }, resolve: { extensions: [ '.ts' ] }, // 出力の設定 output: { // 出力するファイル名 filename: 'bundle.js', // 出力先のパス(v2系以降は絶対パスを指定する必要がある) path: path.join(__dirname, 'output') } };
アプリを作る
これでTypeScriptをビルドする準備ができたのでアプリを作っていく。
以下用に必要なファイルを作成していく。
* index.html: 表示するHTML
* src/main.ts: メインとなるts。webpack.config.js
に書いたやつ
* src/sub.ts: main.ts
で読み込まれるts
ここまでのフォルダ構成は下記の通り。
作成したファイルの中身を入れていく。
. ├── index.html ├── package.json ├── src │ ├── main.ts │ └── sub.ts ├── tsconfig.json └── webpack.config.js
index.html
<!doctype html> <html> <head> <title>こんにちは変換器</title> <meta charset="utf-8"/> </head> <body> <script src="./output/bundle.js"></script> </body> </html>
main.ts
import { Sub } from './sub'; class Main { private sub: Sub constructor() { this.sub = new Sub(); const body = document.getElementsByTagName('body'); const element = body.item(0); const div = document.createElement('div'); div.innerHTML = '「こんにちは」を他言語に変換します。<br>好きな言語を入れてください。<br>※ちなみに対応しているのは日本語、英語、スペイン語、ドイツ語です。<br>'; const input = document.createElement('input'); input.id = 'country' const button = document.createElement('button'); button.textContent = '変換'; button.addEventListener('click', () => { this.transtrationGreeting(document.getElementById('country')['value']); }); div.appendChild(input); div.appendChild(button); element.appendChild(div); } public transtrationGreeting(country: string) { const addDiv = document.createElement('div'); addDiv.innerHTML = `こんにちはを${country}で言うと${this.sub.hello(country)}`; document.getElementsByTagName('body').item(0).appendChild(addDiv); } } const main = new Main();
sub.ts
export class Sub { private greeting = { '日本語': 'こんにちは', '英語': 'Hello', 'スペイン語': 'Hola', 'ドイツ語': 'Hallo' } constructor () { } public hello(country: string) { return this.greeting[country]; } }
アプリをビルドする
ビルドは簡単。webpack
だけでOK。
webpack.config.js
に書いたとおり、output
フォルダにbudle.js
が生成されるはず。
ここまでのフォルダ構成は下記の通り。
. ├── index.html ├── output │ └── bundle.js ├── package.json ├── src │ ├── main.ts │ └── sub.ts ├── tsconfig.json └── webpack.config.js
ビルドが通れば、index.html
を開くとアプリが動く。
アプリは超絶適当なので内容は割愛。htmlに内容書けっていう内容。
まとめ
とりあえずTypeScriptだけで使った場合についてやってみた。
実際にはTSだけなんて無いと思うから、今度はReactなりVueなりと合わせて使ってみようかな。
ちなみに作ったものはここに置いた。
アプリはこっち。
参考サイト
Javascriptのthisまとめ
はじめに
JavaScriptを触ってるけど、thisの使い方は特に意識しないでやってきた。
thisの挙動が複雑だとか4種類あるとかは聞いたことあったけど、
その問題に直面することがなかったから無視してたというのが本音。
天からthisから逃げるなと言われてる気がしたので、いい加減に調べてまとめることにする。
this4種
JavaScriptのthisは以下の4つ。
それぞれについて簡単な例とともにまとめる。
- 関数(function)呼び出しパターン
- メソッド呼び出しパターン
- コンストラクタパターン
- apply,bind,callパターン
関数(function)呼び出しパターン
関数を呼ぶ際のthisは基本的にはglobalオブジェクトを指す。
ただし、use strict
を指定しているとundefinedになる。
// 'use strict'なしパターン function test() { console.log(this); } test(); // Window{...}
// 'use strict'ありパターン 'use strict' function test() { console.log(this); } test(); // undefined
メソッド呼び出しパターン
関数もメソッドも同じだろって感じだけど実は違う。 下記のようなパターンのこと。
const obj = { test: function() { console.log(this); } } obj.test(); // { test: [Function: test] }
ちなみにこのthisをどうしてもglobalとして使いたいなら下記のようにする。
const obj = { test: function() { console.log(this); } } // 下記パターンどちらでもglobalになる // パターン1 const globalThis = obj.test; globalThis(); // Window{...} // パターン2 (0, obj.test)(); // Window{...}
コンストラクタパターン
コンストラクタのthisは生成するobject(自分自身)を指すようになる。
何言ってるかわからなくても、見ればなんとなくわかるはず。
class Test { constructor(value){ this.value = value; // 自身(Test)のvalueに引数のvalueを入れてる } } const newClass = new Test(10); console.log(newClass.value); // 自身のvalueにちゃんと入っている
ちょっと古いクラスの書き方するなら下のような感じ。
もちろん結果は同じ。
ただしこの書き方だと、あくまで関数なのでnew
を忘れるとthisはglobalを指すようになってしまう。
おとなしくclass
と付けるのがいいと思う。
function Test(value) { this.value = value; } const newClass = new Test(10); console.log(newClass.value);
apply,bind,callパターン
apply,call
applyとcallは共にthisの内容を設定できるようにするもの。
下記のようなパターンのこと。
function test() { console.log(this); } const obj = { name: 'test' }; test(); // testのthisをobjに設定 test.call(obj); // obj{...} test.apply(obj); // obj{...}
applyとcallの違いは引数のとり方が違う。
applyは配列、callはそのままといった具合。
const testObj = { test: function(value1, value2) { console.log('this:', this); console.log('value1:', value1); console.log('value2:', value2); } } const obj = { name: 'test' }; testObj.test.apply(obj, [1, 3]); testObj.test.call(obj, 1, 3);
# 実行結果 this: { name: 'test' } value1: 1 value2: 3 this: { name: 'test' } value1: 1 value2: 3
bind
ここで参照するthisはこれじゃなきゃ嫌なんだ!という時に使う。
要は強制的にthisの内容を紐付けてやること。
function test() { console.log(this); } const obj = { name: 'test' }; // testのthisをobjにする const bid = test.bind(obj); test(); // こっちはglobal参照 bind(); // こっちはobj参照
おまけ(アロー関数)
アロー関数はthisを変えたりしない。
変に値が変わらない分わかりやすいかも知れない。
const obj = { func: function(){ console.log(this); }, arrow: () => { console.log(this); } } obj.func(); // { func: [Function: func], arrow: [Function: arrow] } obj.arrow(); // Windw{...}
まとめ
ようやく向き合ったthis
は思ってたよりも理解不能じゃなかった。
理解不能ではなかったけど、わかりやすく説明するのは無理だなって思った。
今度はprototype辺りとも向き合いたい。
Javascriptのfor系統まとめ(for,for..in,for..of,forEach)
TL;DR.
ソースコード。
実行はNode.jsで。
はじめに
for...inとfor...ofどっちがどっちか忘れるからまとめる。
ついでに他のforについてもまとめて、個人的なメリットデメリットも記載する。
forの種類
- for
- for...in
- for...of
- Array.forEach()
for
単純なfor。指定した回数繰り返し処理をしてくれる。
// 単純ループ let sum = 0; for (let i = 1; i <= 10; i++) { sum += i; } // リスト全ループ const list = [1, 3, 5, 7]; for (let i = 0; i < list.length; i++) { console.log('value:', list[i]); }
メリット
わかりにくいとかは無いと思う。
これといったメリットは思いつかなかった。
デメリット
いちいち書くのがめんどくさいと思う。
for...in
オブジェクトのプロパティ(キー)分ループしてくれる。
const obj = {'a':1, 'b':2, 'c':3}; for (const value in obj) { console.log('key:', value); console.log('value:', obj[value]); }
メリット
オブジェクト(連想配列)のループができる。
デメリット
配列に使うとあんまりよくない。
const list = [1, 3, 5]; list.text = 'hoge'; list.func = function(){ console.log('hoge'); } for (const key in list) { console.log('key:', key); console.log('value:', list[key]); }
出力は下記の通り。
配列のループは直感だと値が逐次取れるイメージだと思う。
実際はindexが取れる。
しかも後から追加した変なのも普通に取れてしまう。
key: 0 value: 1 key: 1 value: 3 key: 2 value: 5 key: text value: hoge key: func value: function (){ console.log('hoge'); }
当たり前だけど、TypeScriptでも変わらない。
class test { constructor() { const list:number[] = [1, 3, 5]; list['text'] = '1'; for (const value in list) { console.log(value); } } } new test(); /* 結果 0 1 2 text */
for...of
プロパティの値分ループするfor文。
Javaの拡張forが近しい動きだと思う。
const list = [1, 3, 5]; for (const value of list) { console.log(value); }
メリット
下みたいなことしても問題なく出力してくれる。
const list = [1, 3, 5]; list.text = '1'; for (const value of list) { console.log(value); }
デメリット
ES6で導入されたから、古いブラウザだと動かない。
ブラウザ変えろ。
Array.forEach
配列要素に対しての繰り返しを行ってくれるメソッド。
できることはfor...ofとほぼ同じ。
const list = [1, 3, 5]; list.forEach(element => { console.log(element); });
callbackの引数全指定した場合は下記の通り。
下記例のarrayについては、特に何もしなければループ中同じ値が取れる。
const list = [1, 3, 5]; list.forEach((element, index, array) => { // 取り出した配列の値 // 例:1,3,5 console.log('element:', element); // 配列のインデックス // 例:0,1,2... console.log('index:', index); // ループしてる配列自身 // 例:[1, 3, 5] console.log('array:', array); });
メリット
indexを使うことができる。
(人によると思うけど)見やすい。
callbackだから関数を切り出せる。
(切り出されているところを見たこと無いし、for...ofでも同じ気がするけど)
const loop = element => { console.log('callback:', element); } list.forEach(loop);
デメリット
あくまでcallbackなので、breakできない。
※continueはreturnと書けばOK。
速度に不安があるみたいなので、速度が重要な 場合は使わないほうが無難だと思う。
おまけ
オブジェクトに対してforEachかけるなら、Object.keys()
と組み合わせる。
const obj = { 'a': 1, 'b': 2, 'c': 3 }; Object.keys(obj).forEach(key => { console.log('key:', key); console.log('value:', obj[key]); });
まとめ
for...in以外ならどれでも良いと思う。
軽く調べた感じ、for...ofが優勢だった。
配列にしか使えないforEachと違って、
for...ofはiterable object
ならなんでも使えるらしい。
更に速度面に不安なのもあると思われる。
個人的には速度をとても気にする必要もないし見やすいので、
breakする必要が無いならforEachを使ってる。
GoogleSiteの変更通知をSlackで受け取る
はじめに
ある日、Googleサイトの通知をSlackで受け取りたいという話がありました。
しかし、DriveやCalendarの通知を受け取ることはできるSlackくんですが、なんとSiteの通知は受け取れないのです。
調べたところ先人の方たちは自力で実装していた。
今回は先人に習って自力での実装をしてみた。
Googleサイトの変更通知をSlackに流すまで
上記の通り、SlackとGoogleサイトを紐付けるAPPはなかった。
仕方ないから自力で実装する。
流れは下記の通り。
- サイトの変更通知をメールで受け取る
- GASでGmail見て変更通知から必要な情報を抜き取る
- SlackAPI使って抜き取った情報を適当なチャンネルに流す
サイトの変更通知を受け取る
サイトでShift + f
で通知を受け取れるようになる。
もしくは、サイト右上の歯車から「サイトの操作→サイトの変更通知を受け取る」でもOK。
GASからGmailの情報を抜きとる
サイトの通知メールはnoreply@google.com
というアドレスで来るので、
このアドレスから送られてきた未読メールを対象に情報を取得する。
未読を取得するには下記ようにするだけでOK。
function getUnreadNotificationMail() { const unReadThreads = GmailApp.search("from:noreply@google.com is:unread"); }
メールはHTML形式 & テンプレフォーマットで届くので、
必要な情報をごちゃごちゃして抜き出す。
抜き出すのは下記のように。
function getUnreadNotificationMail() { const unReadThreads = GmailApp.search("from:noreply@google.com is:unread"); unReadThreads.forEach(function(unReadThread, index, array) { const mails = unReadThread.getMessages(); mails.forEach(function(mail, index, array){ if (mail.isUnread()){ const items = createItems(mail); if (items != null) { // ここにSlackへの通知処理を記載する } } }); }); } function extractBody(body) { if (body.indexOf("しました") == -1) { // テンプレフォーマット以外は無視 return null; } //本文から必要な範囲を取り出す return body.substring(0,body.indexOf("しました") + "しました".length); } function createItems(mail) { const message = extractBody(mail.getBody()); if (message == null) { return null; } //通知に必要な情報をオブジェクトにまとめる const items = {}; // タイトルから変更ページ名を取得する。 // タイトルの頭にはサイト名が着いているのでそこは削除 items.text = mail.getSubject().substring("[サイト名称]".length) + "\r" + mail.getDate().toLocaleString(); // 更新者取得 items.author_name = message.substring(message.indexOf(">") + 1, message.indexOf("さん") + "さん".length); // 更新ページ名取得 items.title = message.substring(message.indexOf(">", 80) + 1, message.indexOf("<", 80)) + message.substring(message.lastIndexOf("を")); // 更新ページURL取得 items.title_link = message.substring(message.indexOf("http://sites.google.com/site/"), message.indexOf("\"", message.indexOf("http://sites.google.com/site/"))); return items; }
Slackに変更内容を通知する
toke取得
SlackAPIを使うのにtokenが必要となる。
ここからtoken取得しておく。
Slackに通知を飛ばす
Slackへの通知はAPIを使う。
プレーンテキストで送ることもできるが、見やすいようにちょっと見た目を変えて上げる。
function postNotice(items) { const token = "先程取得したトークン"; const channel = "チャンネル名"; const username = "通知するBOT名"; const attachments = JSON.stringify([ { color: "#89CEEB", //インデント線の色 author_name: items.author_name, //インデント内に表示される著者名 title: items.title ,//インデント内に表示されるタイトル title_link: items.title_link,//そのリンク text: "上記リンクをクリックすると対象のページやファイルを表示します。" //インデント内に表示されるテキスト } ]); const payload = { "channel" : channel, //通知先チャンネル名 "text" : items.text, //送信テキスト "username": username, //BOTの名前 "attachments": attachments //リッチなメッセージを送る用データ }; const option = { "method" : "POST", "payload" : payload }; return UrlFetchApp.fetch("https://slack.com/api/chat.postMessage?token=" + token, option); }
この関数をgetUnreadNotificationMail()
のコメント部で呼び出してあげれば完成。
まとめ
コードの最終形は下記の通り。
function getUnreadNotificationMail() { const unReadThreads = GmailApp.search("from:noreply@google.com is:unread"); unReadThreads.forEach(function(unReadThread, index, array) { const mails = unReadThread.getMessages(); mails.forEach(function(mail, index, array){ if (mail.isUnread()){ const items = createItems(mail); if (items != null) { // ここにSlackへの通知処理を記載する const result = JSON.parse(postNotice(items)); if (result.ok) { mail.markRead(); } } } }); }); } function extractBody(body) { if (body.indexOf("しました") == -1) { return null; } //本文から必要な範囲を取り出す return body.substring(0,body.indexOf("しました") + "しました".length); } function createItems(mail) { const message = extractBody(mail.getBody()); if (message == null) { return null; } //通知に必要な情報をオブジェクトにまとめる const items = {}; items.text = mail.getSubject().substring("[UbiquitousHome]".length) + "\r" + mail.getDate().toLocaleString(); //年/月/日 時:分:秒 JST形式になる items.author_name = message.substring(message.indexOf(">") + 1, message.indexOf("さん") + "さん".length); items.title = message.substring(message.indexOf(">", 80) + 1, message.indexOf("<", 80)) + message.substring(message.lastIndexOf("を")); items.title_link = message.substring(message.indexOf("http://sites.google.com/site/"), message.indexOf("\"", message.indexOf("http://sites.google.com/site/"))); return items; } function postNotice(items) { const token = "先程取得したトークン"; const channel = "チャンネル名"; const username = "通知するBOT名"; const attachments = JSON.stringify([ { color: "#89CEEB", //インデント線の色 author_name: items.author_name, //インデント内に表示される著者名 title: items.title ,//インデント内に表示されるタイトル title_link: items.title_link,//そのリンク text: "上記リンクをクリックすると対象のページやファイルを表示します。" //インデント内に表示されるテキスト } ]); const payload = { "channel" : channel, //通知先チャンネル名 "text" : items.text, //送信テキスト "username": username, //BOTの名前 "attachments": attachments //リッチなメッセージを送る用データ }; const option = { "method" : "POST", "payload" : payload }; return UrlFetchApp.fetch("https://slack.com/api/chat.postMessage?token=" + token, option); }
参考サイト
npm-scriptsを使ってみる
npm scriptsとは
package.jsonのscript
に書いてある下記のようなやつ。
(下のはnpm init
の初期値)
"scripts": { "test": "echo \"Error: no test specified\" && exit 1" }
事前準備
便利そうなやつがデフォルトで使えないから、先に入れておく。
# スクリプトの直列、並列実行をするために必要 npm install --save-dev npm-run-all # ファイルの監視をして何かをするために必要 npm install --save-dev watch # クロスプラットフォーム用(コピー,リムーブの代用) npm install --save-dev cpx npm install --save-dev rimraf
書けること
shell(bat),alias,scriptsに書いた自分以外のスクリプトが動かせる。
ターミナル(コマンドプロンプト)で動くものは基本的に動くと思われる。
使い方
基本的にはnpm run {script名}
でOK。
以下、scriptの例と実行結果サンプル。
"scripts": { "echo": "echo テスト" }
$ npm run echo > hoge@1.0.0 echo /test > echo テスト テスト
予約語
基本的には上記使い方で問題ないが、4種類の予約後が用意されている。
start
,stop
,restart
,test
の4つが予約語となっており、npm start
のようにrunを書かなくても実行できる。
"scripts": { "start": "echo run省略" }
$ npm start > hoge@1.0.0 start > echo run省略 run省略
クラスプラットフォーム向け
scriptはシェルなり、ターミナルコマンドなりを使うことができる。
package.json
に書くことはできるが当然ウインドウズでは動かないので、そこを解消する。
下記jsonのうち、cpx
はコピーコマンドの代替。
rimraf
はrm -rf
の代替となる。
※実行結果は元のコマンドと同じなので省略。
"scripts": { "copy": "cpx ./text.txt ./text2.txt", "clean": "rimraf ./dist" }
直列実行
2つのscriptsを直列実行したいときは、
run-s {コマンド1} {コマンド2}
でコマンド1の結果を待ってコマンド2を実行できる。
例えばビルドして結果をどこかにコピーしたいとき。
以下のようなスクリプトを用意しておくと救われる。
(例はAngular-cli利用した時のもの)
"script": { "all": "run-s build copy" , "build": "ng build" , "copy": "cpx ./dist/* ../server/" }
並列実行
2つの複数のスクリプトを走らせたいときは、
run-p {コマンド1} {コマンド2}
でコマンド1と2を同時に実行できる。
パッと用途が思いつかなかった。下記例だと、2種類のビルドを同時に実行している。
"script": { "parallel": "run-p build:tsc build:angular" , "build:tsc": "tsc test.ts" , "build:angular": "ng build" }
監視
ファイルの変更検知して自動でビルドしたいときは、
npm run watch {コマンド} {監視対象}
で監視対象の変更を検知してコマンドを実行できる。
使い方としては、開発中にホットデプロイのようなことをしたい場合。
ローカルで開発中にいちいちビルドと再起動を行わなくて済むので便利。
下記例はビルドからサーバにファイルを配置、サーバを起動ということをしている。
※フォルダ構成やファイル名は適当に記述
"script": { "watch": "watch \"npm run start:app\" ./src", "start:app": "run-s build boot:server", "boot:server": "nodemon ./app.js", "build": "run-s build:app copy", "build:app": "ng build", "copy": "cpx ./dist ../server/" }
まとめ
以上npm-scriptの簡単な説明でした。
nodeを使うアプリであれば、おそらくpackage.json
やnpm
はあると思うのでとっかりとしては始めやすい方だと思います。
タスクランナーとして使ってる方もいるみたいなので是非使ってみてはいかがでしょうか。
参考サイト
Angular6 + Reduxで簡単なToDoアプリを作る
はじめに
Reduxについて勉強するために、Redux + Angularで簡単なアプリを作成する。
(React知らないので、Angularでやります・・・)
作成したアプリはこちらから
Reduxとは
Facebookが提唱しているFluxと言うアーキテクチャの派生形。
Reactと使われているのが多いイメージだが、Reactに依存している訳では無いので他でも使用可能。
Reduxの構成要素
Action
Actionは何をするかと言う情報を持ったオブジェクト。
UIからStoreへ送るデータとなる。
そしてStoreのための唯一の情報源となる。
Actionは単なるJavascriptのオブジェクトである。
ただし、Actionのタイプを示すtype
プロパティを必ず持つ。
{ type: 'ADD_TODO' , text: 'Reduxについてまとめて記事を書く' };
ActionCreator
Actionを生成する関数のこと。
単純にActionを返す。
Actionと混同しやすいため、要注意。
function addTodo(text: string) { const newTodo: TodoAction = { type: 'ADD_TODO' , text: text }; return newTodo; }
State
Reduxでは、すべてのアプリケーションの状態は1つのオブジェクトとして保持される。
その状態を保持しているのがState。
{ todos: [ { text: 'Reduxについてまとめて記事を書く', completed: true, }, { text: '記事をアップロードする', completed: false } ] }
Reducer
ActionとStateをもとに新しいStateを作成して返す。
Reducerはピュアな関数であることが必須。
そのため、下記のことは厳禁。
※ピュアな関数:インプットが同じなら毎回必ず同じ結果が変える関数
* 引数に手を加える
* 副作用を起こす。例)APIコールやページ遷移
* 純粋ではない関数を呼び出す。 例)Date.now() や Math.random()
Reducerは必ずピュアな関数でなくてはいけないため、
引数が与えられると、次の状態を計算して返す。
public reducer(action: any, state: any) { switch (action['type']) { case 'ADD_TODO': const retState: Array<ITodoState> = state.concat(); retState.push({ text: action['text'] , completed: action['completed'] }); return retState; } }
Store
StoreはActionとReducerをまとめるオブジェクト。
下記のような役割を持つ。
* アプリケーションの状態を保持する
* getState()による状態へのアクセスを許可する
* dispatch(action)による状態更新を許可する
* subscribe(listener)によりリスナーを登録する
* subscribe(listener)で返される関数により、リスナーの登録解除を処理する
Storeはアプリケーション中に1つのみ。
複数のStoreを作ってはいけない。
Reduxの3大原則
Reduxは以下の3大原則に沿って設計されている。
1. Single source of truth
アプリケーション内でStoreは1つのみとし、Stateは単独のオブジェクトとしてStoreに保持される。
2. State is read-only
Stateを直接変更することはできず、actionをStoreへdispatchすることでしかStateは変更できない。
3. Mutations are written as pure functions
Stateを変更する関数(Reducer)はpureな関数にする。
まとめ
Reduxの勉強のために1から?簡単なTODOアプリを作ってみた。
(追加しか出来ない上に、どこにも保存されていませんが……)
ちなみに、Angularにも便利なライブラリがあるらしく、1から作る必要は特になかった……
そのうち、上のライブラリを使ってちゃんと作ろうと思います。
参考にさせて頂いたサイト
SlackBotにDocomo雑談対話APIを組み込む
はじめに
SlackBotと会話するのが夢だったので、実現します。
前提
雑談対話APIの準備
今回は、Docomo雑談対話APIを使う。
事前に色々登録を済ませてAPIキーを取得しておく。
申請に時間が〜みたいな事書いてあるけど、すぐ帰ってくるはず。
登録したらAPIを使うためにAppIDが必要なので、ここを参考に事前に取得しておく。
PostmanでもCurlでもPOST飛ばせれば何でもいいと思われる。
準備が出来たらようやくプログラムが作れる。
Botに組み込む
とりあえず全部に反応してくれれば問題ないので、@default_reply
で指定しとく。
ソースは以下を参照。
# -*- coding: utf-8 -*- import requests import json from slackbot.bot import default_reply import base64 # エンドポイントの設定 KEY = '取得したAPIキー' endpoint = 'https://api.apigw.smt.docomo.ne.jp/naturalChatting/v1/dialogue?APIKEY=REGISTER_KEY' url = endpoint.replace('REGISTER_KEY', KEY) appid = '取得したAppId' # requestパラメータ # voiceTextがAPIに投げる会話文になる # language,botIdはこれで固定 payload = {'language': 'ja-JP', 'botId': 'Chatting', 'appId': appid, 'voiceText': '', 'clientData': {'option': {'mode': ''}}} # レスポンス data = { 'systemText': { 'expression': '', 'utterance': '' }} HEADERS = {'Content-type': 'application/json'} @default_reply() def chat(message): global payload global data payload['voiceText'] = message.body['text'] try: # 送信 r = requests.post(url, data=json.dumps(payload), headers=HEADERS) data = r.json() # jsonの解析 response = data['systemText']['expression'] # command取り出し # しりとりのときはモードを'srtr'に設定するため、レスポンスからモードを受け取る # base64でエンコードされてるので、デコードしてリクエストに詰める command = json.loads(base64.b64decode(data['command']).decode('utf-8')) payload['clientData']['option']['mode'] = command['mode'] except Exceptin as e: # エラーだとスタックトレースが出るので、一応ハンドリングしておく message.reply('は?何言ってんの?') # 表示 message.reply(response)
話しかけて雑談してくれればOK。
まぁ、上辺の会話は出来てそう・・・?
最後に
会話の精度はそこそこないイメージ。
簡単な雑談くらいなら問題ないと思います。
的はずれな会話はご愛嬌というこで。