Skip to content

Slack integration #6

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ see requirements.txt
- pyyaml
- sleekxmpp
- dnspython (optional, for dns plugin)
- slackclient (optional, for slack plugin)

installation
----
Expand Down
28 changes: 23 additions & 5 deletions creep.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import sleekxmpp
import logging
import inspect
from slack import Slack
from plugins import Plugin
from threading import Timer

Expand All @@ -15,6 +16,8 @@ class Creep():

def __init__(self, config):
logging.basicConfig(level=logging.INFO)

self.slack = Slack(self, config) if "slack" in config.keys() else None
self.config = config
self.muted_rooms = set()
self.xmpp = sleekxmpp.ClientXMPP(
Expand All @@ -31,7 +34,7 @@ def __init__(self, config):
else:
self.xmpp.connect()
logging.info("Connected")

self.xmpp.process()

self.xmpp.add_event_handler("session_start", self.handle_connected)
Expand All @@ -41,7 +44,10 @@ def __init__(self, config):
self.plugins = []
if 'plugins' in config:
self._load_plugins(config['plugins'], config)


if self.slack:
self.slack.start()

def handle_connected(self, flap):
self.xmpp.send_presence()
logging.info("Started processing")
Expand All @@ -51,7 +57,10 @@ def handle_connected(self, flap):
wait=True)
logging.info("Connected to chat room '%s'" % room)

def handle_message(self, message):
def handle_message(self, message, sender=None, user=None):
if isinstance(sender, Slack):
return self.__handle_message(message, user)

body = message['body']
if not self.from_us(message):
if not message.get_mucroom():
Expand All @@ -62,7 +71,7 @@ def handle_message(self, message):
message.reply(reply).send()

logging.debug('Handled request "%s"' % body)

def mute(self, room, timeout=10):
def unmute_room():
self.unmute(room)
Expand All @@ -77,6 +86,14 @@ def unmute(self, room):
mbody="I'm back baby!",
mtype='groupchat')

def send_slack_message(self, message):
if self.slack:
self.slack.send_message(message)

def delete_slack_message(self, quote_id):
if self.slack:
self.slack.delete_message(quote_id)

def __handle_message(self, body, origin):
command = body.split(' ')[0] if ' ' in body else body
params = body[body.find(" ")+1:] if ' ' in body else None
Expand All @@ -102,11 +119,12 @@ def from_us(self, message):
return False

def shutdown(self):
self.slack.shutdown()
for plugin in self.plugins:
plugin.shutdown()

self.xmpp.disconnect(wait=True)

def _load_plugins(self, names, config):
for name in names:
try:
Expand Down
16 changes: 13 additions & 3 deletions plugins/quotes.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,37 @@
import sqlite3
from threading import Lock


class Quotes(Plugin):

provides = ['aq', 'iq', 'q', 'sq', 'lq', 'dq']

def __init__(self, creep, config=None):
self.__initialize_db()
self.lock = Lock()
self.creep = creep
if 'admins' in config:
self.admins = config['admins']
else:
self.admins = []

def aq(self, message=None, origin=None):
'''Add a quote. For example: "aq this is my quote"'''
quote_id=None
with self.lock:
cursor = self.db.cursor()
query = 'insert into quotes (content) values (?)'
result = cursor.execute(query, [message])
quote_id = result.lastrowid
cursor.close()
self.db.commit()

return 'inserted quote \'%s\'' % quote_id


if self.creep:
quote = str("%d - %s" % (quote_id, self.iq(quote_id)))
if not quote.startswith('invalid quote_id:'):
self.creep.send_slack_message(quote)

return 'inserted quote \'%s\'' % quote_id

def iq(self, message=None, origin=None):
'''Query for a quote. For example: "iq 123"'''
Expand Down Expand Up @@ -115,6 +122,9 @@ def dq(self, message=None, origin=None):
cursor.execute(query, [quote_id])
cursor.close()
self.db.commit()

if self.creep:
self.creep.delete_slack_message(quote_id)

return "'%s' deleted" % quote_id
except ValueError:
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
PyYAML
dnspython
sleekxmpp
slackclient
145 changes: 145 additions & 0 deletions slack.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import time
import json
import logging
import errno
from ssl import SSLError
from slackclient import SlackClient
from threading import Thread
from random import random

class Slack():

def __init__(self, creep, config, loglevel=logging.INFO):
logging.basicConfig(level=loglevel)
self.creep = creep
self.token = config["slack"]["token"] if 'slack' in config.keys() and 'token' in config["slack"].keys() else None
self.client = SlackClient(self.token)
self.channel = config["slack"]["channel"] if 'slack' in config.keys() and 'channel' in config["slack"].keys() else None
self.channel_id = None
self.user_id = None
self.connected = False
self.connect(config)

def shutdown(self, shutup=False):
logging.debug("Going back to sleep, ZzzzZzzzz...")
self.keep_running = False
if not shutup:
self.send_message("Catch you on the flip side!")

def start(self):
self.keep_running = True
self.thread = Thread(target=self._run)
self.thread.start()
logging.debug("Started slack service integration")

def _run(self):
while self.keep_running and self.connected:
message = self.client.rtm_read()
if message:
self.read_message(message)

def _set_channel(self, config):
if self.connected and 'slack' in config.keys() and 'channel' in config["slack"].keys():
channel_info = json.loads(self.client.api_call("channels.list"))
channel = filter(lambda c: self.channel and c["name"] == self.channel, channel_info["channels"])
if channel:
self.channel_id = channel[0]["id"]
logging.debug("Channel id set to '%s'" % self.user_id)
return True
else:
logging.exception("Channel '" + config["slack"]["channel"] + "' does not exists yet or you are not a member!")
else:
logging.info("Slack client not connected.")
return False

def _set_user_id(self):
if self.connected:
user = json.loads(self.client.api_call("auth.test", token=self.token))
if user:
self.user_id = user["user_id"]
logging.debug("User id set to '%s'" % self.user_id)
return True
else:
logging.exception("Unable to locate user.")
else:
logging.info("Slack client not connected.")
return False

def connect(self, config):
logging.info("Slack client joining channel '%s'" % str(self.channel))
if self.client.rtm_connect():
logging.info("Slack client connected")
self.connected = True
if self._set_channel(config) and self._set_user_id() and self.channel_id:
self.client.rtm_send_message(self.channel_id, lines[int(random()*len(lines))])
return True
else:
self.connected = False
logging.exception(("Connection Failed, invalid token: %s" % str(self.token)))
return False

def send_message(self, message, channel=None):
if not self.channel_id:
return None

if not channel:
channel = self.channel_id

sc = SlackClient(self.token)
if sc.rtm_connect():
sc.rtm_send_message(channel, message)
logging.debug("%s| %s" % (channel, message))

def _get_user_by_id(self, user_id=None):
result = json.loads(self.client.api_call("users.list", token=self.token))
if 'members' in result.keys() and result["members"]:
user = filter(lambda u: 'id' in u.keys() and u["id"] == user_id, result["members"])
if user and user[0] and 'profile' in user[0].keys() and 'email' in user[0]["profile"].keys():
return user[0]["profile"]["email"]
return None

def read_message(self, message):
m = message[0]
if self.user_id and set(["type", "text", "user"])<=set(m.keys()) and m["type"]=="message" and m["user"] != self.user_id:
if m["text"].startswith("<@%s>" % str(self.user_id)):
self.send_message(self._highlight())
elif m["channel"][0]=="D": # direct message
channel = m["channel"]
response = self.creep.handle_message(m["text"], self, self._get_user_by_id(m["user"]))
self.send_message(response, channel)
else:
logging.debug("message ignored: %s" % message)

def delete_message(self, quote_id):
message = self._search_message(quote_id)
if not message:
logging.info("Could not find message %d" % quote_id)
return False

if not 'ts' in message.keys():
logging.info("invalid message format: %s" % str(message))
return False

result = json.loads(self.client.api_call("chat.delete", token=self.token, channel=self.channel_id, ts=message["ts"]))
result_status = result and 'ok' in result.keys() and result["ok"]
logging.info("Message %d delete status: %s" % (quote_id, result_status))

def _search_message(self, quote_id=None):
response = json.loads(self.client.api_call("channels.history", token=self.token, channel=self.channel_id))
if 'messages' in response.keys():
message = filter(lambda m: 'type' in m.keys() and m["type"] == "message" and 'text' in m.keys() and m["text"].split(" ", 1)[0]==str("%d" % quote_id), response["messages"])
if message:
return message[0]
return None

def _highlight(self):
return "Want to use my awesome quoting functionality? DM me!"

def __str__(self):
return 'slack'

lines = [
"Back in the house!",
"Respect for the man with the ice cream van!",
"It's nice to be important, but it's more important to be nice!"
]