-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
first version of mattermost and matrix backends
- Loading branch information
Guilhem Saurel
committed
Sep 14, 2017
1 parent
49b0922
commit 9eec6ee
Showing
5 changed files
with
279 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
# !/usr/bin/python | ||
"""This file contains the class 'BotMatrix' which is a bot for Matrix Chan""" | ||
|
||
import logging | ||
import threading | ||
import time | ||
|
||
from matrix_client.client import MatrixClient | ||
|
||
from pipobot.bot import PipoBot | ||
|
||
logger = logging.getLogger('pipobot.bot_matrix') | ||
|
||
|
||
class BotMatrix(PipoBot): | ||
"""The implementation of a bot for Matrix Chan""" | ||
|
||
def __init__(self, login, passwd, chan, modules, session, name='pipobot', address="https://matrix.org"): | ||
logger.info("Connecting to %s", address) | ||
self.client = MatrixClient(address) | ||
logger.debug("login in") | ||
token = self.client.login_with_password(username=login, password=passwd) | ||
if token: | ||
logger.debug("logged in") | ||
else: | ||
logger.error("login failed") | ||
self.room = self.client.join_room(chan) | ||
self.room.add_listener(self.on_message) | ||
logger.debug("connected to %s", self.room) | ||
if name: | ||
logger.debug("set name to %s", name) | ||
self.client.get_user(self.client.user_id).set_display_name(name) | ||
self.name = name | ||
|
||
super(BotMatrix, self).__init__(name, login, chan, modules, session) | ||
|
||
logger.debug("start listener thread") | ||
self.client.start_listener_thread() | ||
self.say(_("Hi there")) | ||
logger.info("init done") | ||
|
||
def on_message(self, room, event): | ||
logger.debug("new event") | ||
if event['type'] == 'm.room.message': | ||
logger.debug("event is a message") | ||
self.message_handler(event) | ||
elif event['type'] == 'm.room.member': | ||
logger.debug("event is a presence") | ||
self.presence_handler(event) | ||
|
||
def message_handler(self, event): | ||
"""Method called when the bot receives a message""" | ||
# We ignore messages in some cases : | ||
# - the bot is muted | ||
# - the message is empty | ||
if self.mute or event["content"]["body"] == "": | ||
return | ||
|
||
thread = threading.Thread(target=self.answer, args=(event,)) | ||
thread.start() | ||
|
||
def answer(self, mess): | ||
logger.debug('handling message') | ||
result = self.module_answer(mess) | ||
if type(result) is list: | ||
for to_send in result: | ||
self.say(to_send) | ||
else: | ||
self.say(result) | ||
logger.debug('handled message') | ||
|
||
def kill(self): | ||
"""Method used to kill the bot""" | ||
|
||
# The bot says goodbye | ||
self.say(_("I’ve been asked to leave you")) | ||
# The bot leaves the room | ||
self.client.logout() | ||
self.stop_modules() | ||
logger.info('killed') | ||
|
||
def say(self, msg, priv=None, in_reply_to=None): | ||
"""The method to call to make the bot sending messages""" | ||
# If the bot has not been disabled | ||
logger.debug('say %s', msg) | ||
if not self.mute: | ||
if type(msg) is str or type(msg) is unicode: | ||
self.room.send_text(msg) | ||
elif type(msg) is list: | ||
|
||
for line in msg: | ||
time.sleep(0.3) | ||
self.room.send_text(line) | ||
elif type(msg) is dict: | ||
if "users" in msg: | ||
pass | ||
else: | ||
if "xhtml" in mess: | ||
mess_xhtml = mess["xhtml"] | ||
mess_xhtml = "<p>%s</p>" % mess_xhtml | ||
if type(mess_xhtml) is unicode: | ||
mess_xhtml = mess_xhtml.encode("utf-8") | ||
self.room.send_html(mess_xhtml, msg["text"]) | ||
else: | ||
self.room.send_text(msg["text"]) | ||
|
||
def presence_handler(self, mess): | ||
"""Method called when the bot receives a presence message. | ||
Used to record users in the room, as well as their jid and roles""" | ||
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
# !/usr/bin/python | ||
"""This file contains the class 'BotMattermost' which is a bot for Mattermost API""" | ||
|
||
import logging | ||
import threading | ||
import time | ||
from json import loads, dumps | ||
from websocket import create_connection | ||
import requests | ||
|
||
from pipobot.bot import PipoBot | ||
|
||
logger = logging.getLogger('pipobot.bot_mattermost') | ||
|
||
|
||
class MattermostException(Exception): | ||
""" For errors due to Mattermost (conflict, connection/authentification failed, …) """ | ||
pass | ||
|
||
|
||
class BotMattermost(PipoBot): | ||
"""The implementation of a bot for a Mattermost instance""" | ||
|
||
def __init__(self, login, passwd, modules, session, address, default_team, default_channel): | ||
address += '/api/v4' | ||
self.address = address | ||
auth = requests.post('https://%s/users/login' % address, json={'login_id': login, 'password': passwd}) | ||
|
||
if auth.status_code != 200: | ||
logger.error(_("Unable to connect !")) | ||
raise MattermostException(_("Unable to connect !")) | ||
|
||
self.headers = {'Authorization': 'Bearer %s' % auth.headers['Token']} | ||
self.user_id = requests.get('https://%s/users/me' % address, headers=self.headers).json()['id'] | ||
team_url = 'https://%s/teams/name/%s' % (address, default_team) | ||
self.default_team_id = requests.get(team_url, headers=self.headers).json()['id'] | ||
channel_url = 'https://%s/teams/%s/channels/name/%s' % (address, self.default_team_id, default_channel) | ||
self.default_channel_id = requests.get(channel_url, headers=self.headers).json()['id'] | ||
|
||
challenge = dumps({"seq": 1, "action": "authentication_challenge", "data": {'token': auth.headers['Token']}}) | ||
logger.debug('creating WS') | ||
self.ws = create_connection('wss://%s/websocket' % address) | ||
logger.debug('Sending challenge') | ||
self.ws.send(challenge) | ||
|
||
if not self.ws.connected: | ||
logger.error(_("Unable to authenticate websocket !")) | ||
raise MattermostException(_("Unable to authenticate websocket !")) | ||
|
||
super(BotMattermost, self).__init__('...', login, default_channel, modules, session) | ||
|
||
self.thread = threading.Thread(name='mattermost_' + default_channel, target=self.process) | ||
self.thread.start() | ||
self.say(_("Hi there")) | ||
|
||
def process(self): | ||
self.run = True | ||
while self.run: | ||
msg = loads(self.ws.recv()) | ||
self.message_handler(msg) | ||
|
||
|
||
def message_handler(self, msg): | ||
"""Method called when the bot receives a message""" | ||
if self.mute or 'event' not in msg or msg['event'] != 'posted': | ||
return | ||
|
||
thread = threading.Thread(target=self.answer, args=(msg,)) | ||
thread.start() | ||
|
||
def answer(self, mess): | ||
post = loads(mess['data']['post']) | ||
message = {'body': post['message'], 'from': DummySender(mess['data']['sender_name']), 'type': 'chat'} | ||
result = self.module_answer(message) | ||
kwargs = {'root_id': post['parent_id'], 'channel_id': post['channel_id']} | ||
if type(result) is list: | ||
for to_send in result: | ||
self.say(to_send, **kwargs) | ||
elif type(result) is dict: | ||
to_send = result['text'] if 'text' in result else result['xhtml'] | ||
if 'monospace' in result and result['monospace']: | ||
to_send = '`%s`' % to_send | ||
self.say(to_send, **kwargs) | ||
else: | ||
self.say(result, **kwargs) | ||
|
||
def kill(self): | ||
"""Method used to kill the bot""" | ||
|
||
self.say(_("I’ve been asked to leave you")) | ||
self.run = False | ||
self.ws.close() | ||
self.stop_modules() | ||
|
||
def say(self, msg, channel_id=None, root_id=""): | ||
"""The method to call to make the bot sending messages""" | ||
# If the bot has not been disabled | ||
if not self.mute: | ||
if channel_id is None: | ||
channel_id = self.default_channel_id | ||
create_at = int(time.time() * 1000) | ||
r = requests.post('https://%s/posts' % self.address, headers=self.headers, json={ | ||
'channel_id': channel_id, | ||
'message': msg, | ||
'root_id': root_id | ||
}).json() | ||
if 'status_code' in r: | ||
logger.error(_('error in sent message %s:\nresult is: %s' % (msg, r))) | ||
|
||
|
||
class DummySender(object): | ||
def __init__(self, sender): | ||
self.resource = sender |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters