『アッオー』でおなじみICQからIRC,Yahooメッセンジャー,HipChat,Slack,GoogleTalkの共通点は、すべて通信にXMLベースのXMPPプロトコルを利用していることです。インスタントメッセージサービスは目まぐるしく移り変わっていくため、変わらないコアの部分を抑えることが重要だと思います。
XMPPプロトコルを喋ると、大半のメッセージサービスを利用できるBotが作れます。この記事ではXMPPを開発・公開したJabber社のライブラリを利用して複数のメッセージサービスに対応したBotを実装していきます。
作るもの
起動したらHipChatとSlackに両方ログインしていて、話かけたらdeployしたりコマンド実行したりするBot。海外はSlack、国内はHipChatを使う特殊事例が発生して開発することになりました。過渡期つらい 新しいものを、どんどん取り入れていくことは良いことだと思います。
2分で判るXMPPプロトコルの概要
■ rfc3920で仕様公開されている。
■ Jabber社が開発したXMLベースの通信プロトコル
■ 接続に必要な情報は次の5点セット
Server: <stream>
<message>...</message>
Client: <stream>
<message>...</message>
Server: <message>...</message>
Client: <message>...</message>
Server: <message>...</message>
Client: <message>...</message>
Server: </stream>
Client: </stream>
■ Slackで利用するには、AdminでXMPP GateWayを有効にして各アカウントで払い出しする
■ HipChatでは、デフォルト設定のままで利用可能。
XMPPを作ったのはJabber社
Jabberのライブラリjabberbot
を使うと簡単にXMPPを喋るBotが実装できます。
install
pip install jabberbot
pip install xmpppy
pip install lazy-reload
pip install requests
pip install simplejson
SlackとHipChatに対応したBot
# -*- coding: utf-8 -*-
from __future__ import absolute_import, unicode_literals
import sys
import time
import traceback
import logging
from jabberbot import botcmd, JabberBot, xmpp
import multiprocessing as mp
class ChatBot(JabberBot):
"""
XMPP/Jabber botを利用してHipChatのメッセージサービスに接続する
"""
_content_commands = {}
_global_commands = []
_command_aliases = {}
_all_msg_handlers = []
_last_message = ''
_last_send_time = time.time()
_restart = False
def __init__(self, config):
self._config = config
channel = config['connection']['channel']
username = u"%s@%s" % (config['connection']['username'],
config['connection'].get('host',
config['connection']['host']))
self._username = username
super(ChatBot, self).__init__(
username=username,
password=config['connection']['password'])
self.PING_FREQUENCY = 50 # timeout sec
self.join_room(channel, config['connection']['nickname'])
self.log.setLevel(logging.INFO)
def join_room(self, room, username=None, password=None):
"""
ChatRoomにjoinする
"""
NS_MUC = 'http://jabber.org/protocol/muc'
if username is None:
username = self._username.split('@')[0]
my_room_JID = u'/'.join((room, username))
pres = xmpp.Presence(to=my_room_JID)
if password is not None:
pres.setTag(
'x', namespace=NS_MUC).setTagData('password', password)
else:
pres.setTag('x', namespace=NS_MUC)
# Join時にメッセージ履歴を読まない
pres.getTag('x').addChild('history', {'maxchars': '0',
'maxstanzas': '0'})
self.connect().send(pres)
def callback_message(self, conn, mess):
"""
メッセージを受信すると実行される
"""
_type = mess.getType()
jid = mess.getFrom()
props = mess.getProperties()
text = mess.getBody()
username = self.get_sender_username(mess)
print "callback_message:{}".format(text)
print _type
# print jid
print props
print username
super(ChatBot, self).callback_message(conn, mess)
# 問い合わせに特定文字列が入ってたら応答する
import time
import random
time.sleep(1)
if '願' in text:
ret = ["りょ。ちょいまってねー",
"しばしお待ちを",
"http://rr.img.naver.jp/mig?src=http%3A%2F%2Fimgcc.naver.jp%2Fkaze%2Fmission%2FUSER%2F20141001%2F66%2F6169106%2F58%2F186x211xe15ffb8d1f6f246446c89d7e.jpg%2F300%2F600&twidth=300&theight=600&qlt=80&res_format=jpg&op=r",
"http://e-village.main.jp/gazou/image_gazou/gazou_0053.jpeg"]
self.send_simple_reply(mess, random.choice(ret))
if 'バグ' in text:
ret = ["了解で後で直しておきます",
"認識しました",
"このあとの会議終わってから対応します。",
"最新版だと直っているので反映しておきます。"]
self.send_simple_reply(mess, random.choice(ret))
def send_message(self, mess):
"""Send an XMPP message
Overridden from jabberbot to update _last_send_time
"""
self._last_send_time = time.time()
self.connect().send(mess)
def bot_start(conf):
print "++++++++"
print conf
print "++++++++"
bot = ChatBot(conf)
bot.serve_forever()
class ChatDaemon(object):
config = None
def run(self):
try:
# Start Slack Bot
process_slack = mp.Process(target=bot_start, args=(self.config_slack,))
process_slack.start()
# Start HipChat Bot
process_hipchat = mp.Process(target=bot_start, args=(self.config_hipchat,))
process_hipchat.start()
except Exception, e:
print >> sys.stderr, "ERROR: %s" % (e,)
print >> sys.stderr, traceback.format_exc()
return 1
else:
return 0
def main():
import logging
logging.basicConfig()
config_slack = {
'connection': {
'username': '{{name}}',
'password': '{{password}}',
'nickname': '{{name}}',
'host': '{{TeamName}}.xmpp.slack.com',
'channel': '{{RoomName}}@conference.{{TeamName}}.xmpp.slack.com',
}
}
config_hipchat = {
'connection': {
'username': '{{name}}',
'password': '{{password}}',
'nickname': '{{nickname}}',
'host': 'chat.hipchat.com',
'channel': '{{RoomName}}@conf.hipchat.com',
}
}
runner = ChatDaemon()
runner.config_slack = config_slack
runner.config_hipchat = config_hipchat
runner.run()
if __name__ == '__main__':
sys.exit(main())
起動するとBotがログインする
HipChatとSlackから話かけてみた
メッセージを受け取れた
設定情報の払い出し
■ 1.Slack
SLACKはadminでXMPPのGateway有効にしないと駄目
https://{{TeamName}}.slack.com/account/gateways
■ 2.HipChat
https://{{組織名}}.hipchat.com/rooms/show/{{room_id}}
参考
rfc3920
XMPP(Jabberのプロトコル)技術メモ
一般ユーザー向けXMPP入門ページ(仮)
まとめ
もし新しくインスタントメッセージサービスを導入検討する際は、XMPPプロトコルに対応しているかの観点で確認するとエンジニアが幸せになるかもしれません。
callback_message関数
にos.subprocess('deploy cmd hogehoge')
を書いていけば、どんなコマンドでも実装出来ると思いますが、既製品のHuBotとか使った方が幸せになれると思います。
chat応対業務はBotに任せてコード書くんだ╭( ・ㅂ・)و