def slack_thread(): print "in spawned slack thread" sc = SlackClient(SLACK_TOKEN) # Join all channels chans = json.loads(sc.api_call("channels.list")) chans = chans['channels'] for chan in chans: sc.api_call("channels.join", name=chan['name']) if sc.rtm_connect(): while True: msgs = sc.rtm_read() print "Got msgs == " + str(msgs) for msg in msgs: if msg.get('ok') is not None: print "Server acknowledged our message: " + str(msg) elif msg.get('type') is not None: if msg['type'] == 'hello': print "Server said hello: " + str(msg) elif msg['type'] == 'channel_created': print "Channel created: " + str(msg) print sc.api_call("channels.join", name=msg['channel']['name']) elif msg['type'] == 'message': print "Got a message: " + str(msg) sc.rtm_send_message(msg['channel'], str(msg)) else: print "Got RTM message with unknown type field: " + str(msg) else: print "Unknown message format in RTM: " + str(msg) gevent.sleep(1) else: print "Connection Failed, invalid token?"
class SlackThread(threading.Thread): def __init__(self, queue, config): super(SlackThread, self).__init__() self.daemon = True self.queue = queue self._config = config self.conn = None def run(self): try: print 'Connecting to slack' self.conn = SlackClient(self._config['token']) if not self.conn.rtm_connect(): return self.parseLoginData(self.conn.server.login_data) print 'Connected to slack' self.conn.server.websocket.sock.setblocking(True) while True: for event in self.conn.rtm_read(): self.queue.put({'type': 'slack.event', 'data': event}) except Exception as e: print 'SLACK RUNLOOP ERROR: %s' % e self.conn = None def parseLoginData(self, loginData): for user in loginData.get('users', []): if user.get('deleted', False): continue if user.get('is_bot', False): continue self.queue.put({'type': 'slack.join', 'data': {'id': user['id'], 'name': user['name']}})
class SlackROS(): # Must have __init__(self) function for a class, similar to a C++ class constructor. def __init__(self): # Get the ~private namespace parameters from command line or launch file. self.token = rospy.get_param('~token', 'xoxp-123456789') self.channel = rospy.get_param('~channel', 'G1234567Q') self.username = rospy.get_param('~username', 'ros-bot') # Create a publisher for our custom message. pub = rospy.Publisher('from_slack_to_ros', String, queue_size=10) rospy.Subscriber("from_ros_to_slack", String, self.callback) # Create the slack client self.sc = SlackClient(self.token) if self.sc.rtm_connect(): # Main while loop. while not rospy.is_shutdown(): for reply in self.sc.rtm_read(): if "type" in reply and "user" in reply: #print reply if reply["type"] == "message" and reply["channel"] == self.channel: pub.publish(reply["text"]) # Sleep for a while before publishing new messages. Division is so rate != period. rospy.sleep(2.0) def callback(self, data): self.sc.api_call( "chat.postMessage", channel=self.channel, text=data.data, username=self.username, icon_emoji=':robot_face:' )
def marcopolo_bot(): # Insert API token from Slack # My token was disabled as soon as I uploaded it to Git, another token must be generated token = 'xoxb-55268990450-WzfkM0A5ufihTTElZ8k6sC33' # Creating the slackclient object/instance sc = SlackClient(token) # Connect to Slack if sc.rtm_connect(): while True: # Read latest messages as long as connected to Slack rtm_responses = sc.rtm_read() # Check every instance of potential response and check for text response for rtm_response in rtm_responses: if 'text' in rtm_response: # If response is found as 'marco', call API function to respond with 'polo' if rtm_response['text'] == 'marco': sc.api_call( "chat.postMessage", channel="marco-polo", text="polo", username='******', icon_emoji=':robot_face:' ) time.sleep(1) else: quit()
def main(self): token = os.environ.get("SLACK_TOKEN") slack_client = SlackClient(token) if slack_client.rtm_connect(): while True: new_evts = slack_client.rtm_read() for evt in new_evts: #print evt if 'type' in evt: if str(evt["type"]) == "message" and "text" in evt: # this is where the logic for the human input text is placed. # we also get more information from the JSON keyword = True channel = evt['channel'] response = evt['text'] print response elif 'reply_to' in evt: #this is where the logic for the chat bot is placed. slack_client.rtm_send_message('This is where the messages will go', 'Hello World') else: print "Failed."
def bot(): try: slack_client = SlackClient(token=config["slack"]["token"]) slack_client.rtm_connect() bot_info = json.loads(slack_client.api_call("auth.test").decode("utf-8")) last_ping = 0 cache_emoji_list(slack_client) while True: last_ping = autoping(slack_client, last_ping) process_queued_responses(slack_client) for event in slack_client.rtm_read(): print(event) event_type = event.get("type") if event_type == "message": process_message_event(slack_client, bot_info, event) time.sleep(0.1) except KeyboardInterrupt: sys.exit(0)
class RtmBot(object): def __init__(self, token): self.last_ping = 0 self.token = token self.bot_plugins = [] self.slack_client = None self.exit = False def connect(self): """Convenience method that creates Server instance""" self.slack_client = SlackClient(self.token) self.slack_client.rtm_connect() def start(self): self.connect() self.load_plugins() while True and not self.exit: for reply in self.slack_client.rtm_read(): self.input(reply) self.crons() self.output() self.autoping() time.sleep(.1) def autoping(self): #hardcode the interval to 3 seconds now = int(time.time()) if now > self.last_ping + 3: self.slack_client.server.ping() self.last_ping = now def input(self, data): if "type" in data: function_name = "process_" + data["type"] dbg("got {}".format(function_name)) for plugin in self.bot_plugins: plugin.register_jobs() plugin.do(function_name, data) def output(self): for plugin in self.bot_plugins: if plugin.name == 'robbie': self.exit = plugin.exit limiter = False for output in plugin.do_output(): channel = self.slack_client.server.channels.find(output[0]) if channel != None and output[1] != None: if limiter == True: time.sleep(.1) limiter = False message = output[1].encode('utf-8','ignore') channel.send_message("{}".format(message)) limiter = True def crons(self): for plugin in self.bot_plugins: plugin.do_jobs() def load_plugins(self): for plugin in glob.glob(directory+'/plugins/*'): sys.path.insert(0, plugin) sys.path.insert(0, directory+'/plugins/') for plugin in glob.glob(directory+'/plugins/*.py') + glob.glob(directory+'/plugins/*/*.py'): logging.info(plugin) name = plugin.split('/')[-1][:-3] # try: self.bot_plugins.append(Plugin(name))
class TachikomaFrontend(pykka.ThreadingActor, CoreListener): def new_slack_client(self): self.sc = SlackClient(self.slackToken) if not self.sc.rtm_connect(): raise Exception("Bad Slack token?") logger.info("New Slack client started") def __init__(self, config, core): super(TachikomaFrontend, self).__init__() self.daemon = True self.slackToken = config['tachikoma']['slack_token'], self.core = core self.new_slack_client() thread.start_new_thread(self.doSlack, ()) def doSlackRead(self, last_track_told): try: items = self.sc.rtm_read() except Exception, e: logger.info("Exception from Slack: %r", e) time.sleep(1) self.new_slack_client() return last_track_told logger.debug("info %r", items) if items != []: try: current_track = self.core.playback.get_current_track().get(3) except pykka.Timeout, e: logger.warning("Failure to get current track: %r", e) current_track = None return self.doSlackLoop(last_track_told, current_track, items)
class Robot(object): def __init__(self): self.client = SlackClient(SLACK_TOKEN) # self.brain = RedisBrain() self.apps, self.docs = self.load_apps() def load_apps(self): docs = ['='*14, '생협봇 사용방법\n(원하시는 기능이 있을경우 건의하시면 추가해드립니다!)', '='*14] apps = {} for name in APPS: app = import_module('apps.%s' % name) if name != 'system': docs.append( ' !%s: %s' % (', '.join(app.run.commands), app.run.__doc__) ) for command in app.run.commands: apps[command] = app return apps, docs def handle_messages(self, messages): for channel, text, user in messages: command, payloads = self.extract_command(text) if not command: continue app = self.apps.get(command, None) if not app: continue pool.apply_async(func=app.run, args=(self, channel, payloads, user)) def extract_messages(self, events): messages = [] for e in events: user = e.get('user','') channel = e.get('channel', '') text = e.get('text', '') if channel and text: messages.append((channel, text, user)) return messages def extract_command(self, text): if CMD_PREFIX != text[0]: return (None, None) tokens = text.split(' ', 1) if 1 < len(tokens): return tokens[0][1:], tokens[1] else: return (text[1:], '') def run(self): if self.client.rtm_connect(): while True: events = self.client.rtm_read() if events: messages = self.extract_messages(events) self.handle_messages(messages) gevent.sleep(0.3)
class SlackMediator(object): def __init__(self, token): self.token = token self.slack_client = SlackClient(self.token) self.time_keeper = _TimeKeeper() def time_keeping(self): self.time_keeper.reset() return iter(self.time_keeper) def wait(self): self.time_keeper.wait() def read(self): return self.slack_client.rtm_read() def send(self, output, wait_itr): channel_id, message = output channel = self.find_channel(channel_id) if channel is not None and message is not None: next(wait_itr) channel.send_message(message) def ping(self): return self.slack_client.server.ping() def find_channel(self, name): return self.slack_client.server.channels.find(name) def connect(self): """Convenience method that creates Server instance""" logger.info("connect to slack ..") self.slack_client.rtm_connect()
def main(args): global sc for i in range(NUM_WORKERS): t = threading.Thread(target=worker) t.daemon = True t.start() for n in range(NUM_TRY): sc = SlackClient(TOKEN) if sc.rtm_connect(): while True: try: records = sc.rtm_read() except: print "接続が切断されました。再接続します。試行回数: %d" % (n + 1) break for record in records: if "file" in record: fileinfo = record["file"]["id"] filedata = sc.api_call("files.info", file=fileinfo) if fileinfo not in memo: q.put(filedata["file"]) memo.append(fileinfo) time.sleep(WAIT_TIME) else: print "接続に失敗しました。TOKENが間違っていませんか?" time.sleep(60)
def slack(): '''Slack rtm reader started in seprate thread''' logs.write("In slack function in new thread", 'working') sc = SlackClient(token) if sc.rtm_connect(): logs.write("Connected to rtm socket", 'success') while True: time.sleep(0.1) #Get message from rtm socket message=sc.rtm_read() #If the message isn't empty if message!=[]: #If the message is text as opposed to a notification. Eventually plan to have other kinds of messages in a backend communications channel. if message[0].keys()[0]=='text': command=message[0].values()[0] logs.write(command,'working') #The commands are json or plain text. If it isn't a json backend command, interpret it as a "normal" command try: command=json.loads(command) except ValueError: command=[{'type':'command'},{'devices':'all'},{'action':"{0}".format(command)}] #Json slack commands or management can eventually be formatted like so: [{"type":"management/command",{"devices":"all/mobile/desktop/network/device name"},{"action":"message content"}] #Not sure if I want to do that in the backend or command channel or what really, but I'm definitely working with it. commandtype=command[0] devices=command[1] action=command[2] #Replace thisdevicename with whatever you want to name yours in the W.I.L.L slack network (obviously) if devices.values()[0]=='all' or devices.values()[0]=="thisdevicename": logs.write("Checking local W.I.L.L server", 'trying') #Hit W.I.L.L with the command. This is also where you could add exceptions or easter eggs answer=requests.get('http://127.0.0.1:5000/?context=command&command={0}'.format(action.values()[0])).text print sc.api_call( "chat.postMessage", channel="#w_i_l_l", text="{0}".format(answer), username='******') else: logs.write("Connection Failed, invalid token?", 'error')
def slack(): logs.write("In slack function in new thread", 'working') sc = SlackClient(token) if sc.rtm_connect(): logs.write("Connected to rtm socket", 'success') while True: time.sleep(0.1) message=sc.rtm_read() if message!=[]: if message[0].keys()[0]=='text': command=message[0].values()[0] logs.write(command,'working') try: command=json.loads(command) except ValueError: command=[{'type':'command'},{'devices':'all'},{'action':"{0}".format(command)}] commandtype=command[0] devices=command[1] action=command[2] if devices.values()[0]=='all' or devices.values()[0]=="XPS": logs.write("Checking local W.I.L.L server", 'trying') answer=requests.get('http://127.0.0.1:5000/?context=command&command={0}'.format(action.values()[0])).text print sc.api_call( "chat.postMessage", channel="#w_i_l_l", text="{0}".format(answer), username='******') else: logs.write("Connection Failed, invalid token?", 'error')
class RtmBot(object): def __init__(self, token): self.last_ping = 0 self.token = token self.bot_plugins = [] self.slack_client = None def connect(self): """Convenience method that creates Server instance""" self.slack_client = SlackClient(self.token) self.slack_client.rtm_connect() def start(self): self.connect() self.load_plugins() while True: for reply in self.slack_client.rtm_read(): self.input(reply) self.crons() self.output() self.autoping() time.sleep(.1) def autoping(self): #hardcode the interval to 3 seconds now = int(time.time()) if now > self.last_ping + 3: self.slack_client.server.ping() self.last_ping = now def input(self, data): if "type" in data: function_name = "process_" + data["type"] dbg("got {}".format(function_name)) for plugin in self.bot_plugins: plugin.register_jobs() plugin.do(function_name, data) def output(self): for plugin in self.bot_plugins: limiter = False for output in plugin.do_output(): channel = self.slack_client.server.channels.find(output[0]) if channel is not None and output[1] is not None: if limiter is True: time.sleep(.1) limiter = False message = output[1].encode('utf-8') channel.send_message("{}".format(message.decode('utf-8'))) limiter = True def crons(self): for plugin in self.bot_plugins: plugin.do_jobs() def load_plugins(self): plugin_dir = os.path.join(directory, 'plugins') for plugin in glob.glob(os.path.join(plugin_dir, '*')): sys.path.insert(0, plugin) sys.path.insert(0, plugin_dir) for plugin in glob.glob(os.path.join(plugin_dir, '*.py')) + glob.glob(os.path.join(plugin_dir, '*', '*.py')): logging.info(plugin) name = plugin.split(os.sep)[-1][:-3] try: self.bot_plugins.append(Plugin(name)) except: print("error loading plugin {}".format(name))
def main(): sc = SlackClient(BOT_TOKEN) if sc.rtm_connect(): # print sc.server.channels bot_id = sc.server.users.find(bot).id while True: data = sc.rtm_read() if data != []: print data[0] if "type" in data[0] and data[0]["type"] == "message" and "subtype" not in data[0]: if data[0]["user"] != bot_id: text = data[0]["text"] channel_id = data[0]["channel"] channel = sc.server.channels.find(channel_id) if phrase in text: try: test = text.replace(phrase, "") result = Parser().parse(test) sc.rtm_send_message(channel.name,"Rolling a '%s' and got a result of %s"%(test,result.sum)) #TODO: Support message level saving such that a user could track history of roll except Exception as e: sc.rtm_send_message(channel.name,"ROLL FAILED: %s"%e.message) time.sleep(1) else: print "Connection Failed, invalid token?"
def main(): sc = SlackClient(SLACK_TOKEN) rt = RecognitionTracker() if sc.rtm_connect(): while True: messages = sc.rtm_read() for message in messages: message_to_write = None # It only watches the channels it's in, but to be extra # careful here. channel = message.get("channel") if not channel or channel not in CHANNEL_INFO.keys(): continue # The first time you get here after 10AM on a new day, print # out some random results. current_date = (datetime.datetime.now() - datetime.timedelta(hours=17)).date() if current_date != rt.current_date: # Write the daily message to every channel for channel_key in CHANNEL_INFO.iterkeys(): possible_message_to_write = rt.get_daily(channel_key) if possible_message_to_write: sc.api_call( "chat.postMessage", channel=channel_key, username=CHANNEL_INFO[channel_key]['name'], icon_emoji=CHANNEL_INFO[channel_key]['emoji'], attachments=possible_message_to_write ) rt.current_date = current_date command_type = get_command_type(message) if command_type == "thanks": message_to_write = rt.give_thanks(message['text'], message['user'], channel) elif command_type == "summary": message_to_write = rt.get_summary(channel) elif command_type == "help": message_to_write = rt.get_help(channel) elif command_type == "today": message_to_write = rt.get_daily(channel, override=True) if message_to_write: sc.api_call("chat.postMessage", channel=channel, username=CHANNEL_INFO[channel]['name'], icon_emoji=CHANNEL_INFO[channel]['emoji'], attachments=message_to_write) time.sleep(1) else: print "Oh noes, the sadness commences!"
class RtmBot(object): def __init__(self): self.last_ping = 0 self.token = settings.SLACK_TOKEN self.bot_plugins = [] self.slack_client = None def connect(self): """Convenience method that creates Server instance""" self.slack_client = SlackClient(self.token) self.slack_client.rtm_connect() def start(self): self.connect() self.load_plugins() while True: for reply in self.slack_client.rtm_read(): self.input(reply) self.crons() self.output() self.autoping() time.sleep(.1) def autoping(self): # hardcode the interval to 3 seconds now = int(time.time()) if now > self.last_ping + 3: self.slack_client.server.ping() self.last_ping = now def input(self, data): if "type" in data: function_name = "process_" + data["type"] if function_name != "process_pong": dbg("got {}".format(function_name)) for plugin in self.bot_plugins: plugin.register_jobs() plugin.do(function_name, data) def output(self): for plugin in self.bot_plugins: limiter = False for output in plugin.do_output(): channel = self.slack_client.server.channels.find(output[0]) if channel is not None and output[1] is not None: if limiter: time.sleep(.1) limiter = False message = output[1].encode('ascii', 'ignore') channel.send_message("{}".format(message)) limiter = True def crons(self): for plugin in self.bot_plugins: plugin.do_jobs() def load_plugins(self): for plugin in settings.RTM_PLUGINS: self.bot_plugins.append(Plugin(plugin))
def main(): # read the api key into variable with open('key.txt', 'r') as key_file: bot_token = key_file.read().replace('\n', '') # init bot token and officers from google sheets sc = SlackClient(bot_token) # TODO: Dynamically find the bot's id using users.list # could cause problem if this changes for some reason bot_id = "U0H7GEEJW" # counts up after every sleep(1) # so we can poll when counter reachs 60 or 1 min counter = 0 # connect to the bots feed if sc.rtm_connect(): while True: # read events from peterbot's feed try: events = sc.rtm_read() # in the event that it throws an error just set it # to an empty list and continue except Exception, e: # print to add to log sys.stderr.write(e) events = [] for e in events: user_input = "" message = "" # format the input text if e.get("text"): user_input = e.get("text").lower().strip() # return a message based on the user's input message = "Currently Under Maintainence (Lab is Open as of 10:21am)" # if there is a message to send, then send it # will not respond if received from bot message to prevent # looping conversation with itself if message and e.get("user") != bot_id: chan_id = e.get("channel") sc.api_call("chat.postMessage", as_user="******", channel=chan_id, text=message) # delay time.sleep(1) counter += 1 # run script to build up statistics if counter >= 60: counter = 0
class RtmBot(object): def __init__(self, token): self.token = token self.bot_plugins = [] self.slack_client = None def connect(self): ''' Creates server instance ''' self.slack_client = SlackClient(self.token) self.slack_client.rtm_connect() def start(self): self.connect() self.load_plugins() while True: for reply in self.slack_client.rtm_read(): self.input(reply) self.crons() self.output() time.sleep(.1) def input(self, data): if "type" in data: function_name = "process_" + data["type"] dbg("got {}".format(function_name)) for plugin in self.bot_plugins: print plugin, 'registering jobs' plugin.register_jobs() plugin.do(function_name, data) def output(self): for plugin in self.bot_plugins: limiter = False for output in plugin.do_output(): channel = self.slack_client.server.channels.find(output[0]) if channel != None and output[1] != None: if limiter == True: time.sleep(1) limiter = False message = output[1].encode('ascii','ignore') channel.send_message("{}".format(message)) limiter = True def crons(self): for plugin in self.bot_plugins: plugin.do_jobs() def load_plugins(self): for plugin in glob.glob(directory+'/plugins/*'): sys.path.insert(0, plugin) sys.path.insert(0, directory+'/plugins/') for plugin in glob.glob(directory+'/plugins/*.py') + glob.glob(directory+'/plugins/*/*.py'): logging.info(plugin) name = plugin.split('/')[-1][:-3] # try: self.bot_plugins.append(Plugin(name))
class PeckBot(object): def __init__(self, token, timeout): print('[INFO] Initializing bot...') self.token = token self.client = SlackClient(token) self.timeout = timeout self.userid = 'U04U7K4HC' self.responses = [ ('^!reddit ([a-zA-Z0-9]+)', RedditBehavior()), ('^!chat (.*)$', CleverBotBehavior()), ('^!doc$', DocBehavior()), ('^!quote ?(.*)$', QuoteBehavior()), ('^!trivia ?(.*)$', TriviaBehavior()), ('(.+)', NonsensSpewingBehavior()), ('.+', NonsensTrainingBehavior()) ] print('[INFO] Init done.') def connect(self): return self.client.rtm_connect() def send_msg(self, msg, channel): self.client.rtm_send_message(channel, msg) self.pause() def pause(self): time.sleep(self.timeout) def run(self): print('[INFO] PeckBot is running') while True: events = self.client.rtm_read() for event in events: if 'type' in event and event['type'] == 'message': if 'user' in event and event['user'] != self.userid: self.respond(event) self.pause() def respond(self, event): for resp in self.responses: regex, behavior = resp matches = re.findall(regex, event['text']) try: channel_name = json.loads(self.client.api_call('channels.info', channel=event['channel']))['channel']['name'] except KeyError: channel_name = event['channel'] for match in matches: print('[INFO] Triggered {0} on #{1}'.format(behavior.name, channel_name)) try: behavior.execute(self, match, event) except Exception as e: print('[ERROR] {0} failed: {1}'.format(behavior.name, e)) if len(matches) > 0: break
class SlackHandle(threading.Thread): # bot user id __BOT_ID = os.environ.get('BOT_ID') # slack bot token generated from the slack app and integration settings __SLACK_BOT_TOKEN = os.environ.get('SLACK_BOT_TOKEN') def __init__(self, command_queue = None): super(SlackHandle, self).__init__() self.__command_queue = command_queue self.stop_request = threading.Event() if self.__BOT_ID: self.__AT_BOT = "<@" + self.__BOT_ID + ">" else: raise Exception('Please define Bot Id in environment.') if self.__SLACK_BOT_TOKEN: self.__slack_client = SlackClient(self.__SLACK_BOT_TOKEN) else: raise Exception('Please define Slack Bot Token in environment.') def run(self): # try to connect to slack via rtm if self.__slack_client.rtm_connect(): print('Slack bot started and connected') while not self.stop_request.isSet(): # fetch changes and parse rtm json command, channel = self.__parse_slack_output(self.__slack_client.rtm_read()) if command and channel: commandQ = {'command' : command, 'reciever' : { 'handle' : self, 'params' : { 'channel' : channel } } } # print(commandQ) # Queue the commend to be processed by command listner and a command handler execute the command self.__command_queue.put(commandQ) time.sleep(1) else: # there was an error connecting to slack print('Slack bot could not connect.') raise Exception('Slack bot could not connect.') def join(self, timeout = None): self.stop_request.set() super(SlackHandle, self).join(timeout) # Parse the slack rtm output and fetch the text and channel info from the json def __parse_slack_output(self, slack_rtm_output): if slack_rtm_output and len(slack_rtm_output) > 0: for output in slack_rtm_output: if output and 'text' in output and self.__AT_BOT in output['text']: return output['text'].split(self.__AT_BOT)[1].strip().lower(), output['channel'] return None, None # method is called by reply processor to send message back to user def handle(self, message = None, params = {}): if message and len(message) > 0 and 'channel' in params: self.__slack_client.api_call("chat.postMessage", channel= params['channel'], text=message, as_user=True)
class SlackBot(object): def __init__(self): self.config = config self.api = Slacker(self.config.token) self.client = SlackClient(self.config.token) self.plugins = [] self.user_map = {} self.channel_map = {} self.load_plugins() def run(self): self.update_maps() self.client.rtm_connect() while True: for event in self.client.rtm_read(): self.combine_maps(event) for plugin in self.plugins: plugin.process_event(event) time.sleep(1) # in subclass, post with name/icon def post_message(self, channel, message): self.api.chat.post_message(channel, message) def update_maps(self): for user in self.api.users.list().body['members']: self.user_map[user['id']] = user for channel in self.api.channels.list().body['channels']: self.channel_map[channel['id']] = channel for group in self.api.groups.list().body['groups']: self.channel_map[group['id']] = group def combine_maps(self, event): user_id = event.get('user') if user_id is not None: user = self.user_map.get(user_id) if user is not None: event['user_dict'] = user channel_id = event.get('channel') if channel_id is not None: channel = self.channel_map.get(channel_id) if channel is not None: event['channel_dict'] = channel # for override def plugin_classes(self): return [ EventTypePlugin, PrintPlugin, EchoPlugin, ] def load_plugins(self): for klass in self.plugin_classes(): self.plugins.append(klass(self))
class Slack(Channel): def __init__(self, token): self.client = SlackClient(token) self.rooms = {} self.reconnect() self.ignore = self.client.server.users.find(self.client.server.username).id def reconnect(self): return self.client.rtm_connect() def join(self, roomname): channel = self.client.server.channels.find(roomname) if not channel: return room = self.rooms[channel.id] = SlackRoom(self, channel) return room def alive(self): if not self.client.server.websocket.connected: self.reconnect() return self.client.server.websocket.connected def fetch_messages(self): while True: msgs = self.client.rtm_read() if not len(msgs): break for msg in msgs: if not msg.get('type'): continue if msg['type'] == 'hello': continue if msg['type'] == 'presence_change': continue if msg['type'] == 'reconnect_url': continue if msg['type'] == 'user_typing': continue if msg['type'] == 'message': room = self.rooms.get(msg['channel']) if not room: continue #print 'DEBUG_SLACK_RECV_MSG', msg user = msg.get('user') or msg.get('username') or \ msg.get('comment', {}).get('user') or \ msg.get('bot_id') or '_' if user == self.ignore: continue room.append_message(user, msg.get('text', ''), msg) else: print 'UNKNOWN_SLACK_RECV_MSG', msg def close(self): pass
def main(): token = "token" sc = SlackClient(token) if sc.rtm_connect(): while True: print(sc.rtm_read()) time.sleep(1) channel = sc.server.channels.find('channel') channel.send_message('test') else: print("Connection failed, invalid token?")
class Bot(object): def __init__(self, token, bot_user): self.last_ping = 0 self.token = token self.slack_client = None self.pub = pub self.slack = Slacker(token) self.bot_user = bot_user def connect(self): self.slack_client = SlackClient(self.token) return self.slack_client.rtm_connect() def run(self): # connect the slack_client and create a websocket connection isConnected = self.connect() if isConnected: while True: # while True we keep reading from slack every 0.3 secs for messages # for each payload in the array of payloads sent we dispatch a message # which is the type of the payload and the payload itself # any listener gets updated and can perform any action based on the payload for payload in self.slack_client.rtm_read(): print(payload) if 'subtype' in payload: print('we got a msg from a bot please lets see if we wanna handle bot-bot convo') else: self.dispatchMessage(payload['type'],payload) time.sleep(1) else: # should throw an exception here print("Connection closed") # register a callback to listen to changes def registerListener(self, callback, obj_type): self.pub.subscribe(callback, obj_type) # dispatches changes def dispatchMessage(self, obj_type, payload): self.pub.sendMessage(obj_type, payload=payload) def sendChannelMsg(self, msg_dict): channel = msg_dict['channel'] msg = msg_dict['text'] attachments = msg_dict['attachments'] self.slack.chat.post_message(channel=channel, text=msg, username=self.bot_user, attachments=attachments, icon_url="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSzEdaAJwSCfB5K5F6FrASKwAk7ukLdWkWddVuy0ew3_iDqcU_0") def listenForMsg(self, payload): print("just got a message") self.sendChannelMsg(payload)
def main(): sc = SlackClient(token) if sc.rtm_connect(): channel = sc.server.channels.find("squares") while True: response = parse_input(sc.rtm_read()) for msg in response or []: channel.send_message(msg) time.sleep(1) else: print("Connection Failed, invalid token?")
class Jarvis(object): def __init__(self, directory, token): self.last_ping = 0 self.slack_client = SlackClient(token) self.slack_client.rtm_connect() for plugin in glob.glob(directory + '/plugins/*'): sys.path.insert(0, plugin) sys.path.insert(0, directory + '/plugins/') self.plugins = [] for plugin in glob.glob(directory + '/plugins/*.py') + \ glob.glob(directory + '/plugins/*/*.py'): name = plugin.split('/')[-1][:-3] self.plugins.append(Plugin(name)) logger.debug('Done initializing.') def input(self, data): # TODO: fix naive 'text fragment' in data['text'] if 'type' in data and 'text' in data: data['text'] = data['text'].lower() if not data['text'].startswith('jarvis'): return function_name = 'process_' + data['type'] for plugin in self.plugins: plugin.input(function_name, data) def keepalive(self): now = int(time.time()) if now > self.last_ping + 3: self.slack_client.server.ping() self.last_ping = now def output(self): for plugin in self.plugins: for output in plugin.output(): channel = self.slack_client.server.channels.find(output[0]) if channel and output[1]: message = output[1].encode('ascii', 'ignore') channel.send_message('{}'.format(message)) def run(self): while True: for reply in self.slack_client.rtm_read(): self.input(reply) self.output() self.keepalive() time.sleep(.1)
class Bot(object): def __init__(self, config_file): config = json.load(open(config_file)) self.chatbot = ChatBot( 'Robot Overload', io_adapter='chatterbot.adapters.io.NoOutputAdapter', database=config['database']) self.chatbot.train('chatterbot.corpus.english') self.chatbot.train('chatterbot.corpus.english.greetings') self.chatbot.train('chatterbot.corpus.english.conversations') self.token = config['token'] self.bot_id = config['bot_id'] self.slack_client = None def connect(self): self.slack_client = SlackClient(self.token) self.slack_client.rtm_connect() self.last_ping = 0 def start(self): self.connect() while True: for reply in self.slack_client.rtm_read(): self.input(reply) self.autoping() time.sleep(.1) def input(self, data): text = data.get('text', '') if data.get('type') == 'message' and self.bot_id in text: channel = data['channel'] text = text.replace(self.bot_id, '') response = self.chatbot.get_response(text) self.output([channel, response]) def output(self, output): limiter = False channel = self.slack_client.server.channels.find(output[0]) if channel != None and output[1] != None: if limiter == True: time.sleep(.1) limiter = False message = output[1].encode('ascii','ignore') channel.send_message("{}".format(message)) limiter = True def autoping(self): now = int(time.time()) if now > self.last_ping + 3: self.slack_client.server.ping() self.last_ping = now
def handle(self, *args, **options): team = Team.objects.first() client = SlackClient(team.bot_access_token) if client.rtm_connect(): while True: events = client.rtm_read() print("%s----%s" % (team, events)) for event in events: if 'type' in event and event['type'] == 'message' and event['text'] == 'hi': client.rtm_send_message(event['channel'], "hello world") elif 'type' in event and event['type'] == 'message' and event['text'] == 'who': client.rtm_send_message(event['channel'], "I'am Groot") time.sleep(1)
class Gendo(_PackageBoundObject): def __init__(self, import_name, slack_token, channel): _PackageBoundObject.__init__(self, import_name) self.listeners = [] self.client = SlackClient(slack_token) self.channel = channel self.sleep = 0.5 def listen_for(self, rule, **options): def decorator(f): self.add_listener(rule, f, **options) return f return decorator def run(self): if self.client.rtm_connect(): while True: time.sleep(self.sleep) try: data = self.client.rtm_read() if data and data[0].get('type') == 'message': user = data[0].get('user') message = data[0].get('text') self.respond(user, message) except (KeyboardInterrupt, SystemExit): print "Shutting down..." break def respond(self, user, message): if not message: return for phrase, view_func, options in self.listeners: if phrase in message.lower(): response = view_func(user, message, **options) if response: self.speak(response) def add_listener(self, rule, view_func=None, **options): self.listeners.append((rule, view_func, options)) def speak(self, message): self.client.api_call("chat.postMessage", as_user="******", channel=self.channel, text=message) def get_user_info(self, user_id): user = self.client.api_call('users.info', user=user_id) return json.loads(user) def get_user_name(self, user_id): user = self.get_user_info(user_id) return user.get('user', {}).get('name')
cursor.executemany( 'INSERT INTO messages VALUES(?, ?, ?, ?)', [(event['text'], event['user'], event['channel'], event['ts'])]) conn.commit() logger.debug("--------------------------") # Loop if sc.rtm_connect(auto_reconnect=True): update_users() update_channels() logger.info('Archive bot online. Messages will now be recorded...') while sc.server.connected is True: try: for event in sc.rtm_read(): if event['type'] == 'message': handle_message(event) if 'subtype' in event and event['subtype'] in [ 'group_leave' ]: update_channels() elif event['type'] in [ 'group_joined', 'member_joined_channel', 'channel_created', 'group_left' ]: update_channels() except WebSocketConnectionClosedException: sc.rtm_connect() except: logger.error(traceback.format_exc())
if __name__ == '__main__': card_trigger = "!card" card_offset = len(card_trigger) + 1 rule_trigger = "!rule" rule_offset = len(rule_trigger) + 1 refresh_trigger = "!refresh" slack_token = os.environ.get('SLACK_BOT_TOKEN') sc = SlackClient(slack_token) while True: try: if sc.rtm_connect(): populate_cards() print 'Successfully connected' while True: msgs = sc.rtm_read() for msg in msgs: response = None txt = msg.get('text') if msg.get('text' ) is not None and card_trigger in msg.get( 'text'): response = handle_card(txt) if txt is not None and rule_trigger in msg.get('text'): response = handle_rule(txt) if txt is not None and refresh_trigger in msg.get( 'text'): populate_cards() sc.api_call('chat.postMessage', channel=msg.get('channel'), text="Refreshed DB")
import time from slackclient import SlackClient token = "your token here" # found at https://api.slack.com/web#authentication sc = SlackClient(token) if sc.rtm_connect(): while True: result = sc.rtm_read() #print result for item in result: print item if (item["type"] == "message") and (item["user"] == "U1AT0FNF6"): #rhi newMessage = "*" + item["text"] + "*" print sc.api_call("chat.postMessage", channel=item["channel"], text=newMessage, as_user="******") time.sleep(1) else: print "Connection Failed, invalid token?"
" 秒", ts=timeStamp, as_user='******') time.sleep(2.0) sc.api_call("chat.postMessage", channel=_targetChannel, text="Time up!!", ts=timeStamp, as_user='******') if sc.rtm_connect(): while True: time.sleep(1) stream = sc.rtm_read() print(stream) if len(stream) > 0 and 'type' in stream[0] and 'text' in stream[0]: inputSplit = stream[0]['text'].split() print(inputSplit) if stream[0]['type'] == 'message' and inputSplit[ 0] == '<@' + BOTID + '>': targetChannel = stream[0]['channel'] if inputSplit[1].isdigit(): finishTime = int(inputSplit[1]) print('timer start') timer(targetChannel, finishTime) else: post = sc.api_call('chat.postMessage', channel=targetChannel, text='書き方 \n`@timer 秒数`',
""" item_id = utils.find_id(text, keywords.TASK_ITEM) if item_id is None: return error_reply(channel) item = oboy.list_by_id(item_id) answer = prompt(channel, response.delete_confirm(item)) if answer in keywords.ANSWER_POSITIVE: oboy.delete_item(item_id) bot.rtm_send_message(channel, response.delete_success()) else: bot.rtm_send_message(channel, response.delete_regret()) def error_reply(channel): """Sends an error message to the user asking it to try again. :param channel: The channel ID of which to respond to. :type channel: str """ bot.rtm_send_message(channel, response.error_retry()) if __name__ == "__main__": if bot.rtm_connect(): while True: event = bot.rtm_read() if utils.is_user_message(event, BOT_ID): process_entry(event) time.sleep(1)
text=message, username=hashed_user.hexdigest()[:10]) def parse_slack_output(slack_rtm_output): """ The Slack Real Time Messaging API is an events firehose. this parsing function returns None unless a message is directed at the Bot, based on its ID. """ output_list = slack_rtm_output if output_list and len(output_list) > 0: for output in output_list: if output and 'channel' in output and 'text' in output: print output['channel'] if output['channel'][0] == "D": return output['text'], output['user'] return None, None if __name__ == "__main__": READ_WEBSOCKET_DELAY = 1 # 1 second delay between reading from firehose if slack_client.rtm_connect(): while True: message, user = parse_slack_output(slack_client.rtm_read()) if message and user: anon_message(message, user) time.sleep(READ_WEBSOCKET_DELAY) else: print("Connection failed. Invalid Slack token or bot ID?")
def create_default_message(attachments): attachments.append({ "title": "Too dumb", "text": "I'm not smart enough to understand that. Try something with lunch", "fallback": "That's not a command" }) if __name__ == "__main__": bot_id = get_bot_id() if bot_id is not None: if slack_client.rtm_connect(): print("lunchbot connected and running!") while True: try: command, channel, user = parse_slack_output( slack_client.rtm_read(), bot_id) # F**k it, gotta catch 'em all except Exception as e: print( "Caught an exception while trying to read:\nReconnecting...", e) slack_client.rtm_connect() if command and channel and user: handle_command(command, channel, user) time.sleep(1) else: print("Connection failed. Invalid Slack token or bot ID?")
class SlackBot: """ Slack bot base class. Includes lots of useful functionality that bots often require, such as messaging, connection management, and interfacing with APIs. This class is compatible with the [January 2017 release of the Slack API](https://api.slack.com/changelog). This class is intended to be subclassed, with the `on_step` and `on_message` methods overridden to do more useful things. """ def __init__(self, token, logger=None): assert isinstance(token, str), "`token` must be a valid Slack API token" assert logger is None or not isinstance( logger, logging.Logger), "`logger` must be `None` or a logging function" self.client = SlackClient(token) if logger is None: self.logger = logging.getLogger(self.__class__.__name__) else: self.logger = logger # cached versions of methods self.get_user_info_by_id_cached = lru_cache(maxsize=256)( self.get_user_info_by_id) # incoming message fields self.unprocessed_incoming_messages = deque( ) # store unprocessed messages to allow message peeking # outgoing message fields self.max_message_id = 1 # every message sent over RTM needs a unique positive integer ID - this should technically be handled by the Slack library, but that's broken as of now self.last_say_time = 0 # store last message send timestamp to rate limit sending self.bot_user_id = None # ID of this bot user def on_step(self): self.logger.info("step handler called") def on_message(self, message_dict): self.logger.info( "message handler called with message {}".format(message_dict)) def start_loop(self): while True: try: self.start() # start the main loop except KeyboardInterrupt: break except Exception: self.logger.error("main loop threw exception:\n{}".format( traceback.format_exc())) self.logger.info("restarting in 5 seconds...") time.sleep(5) self.logger.info("shutting down...") def retrieve_unprocessed_incoming_messages(self): result = list( self.unprocessed_incoming_messages) + self.client.rtm_read() self.unprocessed_incoming_messages.clear() return result def peek_unprocessed_incoming_messages(self): self.unprocessed_incoming_messages.extend(self.client.rtm_read()) return list(self.unprocessed_incoming_messages) def peek_new_messages(self): new_messages = self.client.rtm_read() self.unprocessed_incoming_messages.extend(new_messages) return list(new_messages) def start(self): # connect to the Slack Realtime Messaging API self.logger.info("connecting to Slack realtime messaging API...") if not self.client.rtm_connect(): raise ConnectionError( "Could not connect to Slack realtime messaging API (possibly a bad token or network issue)" ) self.logger.info("connected to Slack realtime messaging API") # obtain the bot credentials authentication = self.client.api_call("auth.test") assert authentication["ok"], "Could not authenticate with Slack API" self.bot_user_id = authentication["user_id"] last_ping = time.monotonic() while True: # call all the step callbacks try: self.on_step() except Exception: self.logger.error( "step processing threw exception:\n{}".format( traceback.format_exc())) # call all the message callbacks for each newly received message for message_dict in self.retrieve_unprocessed_incoming_messages(): try: self.on_message(message_dict) except KeyboardInterrupt: raise except Exception: self.logger.error( "message processing threw exception:\n{}\n\nmessage contents:\n{}" .format(traceback.format_exc(), message_dict)) # ping the server periodically to make sure our connection is kept alive if time.monotonic() - last_ping > 5: self.client.server.ping() last_ping = time.monotonic() # delay to avoid checking the socket too often time.sleep(0.01) def say(self, sendable_text, *, channel_id, thread_id=None): """Say `sendable_text` in the channel with ID `channel_id`, returning the message ID (unique within each `SlackBot` instance).""" assert self.get_channel_name_by_id( channel_id ) is not None, "`channel_id` must be a valid channel ID rather than \"{}\"".format( channel_id) assert isinstance( thread_id, str ) or thread_id is None, "`thread_id` must be a valid Slack timestamp or None, rather than \"{}\"".format( thread_id) assert isinstance( sendable_text, str), "`text` must be a string rather than \"{}\"".format( sendable_text) # rate limit sending to 1 per second, since that's the Slack API limit current_time = time.monotonic() if current_time - self.last_say_time < 1: time.sleep(max(0, 1 - (current_time - self.last_say_time))) self.last_say_time += 1 else: self.last_say_time = current_time self.logger.info("sending message to channel {}: {}".format( self.get_channel_name_by_id(channel_id), sendable_text)) # the correct method to use here is `rtm_send_message`, but it's technically broken since it doesn't send the message ID so we're going to do this properly ourselves # the message ID allows us to correlate messages with message responses, letting us ensure that messages are actually delivered properly # see the "Sending messages" heading at https://api.slack.com/rtm for more details message_id = self.max_message_id self.max_message_id += 1 if thread_id is not None: # message in a thread self.client.server.send_to_websocket({ "id": message_id, "type": "message", "channel": channel_id, "text": sendable_text, "thread_ts": thread_id, }) else: # top-level message self.client.server.send_to_websocket({ "id": message_id, "type": "message", "channel": channel_id, "text": sendable_text, }) return message_id def say_complete(self, sendable_text, *, channel_id, thread_id=None, timeout=5): """Say `sendable_text` in the channel with ID `channel_id`, waiting for the message to finish sending (raising a `TimeoutError` if this takes more than `timeout` seconds), returning the message timestamp.""" assert float( timeout ) > 0, "`timeout` must be a positive number rather than \"{}\"".format( timeout) message_id = self.say(sendable_text, channel_id=channel_id, thread_id=thread_id) message_timestamp = None start_time = time.monotonic() while message_timestamp is None and time.monotonic( ) - start_time < timeout: # peek at new messages to see if the response is written for message_dict in self.peek_new_messages(): if "ok" in message_dict and message_dict.get( "reply_to" ) == message_id: # received reply for the sent message if not message_dict["ok"]: raise ValueError("Message sending error: {}".format( message_dict.get("error", {}).get("msg"))) assert isinstance( message_dict.get("ts"), str), "Invalid message timestamp: {}".format( message_dict.get("ts")) message_timestamp = message_dict["ts"] break else: time.sleep(0.01) if message_timestamp is None: raise TimeoutError("Message sending timed out") return message_timestamp def react(self, channel_id, timestamp, emoticon): """React with `emoticon` to the message with timestamp `timestamp` in channel with ID `channel_id`.""" assert self.get_channel_name_by_id( channel_id ) is not None, "`channel_id` must be a valid channel ID rather than \"{}\"".format( channel_id) assert isinstance( timestamp, str), "`timestamp` must be a string rather than \"{}\"".format( timestamp) assert isinstance( emoticon, str), "`emoticon` must be a string rather than \"{}\"".format( emoticon) emoticon = emoticon.strip(":") self.logger.info( "adding reaction :{}: to message with timestamp {} in channel {}". format(emoticon, timestamp, self.get_channel_name_by_id(channel_id))) response = self.client.api_call("reactions.add", name=emoticon, channel=channel_id, timestamp=timestamp) assert response.get("ok"), "Reaction addition failed: error {}".format( response.get("error")) def unreact(self, channel_id, timestamp, emoticon): """React with `emoticon` to the message with timestamp `timestamp` in channel with ID `channel_id`.""" assert self.get_channel_name_by_id( channel_id ) is not None, "`channel_id` must be a valid channel ID rather than \"{}\"".format( channel_id) assert isinstance( timestamp, str), "`timestamp` must be a string rather than \"{}\"".format( sendable_text) assert isinstance( emoticon, str), "`emoticon` must be a string rather than \"{}\"".format( sendable_text) emoticon = emoticon.strip(":") self.logger.info( "removing reaction :{}: to message with timestamp {} in channel {}" .format(emoticon, timestamp, self.get_channel_name_by_id(channel_id))) response = self.client.api_call("reactions.remove", name=emoticon, channel=channel_id, timestamp=timestamp) assert response.get("ok"), "Reaction removal failed: error {}".format( response.get("error")) def get_channel_name_by_id(self, channel_id): """Returns the name of the channel with ID `channel_id`, or `None` if there are no channels with that ID. Channels include public channels, direct messages with other users, and private groups.""" assert isinstance( channel_id, str ), "`channel_id` must be a valid channel ID rather than \"{}\"".format( channel_id) for entry in self.client.server.channels: if entry.id == channel_id: return entry.name return None def get_channel_id_by_name(self, channel_name): """Returns the ID of the channel with name `channel_name`, or `None` if there are no channels with that name. Channels include public channels, direct messages with other users, and private groups.""" assert isinstance( channel_name, str ), "`channel_name` must be a valid channel name rather than \"{}\"".format( channel_name) channel_name = channel_name.strip().lstrip("#") # check for channel reference (these are formatted like `<#CHANNEL_ID>` or `<#CHANNEL_ID|CHANNEL_NAME>`) match = re.match(r"<#(\w+)(?:\|[^>]+)?>$", channel_name) if match: return match.group(1) # search by channel name for entry in self.client.server.channels: if entry.name == channel_name: return entry.id return None def get_user_name_by_id(self, user_id): """Returns the username of the user with ID `user_id`, or `None` if there are no users with that ID.""" assert isinstance( user_id, str ), "`user_id` must be a valid user ID rather than \"{}\"".format( user_id) for key, entry in self.client.server.users.items(): if entry.id == user_id: return entry.name return None def get_user_id_by_name(self, user_name): """Returns the ID of the user with username `user_name`, or `None` if there are no users with that username.""" assert isinstance( user_name, str ), "`user_name` must be a valid username rather than \"{}\"".format( user_name) user_name = user_name.strip().lstrip("@") # check for user reference (these are formatted like `<@USER_ID>` or `<@USER_ID|USER_NAME>`) match = re.match(r"^<@(\w+)(?:\|[^>]+)?>$", user_name) if match: return match.group(1) # search by user name for key, entry in self.client.server.users.items(): if entry.name == user_name: return entry.id # search by user real name for key, entry in self.client.server.users.items(): if entry.real_name == user_name: return entry.id return None def get_direct_message_channel_id_by_user_id(self, user_id): """Returns the channel ID of the direct message with the user with ID `user_id`, or `None` if the ID is invalid.""" listing = self.client.api_call("im.list")["ims"] for entry in listing: if entry["user"] == user_id: return entry["id"] return None def get_user_info_by_id(self, user_id): """Returns a [metadata dictionary](https://api.slack.com/types/user) about the user with ID `user_id`.""" assert self.get_user_name_by_id( user_id ) is not None, "`user_id` must exist and be a valid user ID rather than \"{}\"".format( user_id) self.logger.info("retrieving user info for user {}".format( self.get_user_name_by_id(user_id))) response = self.client.api_call("users.info", user=user_id) assert response.get("ok"), "User info request failed: error {}".format( response.get("error")) assert isinstance(response.get("user"), dict) and "id" in response[ "user"], "User info response malformed: {}".format( response.get("user")) return response["user"] def get_user_is_bot(self, user_id): """Returns `True` if the user with ID `user_id` is a bot user, `False` otherwise.""" if user_id == "USLACKBOT": return True # for some reason, Slack doesn't consider Slackbot a real bot user_info = self.get_user_info_by_id_cached(user_id) return user_info.get("is_bot", False) def server_text_to_sendable_text(self, server_text): """Returns `server_text`, a string in Slack server message format, converted into a string in Slack sendable message format.""" assert isinstance( server_text, str), "`server_text` must be a string rather than \"{}\"".format( server_text) text_without_special_sequences = re.sub(r"<[^<>]*>", "", server_text) assert "<" not in text_without_special_sequences and ">" not in text_without_special_sequences, "Invalid special sequence in server text \"{}\", perhaps some text needs to be escaped" # process link references def process_special_sequence(match): original, body = match.group(0), match.group(1).split("|")[0] if body.startswith("#"): return original # channel reference, should send unchanged if body.startswith("@"): return original # user reference, should send unchanged if body.startswith("!"): return original # special command, should send unchanged return body # link, should remove angle brackets and label in order to allow it to linkify return re.sub(r"<(.*?)>", process_special_sequence, server_text) def text_to_sendable_text(self, text): """Returns `text`, a plain text string, converted into a string in Slack sendable message format.""" assert isinstance( text, str), "`text` must be a string rather than \"{}\"".format(text) return text.replace("&", "&").replace("<", "<").replace(">", ">") def sendable_text_to_text(self, sendable_text): """Returns `sendable_text`, a string in Slack sendable message format, converted into a plain text string. The transformation can lose some information for escape sequences, such as link labels.""" assert isinstance( sendable_text, str), "`sendable_text` must be a string rather than \"{}\"".format( sendable_text) text_without_special_sequences = re.sub(r"<[^<>]*>", "", sendable_text) assert "<" not in text_without_special_sequences and ">" not in text_without_special_sequences, "Invalid special sequence in sendable text \"{}\", perhaps some text needs to be escaped" # process link references def process_special_sequence(match): original, body = match.group(0), match.group(1).split("|")[0] if body.startswith("#"): # channel reference channel_name = self.get_channel_name_by_id(body[1:]) if channel_name is None: return original return "#" + channel_name if body.startswith("@"): # user reference user_name = self.get_user_name_by_id(body[1:]) if user_name is None: return original return "@" + user_name if body.startswith("!"): # special command if body == "!channel": return "@channel" if body == "!group": return "@group" if body == "!everyone": return "@everyone" return original raw_text = re.sub(r"<(.*?)>", process_special_sequence, sendable_text) return raw_text.replace("<", "<").replace(">", ">").replace("&", "&") def administrator_console(self, namespace): """Start an interactive administrator Python console with namespace `namespace`.""" import threading import readline # this makes arrow keys work for input() import code def start_console(): code.interact("##########################################\n" + "# Botty Administrator Python Console #\n" + "##########################################\n", local=namespace) console_thread = threading.Thread( target=start_console, daemon=True ) # thread dies when main thread (the only non-daemon thread) exits console_thread.start()
class SlackBackend(ErrBot): room_types = "public_channel,private_channel" @staticmethod def _unpickle_identifier(identifier_str): return SlackBackend.__build_identifier(identifier_str) @staticmethod def _pickle_identifier(identifier): return SlackBackend._unpickle_identifier, (str(identifier),) def _register_identifiers_pickling(self): """ Register identifiers pickling. As Slack needs live objects in its identifiers, we need to override their pickling behavior. But for the unpickling to work we need to use bot.build_identifier, hence the bot parameter here. But then we also need bot for the unpickling so we save it here at module level. """ SlackBackend.__build_identifier = self.build_identifier for cls in (SlackPerson, SlackRoomOccupant, SlackRoom): copyreg.pickle( cls, SlackBackend._pickle_identifier, SlackBackend._unpickle_identifier ) def __init__(self, config): super().__init__(config) identity = config.BOT_IDENTITY self.token = identity.get("token", None) self.proxies = identity.get("proxies", None) if not self.token: log.fatal( 'You need to set your token (found under "Bot Integration" on Slack) in ' "the BOT_IDENTITY setting in your configuration. Without this token I " "cannot connect to Slack." ) sys.exit(1) self.sc = None # Will be initialized in serve_once compact = config.COMPACT_OUTPUT if hasattr(config, "COMPACT_OUTPUT") else False self.md = slack_markdown_converter(compact) self._register_identifiers_pickling() def set_message_size_limit(self, limit=4096, hard_limit=40000): """ Slack supports upto 40000 characters per message, Errbot maintains 4096 by default. """ super().set_message_size_limit(limit, hard_limit) def api_call(self, method, data=None, raise_errors=True): """ Make an API call to the Slack API and return response data. This is a thin wrapper around `SlackClient.server.api_call`. :param method: The API method to invoke (see https://api.slack.com/methods/). :param raise_errors: Whether to raise :class:`~SlackAPIResponseError` if the API returns an error :param data: A dictionary with data to pass along in the API request. :returns: A dictionary containing the (JSON-decoded) API response :raises: :class:`~SlackAPIResponseError` if raise_errors is True and the API responds with `{"ok": false}` """ if data is None: data = {} response = self.sc.api_call(method, **data) if not isinstance(response, collections.Mapping): # Compatibility with SlackClient < 1.0.0 response = json.loads(response.decode("utf-8")) if raise_errors and not response["ok"]: raise SlackAPIResponseError( f"Slack API call to {method} failed: {response['error']}", error=response["error"], ) return response def update_alternate_prefixes(self): """Converts BOT_ALT_PREFIXES to use the slack ID instead of name Slack only acknowledges direct callouts `@username` in chat if referred by using the ID of that user. """ # convert BOT_ALT_PREFIXES to a list try: bot_prefixes = self.bot_config.BOT_ALT_PREFIXES.split(",") except AttributeError: bot_prefixes = list(self.bot_config.BOT_ALT_PREFIXES) converted_prefixes = [] for prefix in bot_prefixes: try: converted_prefixes.append(f"<@{self.username_to_userid(prefix)}>") except Exception as e: log.error( 'Failed to look up Slack userid for alternate prefix "%s": %s', prefix, e, ) self.bot_alt_prefixes = tuple( x.lower() for x in self.bot_config.BOT_ALT_PREFIXES ) log.debug("Converted bot_alt_prefixes: %s", self.bot_config.BOT_ALT_PREFIXES) def serve_once(self): self.sc = SlackClient(self.token, proxies=self.proxies) log.info("Verifying authentication token") self.auth = self.api_call("auth.test", raise_errors=False) if not self.auth["ok"]: raise SlackAPIResponseError( error=f"Couldn't authenticate with Slack. Server said: {self.auth['error']}" ) log.debug("Token accepted") self.bot_identifier = SlackPerson(self.sc, self.auth["user_id"]) log.info("Connecting to Slack real-time-messaging API") if self.sc.rtm_connect(): log.info("Connected") # Block on reads instead of using the busy loop suggested in slackclient docs # https://github.com/slackapi/python-slackclient/issues/46#issuecomment-165674808 self.sc.server.websocket.sock.setblocking(True) self.reset_reconnection_count() # Inject bot identity to alternative prefixes self.update_alternate_prefixes() try: while True: for message in self.sc.rtm_read(): self._dispatch_slack_message(message) except KeyboardInterrupt: log.info("Interrupt received, shutting down..") return True except Exception: log.exception("Error reading from RTM stream:") finally: log.debug("Triggering disconnect callback") self.disconnect_callback() else: raise Exception("Connection failed, invalid token ?") def _dispatch_slack_message(self, message): """ Process an incoming message from slack. """ if "type" not in message: log.debug("Ignoring non-event message: %s.", message) return event_type = message["type"] event_handlers = { "hello": self._hello_event_handler, "presence_change": self._presence_change_event_handler, "message": self._message_event_handler, "member_joined_channel": self._member_joined_channel_event_handler, "reaction_added": self._reaction_event_handler, "reaction_removed": self._reaction_event_handler, } event_handler = event_handlers.get(event_type) if event_handler is None: log.debug( "No event handler available for %s, ignoring this event", event_type ) return try: log.debug("Processing slack event: %s", message) event_handler(message) except Exception: log.exception(f"{event_type} event handler raised an exception") def _hello_event_handler(self, event): """Event handler for the 'hello' event""" self.connect_callback() self.callback_presence(Presence(identifier=self.bot_identifier, status=ONLINE)) def _presence_change_event_handler(self, event): """Event handler for the 'presence_change' event""" idd = SlackPerson(self.sc, event["user"]) presence = event["presence"] # According to https://api.slack.com/docs/presence, presence can # only be one of 'active' and 'away' if presence == "active": status = ONLINE elif presence == "away": status = AWAY else: log.error( f"It appears the Slack API changed, I received an unknown presence type {presence}." ) status = ONLINE self.callback_presence(Presence(identifier=idd, status=status)) def _message_event_handler(self, event): """Event handler for the 'message' event""" channel = event["channel"] if channel[0] not in "CGD": log.warning("Unknown message type! Unable to handle %s", channel) return subtype = event.get("subtype", None) if subtype in ("message_deleted", "channel_topic", "message_replied"): log.debug("Message of type %s, ignoring this event", subtype) return if subtype == "message_changed" and "attachments" in event["message"]: # If you paste a link into Slack, it does a call-out to grab details # from it so it can display this in the chatroom. These show up as # message_changed events with an 'attachments' key in the embedded # message. We should completely ignore these events otherwise we # could end up processing bot commands twice (user issues a command # containing a link, it gets processed, then Slack triggers the # message_changed event and we end up processing it again as a new # message. This is not what we want). log.debug( "Ignoring message_changed event with attachments, likely caused " "by Slack auto-expanding a link" ) return if "message" in event: text = event["message"].get("text", "") user = event["message"].get("user", event.get("bot_id")) else: text = event.get("text", "") user = event.get("user", event.get("bot_id")) text, mentioned = self.process_mentions(text) text = self.sanitize_uris(text) log.debug("Saw an event: %s", pprint.pformat(event)) log.debug("Escaped IDs event text: %s", text) msg = Message( text, extras={ "attachments": event.get("attachments"), "slack_event": event, }, ) if channel.startswith("D"): if subtype == "bot_message": msg.frm = SlackBot( self.sc, bot_id=event.get("bot_id"), bot_username=event.get("username", ""), ) else: msg.frm = SlackPerson(self.sc, user, event["channel"]) msg.to = SlackPerson( self.sc, self.username_to_userid(self.sc.server.username), event["channel"], ) channel_link_name = event["channel"] else: if subtype == "bot_message": msg.frm = SlackRoomBot( self.sc, bot_id=event.get("bot_id"), bot_username=event.get("username", ""), channelid=event["channel"], bot=self, ) else: msg.frm = SlackRoomOccupant(self.sc, user, event["channel"], bot=self) msg.to = SlackRoom(channelid=event["channel"], bot=self) channel_link_name = msg.to.name msg.extras["url"] = ( f"https://{self.sc.server.domain}.slack.com/archives/" f'{channel_link_name}/p{self._ts_for_message(msg).replace(".", "")}' ) self.callback_message(msg) if mentioned: self.callback_mention(msg, mentioned) def _member_joined_channel_event_handler(self, event): """Event handler for the 'member_joined_channel' event""" user = SlackPerson(self.sc, event["user"]) if user == self.bot_identifier: user = self.bot_identifier self.callback_room_joined(SlackRoom(channelid=event["channel"], bot=self), user) def _reaction_event_handler(self, event): """Event handler for the 'reaction_added' and 'reaction_removed' events""" user = SlackPerson(self.sc, event["user"]) item_user = None if event["item_user"]: item_user = SlackPerson(self.sc, event["item_user"]) action = REACTION_ADDED if event["type"] == "reaction_removed": action = REACTION_REMOVED reaction = Reaction( reactor=user, reacted_to_owner=item_user, action=action, timestamp=event["event_ts"], reaction_name=event["reaction"], reacted_to=event["item"], ) self.callback_reaction(reaction) def userid_to_username(self, id_): """Convert a Slack user ID to their user name""" user = self.sc.server.users.get(id_) if user is None: raise UserDoesNotExistError(f"Cannot find user with ID {id_}.") return user.name def username_to_userid(self, name): """Convert a Slack user name to their user ID""" name = name.lstrip("@") user = self.sc.server.users.find(name) if user is None: raise UserDoesNotExistError(f"Cannot find user {name}.") return user.id def channelid_to_channelname(self, id_): """Convert a Slack channel ID to its channel name""" channel = [channel for channel in self.sc.server.channels if channel.id == id_] if not channel: raise RoomDoesNotExistError(f"No channel with ID {id_} exists.") return channel[0].name def channelname_to_channelid(self, name): """Convert a Slack channel name to its channel ID""" name = name.lstrip("#") channel = [ channel for channel in self.sc.server.channels if channel.name == name ] if not channel: raise RoomDoesNotExistError(f"No channel named {name} exists") return channel[0].id def channels(self, exclude_archived=True, joined_only=False, types=room_types): """ Get all channels and groups and return information about them. :param exclude_archived: Exclude archived channels/groups :param joined_only: Filter out channels the bot hasn't joined :returns: A list of channel (https://api.slack.com/types/channel) and group (https://api.slack.com/types/group) types. See also: * https://api.slack.com/methods/conversations.list """ response = self.api_call( "conversations.list", data={"exclude_archived": exclude_archived, "types": types}, ) channels = [ channel for channel in response["channels"] if channel["is_member"] or not joined_only ] # There is no need to list groups anymore. Groups are now identified as 'private_channel' # type using the conversations.list api method. # response = self.api_call('groups.list', data={'exclude_archived': exclude_archived}) # No need to filter for 'is_member' in this next call (it doesn't # (even exist) because leaving a group means you have to get invited # back again by somebody else. # groups = [group for group in response['groups']] return channels @lru_cache(1024) def get_im_channel(self, id_): """Open a direct message channel to a user""" try: response = self.api_call("conversations.open", data={"users": id_}) return response["channel"]["id"] except SlackAPIResponseError as e: if e.error == "cannot_dm_bot": log.info("Tried to DM a bot.") return None else: raise e def _prepare_message(self, msg): # or card """ Translates the common part of messaging for Slack. :param msg: the message you want to extract the Slack concept from. :return: a tuple to user human readable, the channel id """ if msg.is_group: to_channel_id = msg.to.id to_humanreadable = ( msg.to.name if msg.to.name else self.channelid_to_channelname(to_channel_id) ) else: to_humanreadable = msg.to.username to_channel_id = msg.to.channelid if to_channel_id.startswith("C"): log.debug( "This is a divert to private message, sending it directly to the user." ) to_channel_id = self.get_im_channel( self.username_to_userid(msg.to.username) ) return to_humanreadable, to_channel_id def send_message(self, msg): super().send_message(msg) if msg.parent is not None: # we are asked to reply to a specify thread. try: msg.extras["thread_ts"] = self._ts_for_message(msg.parent) except KeyError: # Gives to the user a more interesting explanation if we cannot find a ts from the parent. log.exception( "The provided parent message is not a Slack message " "or does not contain a Slack timestamp." ) to_humanreadable = "<unknown>" try: if msg.is_group: to_channel_id = msg.to.id to_humanreadable = ( msg.to.name if msg.to.name else self.channelid_to_channelname(to_channel_id) ) else: to_humanreadable = msg.to.username if isinstance( msg.to, RoomOccupant ): # private to a room occupant -> this is a divert to private ! log.debug( "This is a divert to private message, sending it directly to the user." ) to_channel_id = self.get_im_channel( self.username_to_userid(msg.to.username) ) else: to_channel_id = msg.to.channelid msgtype = "direct" if msg.is_direct else "channel" log.debug( "Sending %s message to %s (%s).", msgtype, to_humanreadable, to_channel_id, ) body = self.md.convert(msg.body) log.debug("Message size: %d.", len(body)) parts = self.prepare_message_body(body, self.message_size_limit) timestamps = [] for part in parts: data = { "channel": to_channel_id, "text": part, "unfurl_media": "true", "link_names": "1", "as_user": "******", } # Keep the thread_ts to answer to the same thread. if "thread_ts" in msg.extras: data["thread_ts"] = msg.extras["thread_ts"] result = self.api_call("chat.postMessage", data=data) timestamps.append(result["ts"]) msg.extras["ts"] = timestamps except Exception: log.exception( f"An exception occurred while trying to send the following message " f"to {to_humanreadable}: {msg.body}." ) def _slack_upload(self, stream: Stream) -> None: """ Performs an upload defined in a stream :param stream: Stream object :return: None """ try: stream.accept() resp = self.api_call( "files.upload", data={ "channels": stream.identifier.channelid, "filename": stream.name, "file": stream, }, ) if "ok" in resp and resp["ok"]: stream.success() else: stream.error() except Exception: log.exception( f"Upload of {stream.name} to {stream.identifier.channelname} failed." ) def send_stream_request( self, user: Identifier, fsource: BinaryIO, name: str = None, size: int = None, stream_type: str = None, ) -> Stream: """ Starts a file transfer. For Slack, the size and stream_type are unsupported :param user: is the identifier of the person you want to send it to. :param fsource: is a file object you want to send. :param name: is an optional filename for it. :param size: not supported in Slack backend :param stream_type: not supported in Slack backend :return Stream: object on which you can monitor the progress of it. """ stream = Stream(user, fsource, name, size, stream_type) log.debug( "Requesting upload of %s to %s (size hint: %d, stream type: %s).", name, user.channelname, size, stream_type, ) self.thread_pool.apply_async(self._slack_upload, (stream,)) return stream def send_card(self, card: Card): if isinstance(card.to, RoomOccupant): card.to = card.to.room to_humanreadable, to_channel_id = self._prepare_message(card) attachment = {} if card.summary: attachment["pretext"] = card.summary if card.title: attachment["title"] = card.title if card.link: attachment["title_link"] = card.link if card.image: attachment["image_url"] = card.image if card.thumbnail: attachment["thumb_url"] = card.thumbnail if card.color: attachment["color"] = ( COLORS[card.color] if card.color in COLORS else card.color ) if card.fields: attachment["fields"] = [ {"title": key, "value": value, "short": True} for key, value in card.fields ] parts = self.prepare_message_body(card.body, self.message_size_limit) part_count = len(parts) footer = attachment.get("footer", "") for i in range(part_count): if part_count > 1: attachment["footer"] = f"{footer} [{i + 1}/{part_count}]" attachment["text"] = parts[i] data = { "channel": to_channel_id, "attachments": json.dumps([attachment]), "link_names": "1", "as_user": "******", } try: log.debug("Sending data:\n%s", data) self.api_call("chat.postMessage", data=data) except Exception: log.exception( f"An exception occurred while trying to send a card to {to_humanreadable}.[{card}]" ) def __hash__(self): return 0 # this is a singleton anyway def change_presence(self, status: str = ONLINE, message: str = "") -> None: self.api_call( "users.setPresence", data={"presence": "auto" if status == ONLINE else "away"}, ) @staticmethod def prepare_message_body(body, size_limit): """ Returns the parts of a message chunked and ready for sending. This is a staticmethod for easier testing. Args: body (str) size_limit (int): chunk the body into sizes capped at this maximum Returns: [str] """ fixed_format = body.startswith("```") # hack to fix the formatting parts = list(split_string_after(body, size_limit)) if len(parts) == 1: # If we've got an open fixed block, close it out if parts[0].count("```") % 2 != 0: parts[0] += "\n```\n" else: for i, part in enumerate(parts): starts_with_code = part.startswith("```") # If we're continuing a fixed block from the last part if fixed_format and not starts_with_code: parts[i] = "```\n" + part # If we've got an open fixed block, close it out if part.count("```") % 2 != 0: parts[i] += "\n```\n" return parts @staticmethod def extract_identifiers_from_string(text): """ Parse a string for Slack user/channel IDs. Supports strings with the following formats:: <#C12345> <@U12345> <@U12345|user> @user #channel/user #channel Returns the tuple (username, userid, channelname, channelid). Some elements may come back as None. """ exception_message = ( "Unparseable slack identifier, should be of the format `<#C12345>`, `<@U12345>`, " "`<@U12345|user>`, `@user`, `#channel/user` or `#channel`. (Got `%s`)" ) text = text.strip() if text == "": raise ValueError(exception_message % "") channelname = None username = None channelid = None userid = None if text[0] == "<" and text[-1] == ">": exception_message = ( "Unparseable slack ID, should start with U, B, C, G, D or W (got `%s`)" ) text = text[2:-1] if text == "": raise ValueError(exception_message % "") if text[0] in ("U", "B", "W"): if "|" in text: userid, username = text.split("|") else: userid = text elif text[0] in ("C", "G", "D"): channelid = text else: raise ValueError(exception_message % text) elif text[0] == "@": username = text[1:] elif text[0] == "#": plainrep = text[1:] if "/" in text: channelname, username = plainrep.split("/", 1) else: channelname = plainrep else: raise ValueError(exception_message % text) return username, userid, channelname, channelid def build_identifier(self, txtrep): """ Build a :class:`SlackIdentifier` from the given string txtrep. Supports strings with the formats accepted by :func:`~extract_identifiers_from_string`. """ log.debug("building an identifier from %s.", txtrep) username, userid, channelname, channelid = self.extract_identifiers_from_string( txtrep ) if userid is None and username is not None: userid = self.username_to_userid(username) if channelid is None and channelname is not None: channelid = self.channelname_to_channelid(channelname) if userid is not None and channelid is not None: return SlackRoomOccupant(self.sc, userid, channelid, bot=self) if userid is not None: return SlackPerson(self.sc, userid, self.get_im_channel(userid)) if channelid is not None: return SlackRoom(channelid=channelid, bot=self) raise Exception( "You found a bug. I expected at least one of userid, channelid, username or channelname " "to be resolved but none of them were. This shouldn't happen so, please file a bug." ) def is_from_self(self, msg: Message) -> bool: return self.bot_identifier.userid == msg.frm.userid def build_reply(self, msg, text=None, private=False, threaded=False): response = self.build_message(text) if "thread_ts" in msg.extras["slack_event"]: # If we reply to a threaded message, keep it in the thread. response.extras["thread_ts"] = msg.extras["slack_event"]["thread_ts"] elif threaded: # otherwise check if we should start a new thread response.parent = msg response.frm = self.bot_identifier if private: response.to = msg.frm else: response.to = msg.frm.room if isinstance(msg.frm, RoomOccupant) else msg.frm return response def add_reaction(self, msg: Message, reaction: str) -> None: """ Add the specified reaction to the Message if you haven't already. :param msg: A Message. :param reaction: A str giving an emoji, without colons before and after. :raises: ValueError if the emoji doesn't exist. """ return self._react("reactions.add", msg, reaction) def remove_reaction(self, msg: Message, reaction: str) -> None: """ Remove the specified reaction from the Message if it is currently there. :param msg: A Message. :param reaction: A str giving an emoji, without colons before and after. :raises: ValueError if the emoji doesn't exist. """ return self._react("reactions.remove", msg, reaction) def _react(self, method: str, msg: Message, reaction: str) -> None: try: # this logic is from send_message if msg.is_group: to_channel_id = msg.to.id else: to_channel_id = msg.to.channelid ts = self._ts_for_message(msg) self.api_call( method, data={"channel": to_channel_id, "timestamp": ts, "name": reaction}, ) except SlackAPIResponseError as e: if e.error == "invalid_name": raise ValueError(e.error, "No such emoji", reaction) elif e.error in ("no_reaction", "already_reacted"): # This is common if a message was edited after you reacted to it, and you reacted to it again. # Chances are you don't care about this. If you do, call api_call() directly. pass else: raise SlackAPIResponseError(error=e.error) def _ts_for_message(self, msg): try: return msg.extras["slack_event"]["message"]["ts"] except KeyError: return msg.extras["slack_event"]["ts"] def shutdown(self): super().shutdown() @property def mode(self): return "slack" def query_room(self, room): """ Room can either be a name or a channelid """ if room.startswith("C") or room.startswith("G"): return SlackRoom(channelid=room, bot=self) m = SLACK_CLIENT_CHANNEL_HYPERLINK.match(room) if m is not None: return SlackRoom(channelid=m.groupdict()["id"], bot=self) return SlackRoom(name=room, bot=self) def rooms(self): """ Return a list of rooms the bot is currently in. :returns: A list of :class:`~SlackRoom` instances. """ channels = self.channels( joined_only=True, exclude_archived=True, ) return [SlackRoom(channelid=channel["id"], bot=self) for channel in channels] def prefix_groupchat_reply(self, message, identifier): super().prefix_groupchat_reply(message, identifier) message.body = f"@{identifier.nick}: {message.body}" @staticmethod def sanitize_uris(text): """ Sanitizes URI's present within a slack message. e.g. <mailto:[email protected]|[email protected]>, <http://example.org|example.org> <http://example.org> :returns: string """ text = re.sub(r"<([^|>]+)\|([^|>]+)>", r"\2", text) text = re.sub(r"<(http([^>]+))>", r"\1", text) return text def process_mentions(self, text): """ Process mentions in a given string :returns: A formatted string of the original message and a list of :class:`~SlackPerson` instances. """ mentioned = [] m = re.findall("<@[^>]*>*", text) for word in m: try: identifier = self.build_identifier(word) except Exception as e: log.debug( "Tried to build an identifier from '%s' but got exception: %s", word, e, ) continue # We only track mentions of persons. if isinstance(identifier, SlackPerson): log.debug("Someone mentioned") mentioned.append(identifier) text = text.replace(word, str(identifier)) return text, mentioned
class RtmBot(object): def __init__(self, token): self.last_ping = 0 self.token = token self.bot_plugins = [] self.slack_client = None def connect(self): """Convenience method that creates Server instance""" self.slack_client = SlackClient(self.token) self.slack_client.rtm_connect() def start(self): self.connect() self.load_plugins() while True: for reply in self.slack_client.rtm_read(): self.input(reply) self.crons() self.output() self.autoping() time.sleep(.1) def autoping(self): #hardcode the interval to 3 seconds now = int(time.time()) if now > self.last_ping + 3: self.slack_client.server.ping() self.last_ping = now def input(self, data): if "type" in data: function_name = "process_" + data["type"] logging.debug("got {}".format(function_name)) for plugin in self.bot_plugins: plugin.register_jobs() plugin.do(function_name, data) def output(self): for plugin in self.bot_plugins: limiter = False for output in plugin.do_output(): channel = self.slack_client.server.channels.find(output[0]) if channel != None and output[1] != None: if limiter == True: time.sleep(.1) limiter = False message = output[1].encode('ascii','ignore') channel.send_message("{}".format(message)) limiter = True def crons(self): for plugin in self.bot_plugins: plugin.do_jobs() def load_plugins(self): import plugins for plugin in glob.glob(os.path.join(plugins.__path__[0], '*')): sys.path.insert(0, plugin) sys.path.insert(0, os.path.join(plugins.__path__[0])) for plugin in glob.glob(os.path.join(plugins.__path__[0], '*.py')) + glob.glob(os.path.join(plugins.__path__[0], '*','*.py')): logging.info(plugin) name = os.path.split(plugin)[-1][:-3] try: self.bot_plugins.append(Plugin(name)) except: print "error loading plugin %s" % name
check = 0 transfer = 0 add = 0 remove = 0 _help = 0 sender_id = "" message_give_id = "" message_take_id = "" sender_name = "" message_give_name = "" message_take_name = "" #----------------------------------------------------- #Get Message and Such for message in slack_client.rtm_read(): if 'text' in message: print(f'Message received:\n{json.dumps(message, indent=2)}') message_text = message['text'] message_text = message_text.lower() sender_id = message['user'] sender_id = sender_id.lower() message_text = message_text.replace('me', f'<@{sender_id}>') #----------------------------------------------------- #Parse Message actions and ID's if re.match(r'.*(help).*', message_text, re.IGNORECASE): _help = 1 if re.match(r'.*(check|who).*', message_text, re.IGNORECASE): check = 1
print(output) return output['text'].split(AT_BOT)[1].strip().lower(), \ output['channel'] return None, None if __name__ == "__main__": envset.env() # starterbot's ID as an environment variable BOT_ID = os.environ.get("BOT_ID") token = os.environ.get("SLACK_BOT_TOKEN") # constants AT_BOT = "<@" + BOT_ID + ">" EXAMPLE_COMMAND = "do" # instantiate Slack & Twilio clients slack_client = SlackClient(os.environ.get('SLACK_BOT_TOKEN')) READ_WEBSOCKET_DELAY = 1 # 1 second delay between reading from firehose if slack_client.rtm_connect(): print("StarterBot connected and running!") while True: # print(slack_client.rtm_read()) command, channel = parse_slack_output(slack_client.rtm_read()) # print(command, channel) if command and channel: handle_command(command, channel) time.sleep(READ_WEBSOCKET_DELAY) else: print("Connection failed. Invalid Slack token or bot ID?")
for user in users: if user.get('id') == output['user']: user_name = user.get('real_name') if user.get('real_name') == user_n: user_id1 = user.get('id') user_name1 = user.get('real_name') cursor = connection.cursor() cursor.execute("INSERT INTO slack_bot_app_message (user_id, user_name, text, team_id, ts) " "VALUES (%s, %s, %s, %s, %s)", (user_id1, user_name1, output['text'], 1, output['event_ts'])) connection.commit() return None, None, None, None, None, None if __name__ == "__main__": slack_client = SlackClient("xoxb-284823465173-QKs8E9L0PQStyJLc3kTjRxQc") READ_WEBSOCKET_DELAY = 1 if slack_client.rtm_connect(): print("StarterBot connected and running!") while True: command, channel, user, thread, ts, team = parse_slack_output(slack_client.rtm_read()) if command and channel and user and thread == 0: handle_command(command, user, team) if command and channel and user and thread == 1: direct_command(command, user, ts) time.sleep(READ_WEBSOCKET_DELAY) else: print("Connection failed. Invalid Slack token or bot ID?")
"value": "yes" },{ "name": "no", "text": "No", "type": "button", "value": "no" } ] }] slack_client.api_call("chat.postMessage", channel=channel, text=response, as_user=True, attachments= attachments) if __name__ == '__main__': READ_WEBSOCKET_DELAY = 0.5 #per second if slack_client.rtm_connect(): print(BOT_NAME + " connected and running!") while True: rtm = slack_client.rtm_read() if rtm: print rtm command, channel = parse_slack_output(rtm) if command and channel: handle_command(command, channel) time.sleep(READ_WEBSOCKET_DELAY) else: print(BOT_NAME+ " not connected!")
The Slack Real Time Messaging API is an events firehose. this parsing function returns None unless a message is directed at the Bot, based on its ID. """ output_list = slack_rtm_output if output_list and len(output_list) > 0: for output in output_list: if output and 'text' in output and 'user' in output: recieveMessageFromUser(IDToUser[output['user']], output['text']) return if __name__ == "__main__": if not slack_client.rtm_connect(): print("Connection failed. Invalid Slack token or bot ID?") exit() IDToUser = getSlackUsers() for id, user in IDToUser.iteritems(): if user.name == "ashley.coleman": # Remove for release sendMessageToUser(user) READ_WEBSOCKET_DELAY = 1 # 1 second delay between reading from firehose print("Bot connected and running!") while True: parseSlackMessage(slack_client.rtm_read(), IDToUser) time.sleep(READ_WEBSOCKET_DELAY)
class AA5ROBot: """ A Slack bot for the AARO Slack site. """ def __init__(self): # Get the Bot token from the environment. Raises RunTimeError if the # value isn't set because the bot can't run without a token configured. slack_bot_token = os.environ.get('SLACK_BOT_TOKEN') if slack_bot_token == '': raise RuntimeError( 'SLACK_BOT_TOKEN must be set in the environment.') # Create the main SlackClient instance for the bot self.slack_client = SlackClient(slack_bot_token) # start initial connection to Slack RTM if self.slack_client.rtm_connect(with_team_state=False): logger.info("AA5ROBot connected to Slack.") self.aa5robot_id = self.slack_client.api_call( "auth.test")["user_id"] else: logger.warning("Connection to Slack RTM failed.") self.shutdown(1) # Load the bot's commands self.commands = command.get_commands() print('AA5RObot initialized.') def start(self): reconnects = 0 # Process events from Slack RTM until ctrl-c logger.info('Processing events from Slack...') while self.slack_client.server.connected is True: try: data, channel = self.parse_bot_commands( self.slack_client.rtm_read()) if data: self.handle_command(data, channel) time.sleep(RTM_READ_DELAY) except KeyboardInterrupt: self.shutdown() # If execution gets here, the connection to the server was interrupted. # Attempt up to MAX_RECONNECT_ATTEMPTS tries to reconnect to Slack. while reconnects < MAX_RECONNECT_ATTEMPTS: print( 'AA5RObot lost connection to Slack. Attempting reconnect, try {}' .format(reconnects + 1)) if self.slack_client.rtm_connect(with_team_state=False): print("AA5ROBot reconnected to Slack.") self.aa5robot_id = slack_client.api_call( "auth.test")["user_id"] self.start() else: reconnects += 1 time.sleep(RECONNECT_WAIT_TIME) # Bot was unable to reconnect, so end the process. print('Unable to reconnect to Slack. Exiting.') self.shutdown(1) def shutdown(self, exit_code=0): """ Execute cleanup tasks before exiting the process. """ # call shutdown method on all command instances for instance in self.commands: instance[1].shutdown() # end the process print("AA5ROBot exiting.") sys.exit(exit_code) def parse_bot_commands(self, slack_events): """ Parses a list of events coming from the Slack RTM API to find bot commands. If a bot command is found, this function returns a tuple of command and channel. If a command is not found, then this function returns None, None. """ for event in slack_events: try: if event["type"] == "message" and not "subtype" in event: user_id, message = self.parse_direct_mention(event["text"]) if user_id == self.aa5robot_id: return message, event["channel"] except KeyError: return None, None return None, None def parse_direct_mention(self, message_text): """ Finds a direct mention (a mention that is at the beginning) in message text and returns the user ID which was mentioned. If there is no direct mention, returns None. """ matches = re.search("^<@(|[WU].+?)>(.*)", message_text) # the first group contains the username, the second group contains the remaining message return (matches.group(1), matches.group(2).strip()) if matches else (None, None) def handle_command(self, data, channel): """ Executes a bot command. """ logger.debug('channel: {}, data: {}'.format(channel, data)) # get command string try: command_str = data.split()[0].lower() except IndexError: self.send_message( channel, "Not sure what you mean. Tell me 'help' for more info.") return if command_str == '': self.send_message( channel, "Not sure what you mean. Tell me 'help' for more info.") return if command_str == 'help' or command_str == '?': self.handle_help(channel) return command_strings = [i[0] for i in self.commands] if command_str in command_strings: logger.info("Executing command '{}'.".format(command_str)) method, response = self.commands[command_strings.index( command_str)][1].do_command(data) if method == command.MessageTypes.RTM_MESSAGE: self.send_message(channel, response) if method == command.MessageTypes.API_CALL: self.chat_post_message(channel, response) else: self.send_message( channel, "Not sure what you mean. Tell me 'help' for more info.") return def handle_help(self, channel): """ Sends the bot's help message to Slack. """ self.send_message(channel, "Help not implemented yet!") def send_message(self, channel, response): """ Send a text-only response via the RTM API. """ self.slack_client.rtm_send_message(channel, response) def chat_post_message(self, channel, response): """ Send a chat.postMessage API call. """ self.slack_client.api_call("chat.postMessage", channel=channel, as_user=True, attachments=json.dumps(response))
class SlackBot(object): def __init__(self, token, reply_func, only_speaking_to_me=True): self.last_ping = 0 self.token = token self.reply_func = reply_func self.slack_client = None self.user_id = None self.only_speaking_to_me = only_speaking_to_me def _connect(self): self.slack_client = SlackClient(self.token) self.user_id = self.slack_client.api_call('auth.test')['user_id'] self.slack_client.rtm_connect() time.sleep(1) self.slack_client.rtm_read() def start(self): while True: self._connect() try: while True: outbox = [] for event in self.slack_client.rtm_read(): # print event outbox += self._process_event(event) self._send_messages(outbox) self._autoping() time.sleep(0.1) except Exception as e: traceback.print_exc() time.sleep(60) # What happened?; try to reconnect def _autoping(self): # hardcode the interval to 3 seconds now = int(time.time()) if now > self.last_ping + 3: self.slack_client.server.ping() self.last_ping = now def _process_event(self, event): channel = event.get('channel') mtext = None callout = "<@{}>".format(self.user_id) if event.get('type') == 'message' and \ channel and event.get('text') and \ event.get('user', self.user_id) != self.user_id: if channel.startswith("D") or callout in event['text']: event['speaking_to_me'] = True else: event['speaking_to_me'] = False event['username'] = self.slack_client.server.users.find( event.get('user')).name # Turn "@your_bot: a bunch of words" into "a bunch of words" event['text_query'] = event['text'].replace(callout + ':', ' ').replace( callout, ' ') if not self.only_speaking_to_me or event['speaking_to_me']: mtext = self.reply_func(self.slack_client, event) if mtext: return [Message(channel=channel, text=mtext)] else: return [] def _send_messages(self, outbox): limiter = False for message in outbox: channel_obj = self.slack_client.server.channels.find( message.channel) if channel_obj is not None and message.text: if limiter: time.sleep(1) limiter = True channel_obj.send_message(u"{}".format(message.text)) print u"Sent: " + message.text + u" To: " + channel_obj.name
matches = re.search(MENCION_REGEX, texto) return (matches.group(1), matches.group(2).strip()) if matches else (None, None) def maneja_comando(comando, canal): """ Ejecuta el comando si se conoce. """ # Default response is help text for the user default_response = "No te entiendo. Prueba *ve*." response = comandos.maneja(comando) slack_client.api_call("chat.postMessage", channel=canal, text=response or default_response) if __name__ == "__main__": if slack_client.rtm_connect(with_team_state=False): print("¡Vamos charlandero!") starterbot_id = slack_client.api_call("auth.test")["user_id"] while True: command, channel = procesa_comandos(slack_client.rtm_read()) if command: maneja_comando(command, channel) time.sleep(RTM_READ_DELAY) else: print("Ha fallado. Lee más abajo para averiguar por qué")
class QASlackBot: buildparams = {} client = None my_user_name = '' userdict = {} reservedict = {} channel = None message = None buildparamsList = [] def userlist(self): api_call = self.client.api_call("users.list") if api_call.get('ok'): # retrieve all users users = api_call.get('members') for user in users: self.userdict[user['id']] = user['name'] #log.debug(self.userdict) def connect(self, token): self.client = SlackClient(token) self.client.rtm_connect() self.my_user_name = self.client.server.username log.debug("Connected to Slack as " + self.my_user_name) def listen(self): while True: try: input = self.client.rtm_read() if input: for action in input: log.debug(action) if 'type' in action and action['type'] == "message": self.process_message(action) else: sleep(1) # Check for time reserved and release when time is up for key in topics.keys(): if key in self.reservedict: elapsed = datetime.now() - self.reservedict[key][1] if elapsed.total_seconds() > TIMEOUT: msg = "@{0} уже 8 часов занимает сервер! Освобождаю `{1}`".format( self.reservedict[key][0], key) log.debug(msg) self.post(self.reservedict[key][2], msg) del self.reservedict[key] except Exception as e: pass #log.error("Exception: ", e.message) def process_message(self, message): self.channel = message['channel'] self.message = message['text'] if self.message.lower().find(" help") == 12: self.help() elif self.message.lower().find(" status") == 12: self.status() for key in topics.keys(): if self.message.lower().startswith( "take " + key) or self.message.lower().startswith( "t " + key) or self.message.lower().startswith(key + " take"): id = message['user'] # Hold state of who is using the stack if key not in self.reservedict: response = self.newreservation(key, id) else: response = self.existingReservation(key, id) elif key in self.reservedict and ( self.message.lower().startswith("free " + key) or self.message.lower().startswith("f " + key) or self.message.lower().startswith(key + " free")): response = self.releaseStack(key) def post(self, channel, message): chan = self.client.server.channels.find(channel) if not chan: raise Exception("Channel %s not found." % channel) return chan.send_message(message) def help(self): self.post( self.channel, "```Добро пожаловать в систему резервации тестовых серверов! \n\n Список всех серверов:\n \ qa1\n qa2\n qa3\n qa4\n stage2\n sandbox1\n prod\n- Зарезервировать сервер: 't <server>' ИЛИ 'take <server>' ИЛИ '<server> take'\n- Освободить сервер: 'f <server>' ИЛИ 'free <server>' ИЛИ '<server> free'\n- Проверить статус свободных серверов: \ '@qabot status'\n\nЛимит использования - 8 часов. Если используете дольше, забейте снова.```" ) def status(self): if not self.reservedict.keys(): self.post(self.channel, "Все свободно!") for key in self.reservedict.keys(): response = topics[key].format(self.reservedict[key][0], key) self.post(self.channel, response) log.info(response) def newreservation(self, key, id): log.info("not there") self.reservedict[key] = [ self.userdict[id], datetime.now(), self.channel ] response = topics[key].format(self.userdict[id], key) log.info("Posting to {0}: {1}".format(self.channel, response)) self.post(self.channel, response) def existingReservation(self, key, id): log.info("Stack already taken") response = "Сервер уже занят." log.info("Posting to {0}: {1}".format(self.channel, response)) self.post(self.channel, response) def releaseStack(self, key): log.info("release by user") response = self.reservedict[key][0] + " занял сервер " + key self.post(self.reservedict[key][2], response) del self.reservedict[key]
class RtmBot(object): def __init__(self, token): self.last_ping = 0 self.token = token self.bot_plugins = [] self.slack_client = None self.bot_on = True self.mode = HELPFUL def connect(self): """Convenience method that creates Server instance""" self.slack_client = SlackClient(self.token) self.slack_client.rtm_connect() logging.info(u"Connected {} to {} team at https://{}.slack.com".format( self.slack_client.server.username, self.slack_client.server.login_data['team']['name'], self.slack_client.server.domain)) def start(self): self.connect() self.load_plugins() while True: for reply in self.slack_client.rtm_read(): self.input(reply) self.crons() self.output() self.autoping() time.sleep(.1) def autoping(self): #hardcode the interval to 60 seconds now = int(time.time()) if now > self.last_ping + 60: self.slack_client.server.ping() self.last_ping = now def isBotMention(self, message): botUserName = self.slack_client.server.login_data['self']['id'] if re.search("@{}".format(botUserName), message): return True else: return False def input(self, data): # Make sure we're not responding to ourselves dbg("start input") if "user" in data and data[ 'user'] != self.slack_client.server.login_data['self']['id']: # data is of proper form if "type" in data: # Not doing anything with this event yet if data["type"] == "user_typing": return function_name = "process_" + data["type"] dbg("got {}".format(function_name)) dbg("data {}".format(data)) if "text" in data: if (self.mode == QUIET): if (data['text'] == "lb start" or data['text'] == "lemonbot start"): if (self.isAdmin(data['user'])): dbg("quiet mode admin") self.mode = HELPFUL function_name = "process_unmute" else: dbg("admin access restriction") function_name = "process_non_admin" elif self.mode == HELPFUL or self.mode == SNARKY: if self.isBotMention(data['text']): dbg("mention") function_name = "process_mention" self.bot_on = True elif data['text'].lower().startswith( "lemonbot") or data['text'].lower().startswith( "lb"): dbg("command") function_name = "process_helpful" if (True in [ x in data["text"] for x in ["hush", "shutup", "shut up", "quiet", "stfu"] ]): dbg("helpful mode") self.mode = HELPFUL function_name = "process_mode_helpful" elif ("mute" in data['text'].lower()): dbg("before admin check") if (self.isAdmin(data['user'])): dbg("quiet mode") self.mode = QUIET function_name = "process_mode_quiet" else: dbg("quiet mode access restriction") function_name = "process_non_admin" elif ("snarky" in data["text"].lower()): dbg("snarky mode") self.mode = SNARKY function_name = "process_mode_snarky" elif self.mode == SNARKY: dbg("snarky") function_name = "process_snarky" for plugin in self.bot_plugins: plugin.register_jobs() plugin.do(function_name, data) def isAdmin(self, id): user = self.getUser(id) dbg("admin print {}".format(user["is_admin"])) if user['is_admin'] or user['is_owner'] == "true": return (True) else: return (False) def getUser(self, id): user = self.slack_client.api_call("users.info", token="{}".format(self.token), user="******".format(id))["user"] dbg("getUser: {}".format(user['name'])) return (user) def getMembers(self): members = self.slack_client.api_call( "users.list", token="{}".format(self.token), )["members"] dbg("getMembers: {}".format([x["name"] for x in members])) return (members) def output(self): for plugin in self.bot_plugins: limiter = False for output in plugin.do_output(): channel = self.slack_client.server.channels.find(output[0]) if channel != None and output[1] != None: if limiter == True: time.sleep(.1) limiter = False message = output[1].encode('ascii', 'ignore') if message.startswith("__typing__"): user_typing_json = { "type": "typing", "channel": channel.id } logging.debug(user_typing_json) self.slack_client.server.send_to_websocket( user_typing_json) time.sleep(output[2]) else: channel.send_message("{}".format(message)) limiter = True for attachment in plugin.do_attachment(): channel = self.slack_client.server.channels.find(attachment[0]) if channel != None and attachment[1] != None: attachments = [] if attachment != None and attachment[2] != None: attachments.append(attachment[2]) attachments_json = json.dumps(attachments) resp = self.slack_client.api_call( "chat.postMessage", text="{}".format(attachment[1]), channel="{}".format(channel.id), as_user="******", attachments=attachments_json, ) logging.debug(resp) def crons(self): for plugin in self.bot_plugins: plugin.do_jobs() def load_plugins(self): for plugin in glob.glob(directory + '/plugins/*'): sys.path.insert(0, plugin) sys.path.insert(0, directory + '/plugins/') for plugin in glob.glob(directory + '/plugins/*.py') + glob.glob( directory + '/plugins/*/*.py'): logging.info(plugin) name = plugin.split('/')[-1][:-3] # try: self.bot_plugins.append(Plugin(name))
def gender(self, message): print('gender mode: processing message') # this regex looks for the keywords at the beginning or end of the # text, or after or before any non-alphanumeric characters. gender_terms = re.compile( r'(\W|^)(guys|dude|his|her|hers|she|he)(\W|$)') if gender_terms.search(message): # construct responses to gendered terms here response = "Hello! I'm the loving language bot. We are conducting an experiment in non-gendered language. This bot will periodically suggest that you consider using alternate phrasing that is less gender-specific. This is one of those times. " self.reply(response) def conditional(self, message): print('Mode "conditional" not yet implemented') def eprime(self, message): print('Mode "eprime" not yet implemented') if client.rtm_connect(): langbot = LanguageBot(client) langbot.enable('gender') while True: incoming = client.rtm_read() if incoming: langbot.new_incoming(incoming) time.sleep(1) else: print("Connection Failed")
class SmashBot(): def __init__(self): self.slack_client = SlackClient(bot_config.get_slack_api_key()) self.logger = logging.getLogger('smashbot') hdlr = logging.FileHandler(bot_config.get_log_path()) formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s') hdlr.setFormatter(formatter) self.logger.addHandler(hdlr) self.logger.setLevel(logging.DEBUG) self.logger.debug('booting up smashbot file') def keepalive(self): while True: time.sleep(3) try: self.slack_client.server.ping() except WebSocketConnectionClosedException as e: self.logger.debug('Keep alive web socket exception.') self.slack_client.rtm_connect() def print_help(self, channel): message = 'I support the following:' message = message + '\n`@sul me over @them 3-2` or `@sul @them over me 3-2` - report a score' message = message + '\n`@sul group a` - see the current rankings of a group' # message = message + '\n`@sul leaderboard` - see the leaderboard, sorted by winrate' # message = message + '\n`@sul loserboard` - see the loserboard, sorted by winrate' message = message + '\n`@sul who do i play` - see who you play this week (only in dms)' message = message + '\n`@sul matches for week` - see all matches occuring this week in all groups' # message = message + '\n`@sul my total stats` - see your total wins and losses (both games and sets)' self.slack_client.api_call("chat.postMessage", channel=channel, text=message, as_user=True) ### TODO Refactor so it can support Bo3 AND Bo5 across the database def get_leaderboard(self, reverse_order=True): matches = db.get_matches() players = db.get_players() player_dict = dict() for player in players: player_dict[player.slack_id] = { 'games_won': 0, 'games_total': 0, 'name': player.name } for match in matches: games_played = match.sets player_1 = player_dict[match.player_1_id] player_2 = player_dict[match.player_2_id] player_dict[match.player_1_id][ 'games_total'] = player_1['games_total'] + games_played player_dict[match.player_2_id][ 'games_total'] = player_2['games_total'] + games_played if match.player_1_id == match.winner_id: player_dict[ match.player_1_id]['games_won'] = player_1['games_won'] + 2 if games_played == 3: player_dict[match.player_2_id][ 'games_won'] = player_2['games_won'] + 1 elif match.player_2_id == match.winner_id: player_dict[ match.player_2_id]['games_won'] = player_2['games_won'] + 2 if games_played == 3: player_dict[match.player_1_id][ 'games_won'] = player_1['games_won'] + 1 winrate_dict = dict() for player_id, player in player_dict.items(): if player['games_total'] == 0: winrate_dict[player['name']] = { 'games_won': 0, 'games_lost': 0, 'winrate': round(0, 2) } else: winrate_dict[player['name']] = { 'games_won': player['games_won'], 'games_lost': player['games_total'] - player['games_won'], 'winrate': round((player['games_won'] / player['games_total']) * 100, 2) } sorted_winrates = collections.OrderedDict( sorted(winrate_dict.items(), key=lambda x: x[1]['winrate'], reverse=reverse_order)) return sorted_winrates ### TODO Refactor so it can support Bo3 AND Bo5 across the database def print_leaderboard(self, channel): sorted_winrates = self.get_leaderboard() message = "" for player_name in list(sorted_winrates)[:10]: player_object = sorted_winrates[player_name] message = message + f"\n {player_name}: {player_object['winrate']}% ({player_object['games_won']}-{player_object['games_lost']})" self.slack_client.api_call("chat.postMessage", channel=channel, text=message, as_user=True) ### TODO Refactor so it can support Bo3 AND Bo5 across the database def print_loserboard(self, channel): sorted_winrates = self.get_leaderboard(False) message = "" for player_name in list(sorted_winrates)[:10]: player_object = sorted_winrates[player_name] message = message + f"\n {player_name}: {player_object['winrate']}% ({player_object['games_won']}-{player_object['games_lost']})" self.slack_client.api_call("chat.postMessage", channel=channel, text=message, as_user=True) def print_whole_week(self, channel, date): all_weekly_matches = db.get_matches_for_week(date) players = db.get_players() message = "" for match in all_weekly_matches: message = message + f"\n {get_player_name(players, match.player_1_id)} vs. {get_player_name(players, match.player_2_id)} : week: {match.week}" self.slack_client.api_call("chat.postMessage", channel=channel, text=message, as_user=True) def print_user_week(self, user_id, channel, date): all_weekly_matches = db.get_matches_for_week(date) players = db.get_players() user_match_dict = dict() for match in all_weekly_matches: if match.player_1_id == user_id: user_match_dict[get_player_name( players, match.player_2_id)] = match.week elif match.player_2_id == user_id: user_match_dict[get_player_name( players, match.player_1_id)] = match.week message = "" for player, week in user_match_dict.items(): message = message + f"\n Playing: {player} | week: {week}" self.slack_client.api_call("chat.postMessage", channel=channel, text=message, as_user=True) ### TODO Refactor so it can support Bo3 AND Bo5 across the database def print_user_stats(self, user_id, channel): all_matches = db.get_matches() total_won_matches = 0 total_lost_matches = 0 total_won_sets = 0 total_lost_sets = 0 for match in all_matches: if match.winner_id is None: continue elif user_id == match.winner_id: total_won_matches += 1 total_won_sets += 2 if match.sets == 3: total_lost_sets += 1 elif (match.player_1_id == user_id or match.player_2_id == user_id) and user_id != match.winner_id: total_lost_matches += 1 total_lost_sets += 2 if match.sets == 3: total_won_sets += 1 message = f"\n Matches Won: {total_won_matches} | Matches Lost: {total_lost_matches} | Sets Won: {total_won_sets} | Sets Lost: {total_lost_sets}" self.slack_client.api_call("chat.postMessage", channel=channel, text=message, as_user=True) def print_group(self, channel, group): try: season = db.get_current_season() all_matches = db.get_matches_for_season(season) all_players = db.get_players() group_matches = [ m for m in all_matches if m.grouping.lower() == group.lower() ] if not len(group_matches): raise Exception('Not a match') players = gather_scores(group_matches) message = 'Group ' + group.upper() + ':' for p in players: message += '\n' + get_player_name( all_players, p['player_id']) + ' ' + str( p['m_w']) + '-' + str(p['m_l']) message += ' (' + str(p['s_w']) + '-' + str(p['s_l']) + ')' self.slack_client.api_call("chat.postMessage", channel=channel, text=message, as_user=True) except Exception as e: self.logger.debug(e) self.slack_client.api_call("chat.postMessage", channel=channel, text="Not a group (or I messed up).", as_user=True) def parse_first_slack_id(self, message): return message[message.index('<@') + 2:message.index('>')].upper() def parse_second_slack_id(self, message): message = message[message.index('>') + 1:] return self.parse_first_slack_id(message) def parse_score(self, message): dash_index = message.index('-') score_substring = message[dash_index - 1:dash_index + 2] if score_substring != "3-0" and score_substring != "3-1" and score_substring != "3-2": raise Exception("Malformed score") score_1 = int(score_substring[0]) score_2 = int(score_substring[2]) return score_1, score_2 def parse_message(self, command, poster): isAdmin = poster == bot_config.get_commissioner_slack_id() if command.startswith('me over '): winner = poster loser = self.parse_first_slack_id(command) elif command.startswith('<@') and command.index('over me') > 0: winner = self.parse_first_slack_id(command) loser = poster elif isAdmin and command.startswith('<@'): winner = self.parse_first_slack_id(command) loser = self.parse_second_slack_id(command) else: self.logger.debug('Bad message format') return None if winner == loser: self.logger.debug('Cant play against yourself') return None try: score_1, score_2 = self.parse_score(command) except Exception as e: self.logger.debug('Malformed score', e) return None return { 'winner_id': winner, 'loser_id': loser, 'score_total': (score_1 + score_2) } def enter_score(self, winner_id, loser_id, score_total, channel, timestamp): try: if not db.update_match_by_id(winner_id, loser_id, score_total): self.slack_client.api_call( "chat.postMessage", channel=channel, text='Not a match I have (or I messed up).', as_user=True) self.slack_client.api_call("reactions.add", name="x", channel=channel, timestamp=timestamp) return self.slack_client.api_call( "chat.postMessage", channel=bot_config.get_commissioner_slack_id(), text='Entered into db', as_user=True) self.slack_client.api_call("reactions.add", name="white_check_mark", channel=channel, timestamp=timestamp) except Exception as e: self.slack_client.api_call( "chat.postMessage", channel=bot_config.get_commissioner_slack_id(), text='Failed to enter into db', as_user=True) self.slack_client.api_call("reactions.add", name="x", channel=channel, timestamp=timestamp) self.logger.error(e) def filter_invalid_messages(self, message_list): valid_messages = [] for message_object in message_list: if message_object is None: continue if 'text' not in message_object or 'channel' not in message_object or 'user' not in message_object or 'ts' not in message_object: continue if 'bot_id' in message_object: continue message_text = message_object['text'] if message_object['channel'][:1] == 'D': if message_text.startswith('<@' + bot_config.get_bot_slack_user_id() + '>'): message_text = message_text[message_text.index(">") + 1:].strip() message_object['text'] = message_text valid_messages.append(message_object) continue if message_object['channel'] == bot_config.get_channel_slack_id( ) and message_text.startswith('<@' + bot_config.get_bot_slack_user_id() + '>'): message_text = message_text[message_text.index(">") + 1:].strip() message_object['text'] = message_text valid_messages.append(message_object) continue return valid_messages def handle_message(self, message_object): command = message_object["text"] channel = message_object["channel"] user_id = message_object["user"] timestamp = float(message_object["ts"]) user_date = datetime.fromtimestamp(timestamp).date() """" if command == 'leaderboard': self.print_leaderboard(channel) elif command == 'loserboard' or command == 'troy': self.print_loserboard(channel) elif command == 'my total stats' and channel[:1] == 'D': self.print_user_stats(user_id, channel) """ if command == 'matches for week': self.print_whole_week(channel, user_date) elif command == 'who do i play' and channel[:1] == 'D': self.print_user_week(user_id, channel, user_date) elif command == 'help': self.print_help(channel) elif command.startswith('group'): self.print_group(channel, command[6]) else: result = None try: result = self.parse_message(command, user_id) except Exception as e: self.logger.debug(e) if result is None: format_msg = "Didn't catch that. The format is `@sul me over @them 3-2` or `@sul @them over me 3-2`." self.slack_client.api_call("chat.postMessage", channel=channel, text=format_msg, as_user=True) elif result is not None and channel[:1] == 'D': format_msg = "Nice try, you have to put this in the main channel" self.slack_client.api_call('chat.postMessage', channel=channel, text=format_msg, as_user=True) elif result is not None and channel == bot_config.get_channel_slack_id( ): self.enter_score(result['winner_id'], result['loser_id'], result['score_total'], channel, message_object["ts"]) player = db.get_player_by_id(result['winner_id']) self.print_group(channel, player.grouping) return None def start_bot(self): p = Process(target=self.keepalive) p.start() if self.slack_client.rtm_connect(): print("StarterBot connected and running!") while True: try: message_list = self.slack_client.rtm_read() message_list = self.filter_invalid_messages(message_list) for message in message_list: try: self.handle_message(message) except Exception as e: self.logger.debug(e) self.slack_client.api_call( "reactions.add", name="x", channel=message["channel"], timestamp=message["ts"]) time.sleep(1) except Exception as e: self.logger.debug('Main while loop web socket exception.', e) self.slack_client.rtm_connect() else: print("Connection failed. Invalid Slack token or bot ID?")
def main(): sc = SlackClient(SLACK_BOT_TOKEN) error_msg = json.dumps([{ "color": "#e74c3c", "attachment_type": "default", "text": "", "image_url": "http://i2.kym-cdn.com/photos/images/original/000/329/784/bd6.jpg" }]) # Connect to slack if sc.rtm_connect(): while True: # Listen for any latest events for slack_event in sc.rtm_read(): message = slack_event.get("text") user = slack_event.get("user") channel = slack_event.get("channel") if (message and user): if (SLACK_BOT_NAME in message): movieName = message[13:] if (len(movieName.strip()) == 0): sc.api_call("chat.postMessage", channel=channel, text="", attachments=error_msg, as_user=True) else: try: url = "http://www.omdbapi.com/?t=" + message[ 13:] response = requests.get(url) if response.status_code == 200: data = response.json() print "Calling " + url intro_msg = json.dumps([{ "fallback": "There seems to be some issue with displaying the data", "title": message[13:], "color": "#50e043", "attachment_type": "default", "text": data["Plot"], "fields": [{ "title": "Title", "value": data["Title"], "short": True }, { "title": "Actors", "value": data["Actors"], "short": True }, { "title": "Released", "value": data["Released"], "short": True }, { "title": "Rated", "value": data["Rated"], "short": True }, { "title": "IMDB Ratings", "value": data["Ratings"][0]["Value"], "short": True }], "image_url": data["Poster"] }]) sc.api_call( "chat.postMessage", channel=channel, text="Here is some information about " + message[13:], attachments=intro_msg, as_user=True) except: sc.rtm_send_message( channel, "Hey " + "<@" + user + "> !" + " I couldn't find this movie") else: sc.rtm_send_message(channel, "")
class TaskBot: def __init__(self, slack_token, db_url, task_manager: TaskManager): print("token: {}".format(slack_token)) print("db url: {}".format(db_url)) self.slack_client = SlackClient(slack_token) self.starterbot_id = None self.im_channel_id = None client = pymongo.MongoClient(db_url, 27017) db = client['test_db'] self.task_collection = db.task_list self.done_collection = db.done_list self.task_manager = task_manager print("current_position: {}".format(self.task_manager.position)) self.displayed_task = None def parse_bot_command(self, slack_events): """ Parses a list of events coming from the Slack RTM API to find bot commands. If a bot command is found, this function returns a tuple of command and channel. If its not found, then this function returns None, None. """ print(slack_events) for event in slack_events: if event["type"] == "message" and not "subtype" in event: if event["channel"] == self.im_channel_id: return event["text"], event["channel"] user_id, message = self.parse_direct_mention(event["text"]) if user_id == self.starterbot_id: return message, event["channel"] return None, None def parse_direct_mention(self, message_text): """ Finds a direct mention (a mention that is at the beginning) in message text and returns the user ID which was mentioned. If there is no direct mention, returns None """ matches = re.search(MENTION_REGEX, message_text) # the first group contains the username, the second group contains the remaining message return (matches.group(1), matches.group(2).strip()) if matches else (None, None) def show_next(self): self.displayed_task = self.task_manager.next(datetime.datetime.now()) return self.displayed_task.description #def done_task(self, task_name): def handle_command(self, command: str, channel, condition=None): """ Executes bot command if the command is known """ # Default response is help text for the user default_response = "Not sure what you mean." # Finds and executes the given command, filling in response response = "" # Default timestamp timestamp = datetime.datetime.now() # This is where you start to implement more commands! # for handler in task_handlers: # if command.startswith(handler.TASK_COMMAND): # handler.handle_request(command, condition) if command.startswith("next"): response = "next task is {}.".format(self.show_next()) elif command.startswith("done"): commands = command.split(' ') if len(commands) > 1: if is_int(commands[1]): try: target_task = self.task_manager.get_task_by_index(int(commands[1])) response = "{} is done! Well done!\n".format(target_task.description) self.task_manager.done_task(target_task, timestamp) self.task_manager.update_task_list() response += self.show_next() except ValueError as e: response = e.args[0] else: try: self.task_manager.done_task_by_name(commands[1], timestamp) self.task_manager.update_task_list() response = "{} is done! Well done!\n".format(commands[1]) response += self.show_next() except ValueError as e: response = e.args[0] else: self.task_manager.done_task_by_index(0, timestamp) self.task_manager.update_task_list() response = "{} is done! Well done!\n".format(self.displayed_task.description) response += self.show_next() elif command.startswith("postpone"): self.task_manager.postpone(self.displayed_task) response = "postponed {}.\n".format(self.displayed_task.description) response += self.show_next() elif command.startswith("adddaily"): commands = command.split(' ') if len(commands) > 1: if len(commands) > 2 and is_int(commands[2]): frequency = int(commands[2]) else: frequency = 5 gen = {'task_type': 'Unscheduled', 'last_done': datetime.datetime(2018, 9, 16, 12, 30, 0), 'frequency': frequency, 'task': {'priority': 4, 'due': datetime.datetime(2018, 1, 10, 10, 0, 0), 'time_needed': 15, 'description': commands[1], 'time_slot': 'any', 'cancellable': False, 'position': 'home'}} try: self.task_manager.insert_generator(gen) response = "A task {} is added!".format(commands[1]) except: response = "Failed to add task. Something wrong!" elif command.startswith("top"): commands = command.split(' ') length = 10 if len(commands) > 1: try: length = int(commands[1]) except ValueError: length = 10 tasks = self.task_manager.top(length) response = "task list:\n" for index, task in enumerate(tasks): response += "{} {}: {}, {}\n".format(index, task.description, task.priority, task.due.date()) elif command.startswith("task"): print(command) dummy, args = parse_command(['task'], command) print(args) self.task_manager.add_task(args[0]) # Sends the response back to the channel self.slack_client.api_call( "chat.postMessage", channel=channel, text=response or default_response#, # attachments=BUTTON_JSON['attachments'] ) def handle_command_test(self, command_line: str, channel, condition=None): """ Executes bot command if the command is known """ default_response = "Not sure what you mean." # Finds and executes the given command, filling in response response = "" # Default timestamp timestamp = datetime.datetime.now() # This is where you start to implement more commands! # for handler in task_handlers: # if command.startswith(handler.TASK_COMMAND): # handler.handle_request(command, condition) command_set = {'next': self.show_next, 'done': self.done_task , } command, arg = parse_command(command_set.keys(), command_line) if command.startswith("next"): response = "next task is {}.".format(self.show_next()) elif command.startswith("done"): commands = command.split(' ') if len(commands) > 1: if is_int(commands[1]): try: self.task_manager.done_task_by_index(int(commands[1]), timestamp) self.task_manager.update_task_list() response = "{} is done! Well done!\n".format(self.task_manager.get_task_by_index(int(commands[1])).description) response += self.show_next() except ValueError as e: response = e.args[0] else: try: self.task_manager.done_task_by_name(commands[1], timestamp) self.task_manager.update_task_list() response = "{} is done! Well done!\n".format(commands[1]) response += self.show_next() except ValueError as e: response = e.args[0] else: self.task_manager.done_task_by_index(0, timestamp) self.task_manager.update_task_list() response = "{} is done! Well done!\n".format(self.displayed_task.description) response += self.show_next() elif command.startswith("postpone"): self.task_manager.postpone(self.displayed_task) response = "postponed {}.\n".format(self.displayed_task.description) response += self.show_next() elif command.startswith("adddaily"): commands = command.split(' ') if len(commands) > 1: if len(commands) > 2 and is_int(commands[2]): frequency = int(commands[2]) else: frequency = 5 gen = {'task_type': 'Unscheduled', 'last_done': datetime.datetime(2018, 9, 16, 12, 30, 0), 'frequency': frequency, 'task': {'priority': 4, 'due': datetime.datetime(2018, 1, 10, 10, 0, 0), 'time_needed': 15, 'description': commands[1], 'time_slot': 'any', 'cancellable': False, 'position': 'home'}} try: self.task_manager.insert_generator(gen) response = "A task {} is added!".format(commands[1]) except: response = "Failed to add task. Something wrong!" elif command.startswith("top"): commands = command.split(' ') length = 10 if len(commands) > 1: try: length = int(commands[1]) except ValueError: length = 10 tasks = self.task_manager.top(length) response = "task list:\n" for index, task in enumerate(tasks): response += "{} {}: {}\n".format(index, task.description, task.priority) elif command.startswith("task"): print(command) dummy, args = parse_command(['task'], command) print(args) self.task_manager.add_task(args[0]) def run(self): if self.slack_client.rtm_connect(with_team_state=False): print("Starter Bot connected and running!") self.starterbot_id = self.slack_client.api_call("auth.test")["user_id"] self.im_channel_id = self.slack_client.api_call("im.list")["ims"][1]["id"] while True: raw_command, channel = self.parse_bot_command(self.slack_client.rtm_read()) if raw_command: self.handle_command(raw_command, channel) # morning_tasks # if morning_obj.is_morning(datetime.datetime.now()): # tasks = task_collection.find({"time_slot": "morning"}) # task_list = "Good morning! let's begin moving!\n" # for task in tasks: # task_list += u"{}\n".format(task["description"]) # # Sends the response back to the channel # slack_client.api_call( # "chat.postMessage", # channel=im_channel_id, # text=task_list, # ) time.sleep(RTM_READ_DELAY) else: print("Connection failed. Exception traceback printed above.")
class Robot(object): def __init__(self): self.client = SlackClient(SLACK_TOKEN) self.brain = RedisBrain() self.apps, self.docs = self.load_apps() def load_apps(self): docs = ['=' * 14, 'Usage', '=' * 14] apps = {} for name in APPS: app = import_module('apps.%s' % name) docs.append('{0}{1}: {2}'.format(CMD_PREFIX, ', '.join(app.run.commands), app.run.__doc__)) for command in app.run.commands: apps[command] = app return apps, docs def handle_message(self, message): channel, user, text = message command, payloads = self.extract_command(text) if not command: return app = self.apps.get(command, None) if not app: return try: pool.apply_async(func=app.run, args=(self, channel, user, payloads)) except: traceback.print_exc() def extract_messages(self, events): messages = [] for event in events: channel = event.get('channel', '') user = event.get('user', '') text = event.get('text', '') if channel and user and text: messages.append((channel, user, text)) return messages def extract_command(self, text): if CMD_PREFIX and CMD_PREFIX != text[0]: return (None, None) tokens = text.split(' ', 1) if 1 < len(tokens): return tokens[0][CMD_LENGTH:], tokens[1] else: return (text[CMD_LENGTH:], '') def rtm_connect(self): conn = None try: conn = self.client.rtm_connect() except: logger.error(traceback.format_exc()) else: return conn def read_message(self): events = None try: events = self.client.rtm_read() except: logger.error(traceback.format_exc()) self.rtm_connect() return events def run(self): if not self.rtm_connect(): raise RuntimeError( 'Can not connect to slack client. Check your settings.') while True: events = self.read_message() if events: messages = self.extract_messages(events) for message in messages: self.handle_message(message) gevent.sleep(0.3)
log.info("Message: %s" % args.message) log.info("Username: %s" % args.username) slack_client.api_call("chat.postMessage", as_user=False, username=args.username, icon_emoji=":%s:" % args.emoji, channel=args.convo, text=args.message) sys.exit(" ... exiting") if __name__ == "__main__": if slack_client.rtm_connect(with_team_state=False): try: handler = Handler(config) while True: handler.handle_events(slack_client.rtm_read()) time.sleep(RTM_READ_DELAY) except (KeyboardInterrupt, SystemExit) as e: config.pop('commands') config.pop('plugins') config.dump(os.path.join(script_path, 'slack_settings.json')) # persistence.close_db() slack_client.api_call("chat.postMessage", as_user=True, channel=config.get('slackbot.botchannel.id'), text='Bye bye ... Reason: %s' % str(e)) sys.exit(" ... exiting") else:
def _fetch_slack_blessing(self, slack_token: Text, slack_channel_id: Text, model_uri: Text) -> _SlackResponse: """Send message via Slack channel and wait for response. Args: slack_token: The user-defined function to obtain token to send and receive messages. slack_channel_id: The id of the Slack channel to send and receive messages. model_uri: The URI of the model waiting for human review. Returns: A _SlackResponse instance. Raises: ConnectionError: When connection to slack server cannot be established. """ sc = SlackClient(slack_token) msg = _NOTIFY_MODEL_REVIEW_TEMPLATE.format(model_uri) ts = 0 if not sc.rtm_connect(): msg = 'Cannot connect to slack server with given token' absl.logging.error(msg) raise ConnectionError(msg) # pylint: disable=undefined-variable sc.rtm_send_message(slack_channel_id, message=msg) while sc.server.connected: payload_list = sc.rtm_read() if not payload_list: continue for payload in payload_list: if payload.get('ok') and payload.get( 'reply_to') == 0 and not ts: ts = payload['ts'] continue if not self._is_valid_message(payload, slack_channel_id, ts): continue if payload.get('text').lower() in _APPROVE_TEXT: absl.logging.info( 'User %s approves the model located at %s', payload.get('user'), model_uri) return _SlackResponse(True, payload.get('user'), payload.get('text'), slack_channel_id, str(ts)) elif payload.get('text').lower() in _DECLINE_TEXT: absl.logging.info( 'User %s declines the model located at %s', payload.get('user'), model_uri) return _SlackResponse(False, payload.get('user'), payload.get('text'), slack_channel_id, str(ts)) else: unrecognized_text = payload.get('text') absl.logging.info('Unrecognized response: %s', unrecognized_text) sc.rtm_send_message( slack_channel_id, message=_NOTIFY_CORRECT_REPLY_TEMPLATE.format( unrecognized_text), thread=ts)
if response != False and response != None: logger_response = response.replace('\n', ' ')[:20] logger.info(f"Response: {logger_response}...") SLCKCLNT.api_call( "chat.postMessage", channel=event['channel'], text=response or "What was that? :: Try: " + ", ".join([x for x in CMMDS.keys()])) if response == None: SLCKCLNT.api_call("chat.postMessage", channel=event['channel'], text="What was that? :: Try: " + ", ".join([x for x in CMMDS.keys()])) if __name__ == "__main__": if SLCKCLNT.rtm_connect(with_team_state=False): SLCKBTD = SLCKCLNT.api_call("auth.test")["user_id"] logger.info(f"Bot connected {SLCKBTD}") while True: command, channel = parse_incoming(SLCKCLNT.rtm_read()) if command: handle_command(command, channel) time.sleep(RTM_READ_DELAY) else: logger.exception("Connection Failed")
process = subprocess.Popen(cmd.split(), shell=True, stdout=subprocess.PIPE) for line in process.stdout: response = line.decode("ascii", "replace") # Sends the response back to the channel slack_client.api_call("chat.postMessage", channel=channel, text=response or default_response) return # Sends the response back to the channel slack_client.api_call("chat.postMessage", channel=channel, text=response or default_response) if __name__ == "__main__": if slack_client.rtm_connect(with_team_state=False): print("Starter Bot connected and running!") # Read bot's user ID by calling Web API method `auth.test` starterbot_id = slack_client.api_call("auth.test")["user_id"] print(starterbot_id) while True: command, channel = parse_bot_commands(slack_client.rtm_read()) if command: handle_command(command, channel) time.sleep(RTM_READ_DELAY) else: print("Connection failed. Exception traceback printed above.")
import os import time import api_functions from slackclient import SlackClient # dogeBot's ID as an environment variable BOT_ID = os.environ.get("BOT_ID") # constants AT_BOT = "<@" + BOT_ID + ">" EXAMPLE_COMMAND = "do" # instantiate Slack & Twilio clients slack_client = SlackClient(os.environ.get('SLACK_BOT_TOKEN')) if __name__ == "__main__": READ_WEBSOCKET_DELAY = 1 # 1 second delay between reading from firehose if slack_client.rtm_connect(): print("dogeBot connected and running!") while True: command, channel = api_functions.parse_slack_output( slack_client.rtm_read(), AT_BOT) if command and channel: api_functions.handle_command(command, channel, EXAMPLE_COMMAND, slack_client) time.sleep(READ_WEBSOCKET_DELAY) else: print("Connection failed. Invalid Slack token or bot ID?")