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 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 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 init(): if len(sys.argv) != 3: print 'usage: %s <sysdig-token> <slack-token>' % sys.argv[0] sys.exit(1) else: sdc_token = sys.argv[1] slack_token = sys.argv[2] # # Instantiate the SDC client and Retrieve the SDC user information to make sure we have a valid connection # sdclient = SdcClient(sdc_token) # # Make a connection to the slack API # sc = SlackClient(slack_token) sc.rtm_connect() slack_id = json.loads(sc.api_call('auth.test'))['user_id'] # # Start talking! # dude = SlackBuddy(sdclient, sc, slack_id) dude.run()
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))
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 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 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 send_message(): channel = request.form['channel'] message = request.form['message'] access_token = request.form['access_token'] #Ideally, the only parameters taken would be origin user + message. This can be worked on in a further iteration. sc = SlackClient(access_token) sc.rtm_connect() sc.rtm_send_message(channel=channel, message=message) return "Success"
class Converser: topics = {} client = None debug = False my_user_name = '' def connect(self, token): self.client = SlackClient(token) self.client.rtm_connect() self.my_user_name = self.client.server.username print ("Connected to Slack!") def listen(self): while True: try: input = self.client.rtm_read() if input: for action in input: if self.debug: print(action) if 'type' in action and action['type'] == "message": print ("user name: ", self.my_user_name) print (action['text'].lower()) if 'text' in action and action['text'].lower().startswith(self.my_user_name): print("I was called") self.process_message(action) else: sleep(1) except Exception as e: print ("Exception: ", e.message) def process_message(self, message): for topic in self.topics.keys(): if topic.lower() in message['text'].lower(): response = self.topics[topic].format(**message) if response.startswith("sys:"): response = os.popen(response[4:]).read() print("Posting to [%s]: %s" % (message['channel'], response)) self.post(message['channel'], response) 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 run(sc: SlackClient, channel: str, message: str, retries: int, logger: logging.Logger) -> None: if sc.rtm_connect(): logger.info("Connected to Slack") channel_id = find_channel_id(channel, sc) logger.debug(f"Found channel ID {channel_id} for #{channel}") logger.info(f"Listening for joins in #{channel}") retry_count = 0 backoff = 0.5 while True: try: # Handle dem events! for event in sc.rtm_read(): handle_event(event, channel, channel_id, message, sc, logger) # Reset exponential backoff retry strategy every time we # successfully loop. Failure would have happened in rtm_read() retry_count = 0 time.sleep(0.5) # This is necessary to handle an error caused by a bug in Slack's # Python client. For more information see # https://github.com/slackhq/python-slackclient/issues/127 # # The TimeoutError could be more elegantly resolved by making a PR # to the websocket-client library and letting them coerce that # exception to a WebSocketTimeoutException. except (websocket.WebSocketConnectionClosedException, TimeoutError): logger.error("Lost connection to Slack, reconnecting...") if not sc.rtm_connect(): logger.info("Failed to reconnect to Slack") if retry_count >= retries: sys.exit(bail( 'fatal', 'red', "Too many failed reconnect attempts, shutting down") ) time.sleep((backoff ** 2) / 4) else: logger.info("Reconnected to Slack") retry_count += 1 else: sys.exit(bail('fatal', 'red', "Couldn't connect to Slack"))
class RtmBot(object): def __init__(self, token): self.last_ping = 0 self.token = token self.slack_client = None self.plugins = [] for name in plugins.__all__: plugin_cls = getattr(plugins, name) self.plugins.append(plugin_cls()) 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() while True: for reply in self.slack_client.rtm_read(): self.input(reply) self.output() self.auto_ping() time.sleep(.1) def auto_ping(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): print(data) for plugin in self.plugins: plugin.do(data) def output(self): for plugin in self.plugins: limiter = False for (channel_code, message) in plugin.outputs: channel = self.slack_client.server.channels.find(channel_code) if channel is not None and message is not None: if limiter is True: time.sleep(.1) limiter = False channel.send_message(message) limiter = True plugin.outputs = []
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 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?"
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)
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 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)
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 create_slack_client(token): slack_client = SlackClient(token) if not slack_client.rtm_connect(): raise Exception('Cannot connect to Slack') clients.slack = slack_client tokens.slack = token return slack_client
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)
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 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 StandupBot: def __init__(self, token): self.token = token self.slack_client = None self.standup = None self.last_ping = 0 self.keepalive_timer = 3 print("Standup Bot initialized.") def connect(self): self.slack_client = SlackClient(self.token) self.slack_client.rtm_connect() def keepalive(self): now = int(time.time()) if now > self.last_ping + self.keepalive_timer: self.slack_client.server.ping() self.last_ping = now def start(self): self.connect() while True: for response in self.slack_client.rtm_read(): self.response_handler(response) self.keepalive() time.sleep(.5) def message_handler(self, channel, message): if "!standup" in message: # check to see if self.Standup is already initialized, and if so, if the standup is already started. if self.standup is None: self.slack_client.rtm_send_message(channel=channel, message="Hello <!channel>, it's time for Standup!") self.standup = Standup(channel, self.slack_client) elif self.standup is not None & self.standup.status is "INITIALIZED": self.slack_client.rtm_send_message([channel, "I will restart the meeting."]) self.standup = Standup(channel, self.slack_client) elif "!start" in message: if self.standup is not None: self.standup.start_standup() def response_handler(self, response): print(response) if "type" in response and "subtype" not in response: if "message" in response["type"]: self.message_handler(channel=response["channel"], message=response["text"])
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 Bot: def __init__(self, token): self.token = token print("Bot instantiated. My token is: "+self.token+".") def connect(self): print("Connecting with token: "+self.token+"...") self.client = SlackClient(self.token) self.client.rtm_connect() is_ok = self.client.api_call("api.test") is_ok = is_ok["ok"] if is_ok == True: print("Connection established!") else: print("Error code : 1. Connection failure. Check access token?") sys.exit() def print_chat(self, msg, channel): self.client.rtm_send_message(channel, msg) def read_msg(self): msg = self.client.rtm_read() if len(msg) > 0: msg = msg[0] if "type" in msg: if msg["type"] == "message": return msg return [] else: return [] def hello(self): self.client.api_call("chat.postMessage", token=self.token, channel="#general", text="Hello World", as_user=True) def id2name(self, id): users = self.client.api_call("users.list", token=self.token) users = users["members"] for user in users: if user["id"] == id: username = user["name"] return "@"+username
def init(): sdc_token = None try: sdc_token = os.environ["SYSDIG_API_TOKEN"] except KeyError: pass slack_token = None try: slack_token = os.environ["SLACK_TOKEN"] except KeyError: pass parser = argparse.ArgumentParser(description='Sysdigbot: the Sysdig Cloud Slack bot.') parser.add_argument('--sysdig-api-token', dest='sdc_token', required=(sdc_token is None), default=sdc_token, type=str, help='Sysdig API Token, you can use also SYSDIG_API_TOKEN environment variable to set it') parser.add_argument('--slack-token', dest='slack_token', required=(slack_token is None), default=slack_token, type=str, help='Slack Token, you can use also SLACK_TOKEN environment variable to set it') parser.add_argument('--quiet', dest='quiet', action='store_true', help='Prevents the bot from printing output on channels, which is useful to avoid any kind of channel pollution') parser.add_argument('--no-auto-events', dest='auto_events', action='store_false', help='By default Sysdigbot converts every message in a channel in a Sysdig Cloud event, this flag disables it') parser.add_argument('--log-level', dest='log_level', type=LogLevelFromString, help='Logging level, available values: debug, info, warning, error') args = parser.parse_args() logging.basicConfig(format="%(asctime)s - %(levelname)s - %(message)s", level=args.log_level) # requests generates too noise on information level logging.getLogger("requests").setLevel(logging.WARNING) logging.getLogger("urllib3").setLevel(logging.WARNING) logging.debug("Starting Sysdigbot, config=%s", repr(args)) # # Instantiate the SDC client and Retrieve the SDC user information to make sure we have a valid connection # sdclient = SdcClient(args.sdc_token) # # Make a connection to the slack API # sc = SlackClient(args.slack_token) sc.rtm_connect() sinfo = sc.api_call('auth.test') slack_id = sinfo['user_id'] # # Start talking! # dude = SlackBuddy(sdclient, sc, slack_id, args.quiet) dude.auto_events = args.auto_events dude.run()
class ChatBackend(object): """Interface for listening on slack RTM api and shoot events!""" def __init__(self): self.socket_connections = 0 self.users = {} self.sc = SlackClient(SLACK_API_TOKEN) self.connected_to_rtm = False self.channel_info = {} def send_to_slack(self, msg): msg = json.loads(msg) post_data = { "channel": SLACK_CHANNEL, "text": msg["text"], "username": msg["username"], "icon_url" : self.gravatarUrl(msg["username"]) } self.sc.server.api_call("chat.postMessage", **post_data) def send_to_console(self, msg): """Send given data to the registered client. Automatically discards invalid connections.""" print "Received a message through slack API: %s" % msg if(msg.get("subtype","") == "bot_message"): msg.update({ "real_name" : "bot", "profile": { "image_32" : msg["icons"]["image_48"] } }) elif("ok" in self.users and self.users["ok"] and "user" in msg): user = next((user for user in self.users["members"] if user['id'] == msg["user"]), None) msg.update(user) if user is not None else "" socketio.emit('message', msg, namespace='/receive') def run(self): """Listens for new messages in slack, and sends them to clients.""" self.users = json.loads(self.sc.server.api_call('users.list')) self.update_member_count() if self.sc.rtm_connect(): print "Connected to slack RTM" socketio.emit('rtm_connected', namespace='/receive') self.connected_to_rtm = True while True: for msg in self.sc.rtm_read(): if msg["type"] == "message" and msg["channel"] == SLACK_CHANNEL: gevent.spawn(self.send_to_console , msg) time.sleep(1) gevent.sleep() def start(self): """Maintains a slack RTM subscription in the background.""" gevent.spawn(self.run) def update_member_count(self): self.channel_info = json.loads(self.sc.server.api_call("channels.info", **{"channel": SLACK_CHANNEL})) @classmethod def gravatarUrl(self, username): default_gravatar = "http://fedsonslack.com/img/fos_icon.png" grav_url = "https://www.gravatar.com/avatar/%s?default=%s" % (hashlib.md5(username.lower()).hexdigest() , default_gravatar) return grav_url
class RtmBot(FileSystemEventHandler): def __init__(self, config): self.last_ping = 0 self.token = config['SLACK_TOKEN'] self.bot_plugins = [] self.plugin_dir = directory + '/plugins' self.slack_client = None self.reload = True self.pool = None self.pool_size = config['POOL_SIZE'] vv('Started') for plugin in glob.glob(self.plugin_dir + '/*'): sys.path.insert(0, plugin) sys.path.insert(0, self.plugin_dir) def on_modified(self, event): if not self.pool: return self.pool.wait_completion() self.pool = None self.reload = True def connect(self): """Convenience method that creates Server instance""" self.slack_client = SlackClient(self.token) self.connected = False if self.slack_client.rtm_connect(): self.connected = True def start(self): self.connect() # DOC(sumitjami) This code is to executed only once in the lifetime. # when the bot starts executing. if not self.pool: self.pool = ThreadPool(self.pool_size) self.load_plugins() while True: try: for reply in self.slack_client.rtm_read(): self.input(reply) self.crons() except: self.connected = False self.connect() self.crons() else: self.autoping() self.output() time.sleep(.1) if self.reload: self.bot_plugins = [] if not self.pool: self.pool = ThreadPool(self.pool_size) vv('reloading') self.load_plugins() 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 data.get('user'): data['user_object'] = self.slack_client.server.users.find( data['user']) # vvvv('got {}'.format(function_name)) for plugin in self.bot_plugins: # plugin.register_jobs() # vvvv('doing plugin %s' % plugin.name) 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(self.plugin_dir + '/*.py') + glob.glob(self.plugin_dir + '/*/*.py'): name = plugin.split('/')[-1][:-3] plg = None try: plg = Plugin(self, name, plgnid=len(self.bot_plugins) + 1) except Exception, e: logger.exception('error loading plugin %s' % name) continue if not plg.disabled: # vv("Plugin:"+plugin) self.bot_plugins.append(plg) plg.register_jobs() vv('Plugind Enabled %s' % name) else: vv('Plugind Disabled %s' % name) self.reload = False
class SlackBot(): """master slack client that remains alive for the duration of the script. subsidiary connections to SlackClient are made on each connection drop or error""" def __init__(self, config): self.config = config self.token = self.config['api_key'] self.slack_client = None self.name = self.config['bot_name'] self.slack_user_id = None self.direct_message_channels = None self.channel_id = None self.channel_name = None self.master = self.config['master'] self.plugins = self.config.get('plugins', 'plugins').split('\n') logger.info("Plugins installed: " + str(self.plugins)) self.last_ping = 0 self.reconnects = 0 self.error_count = 0 self.run_time = 0 self.run_time_total = 0 self.first_time = True self.auth_check = True self.errors = [] self.ping_frequency = 15 def test_connection(self, verbose=True): """tests whether the device is connected to the internet""" connected = False retries = 0 while connected == False: if verbose: logger.info("Testing internet connection...") try: socket.create_connection(("www.google.com", 80)) if verbose: logger.info("internet working") connected = True return True except (socket.gaierror, socket.error): logger.error("Internet connection down - retrying " + str(retries)) error = utils.ConnectionDrop(self, "internet down") retries += 1 time.sleep((1 + retries)) def generate_client(self): """creates an instance of SlackClient for each connection""" if self.test_connection(): self.reconnects += 1 logger.info("Generating slack_client") # check token is valid self.slack_client = SlackClient(self.token) if self.auth_check: self.auth_check = False if self.slack_client.api_call( "auth.test", token=self.token).get('ok') == False: logger.error("key not recognised") sys.exit("Invalid key.. exiting") logger.info("Token valid - SlackClient generated " + str(self.slack_client)) logger.info("Connecting to RTM...") #test RTM connection try: self.slack_client.rtm_connect() logger.info("Connected to RTM") self.run_time = 0 except Exception as e: logger.error("Error in RTM connection: " + str(e)) logger.warning("Exiting script...") sys.exit(1) logger.info("Getting user & channel IDs") #get list of users, channels and direct message channels channel_list = self.slack_client.api_call("channels.list") self.direct_message_channels = self.slack_client.api_call( "im.list") user_list = self.slack_client.api_call("users.list") for channel in channel_list.get('channels'): if channel.get('is_member'): self.channel_id = str(channel.get('id')) self.channel_name = str(channel.get('name')) for user in user_list.get('members'): if user.get('name') == self.name: self.slack_user_id = user.get('id') logger.info("Bot ID: " + str(self.slack_user_id) + " Channel ID: " + str(self.channel_id) + "/ " + str(self.channel_name)) def say(self, text_message): """simple function to post a message to the bot's channel""" try: self.slack_client.api_call("chat.postMessage", channel=self.channel_id, text=str(text_message), as_user=True) except (websocket.WebSocketConnectionClosedException, socket.error) as e: error = utils.ConnectionDrop(self, "chat connection error") def autoping(self): """pings the slack server as set by the Bot""" now = int(time.time()) if now > self.last_ping + self.ping_frequency: self.slack_client.server.ping() self.last_ping = now def load_plugin(self, name): """loads the plugin for the process method""" plugin = __import__("plugin_%s" % name) return plugin def call_plugin(self, name, message): plugin = self.load_plugin(name) plugin.plugin_main(message, self) def process(self): """checks for connection errors, reads the RTM firehose and parses messages""" self.run_time += 1 self.run_time_total += 1 try: messages = self.slack_client.rtm_read() self.error_count = 0 if self.first_time: self.say("Bot ID:" + str(self.slack_user_id) + " is awake") self.first_time = False if self.errors: drop_period = int(time.time()) - self.errors[0].timestamp self.say("I was offline for " + str(drop_period) + " secs. " + str(len(self.errors)) + "errors.") logger.debug("Offline for " + str(drop_period) + " secs") self.errors = [] except websocket.WebSocketConnectionClosedException: error = utils.ConnectionDrop(self, "websocket drop") self.generate_client() return except socket.error: error = utils.ConnectionDrop(self, "Socket error") time.sleep(5) self.error_count += 1 if self.error_count > 5: self.generate_client() return #checks the message stream for message in messages: #print (message) if message['type'] == 'presence_change': if message['presence'] == 'active': time.sleep(.5) self.say( lex.response('greetings') + " " + str(self.master)) if 'text' in message: if message['text'].startswith( "<@%s>" % self.slack_user_id ) or 'text' in message and message['text'].startswith( "<!%s>" % 'everyone'): #if user issues a command, run through through all plugins message_text = message['text'] for plugin in self.plugins: self.call_plugin(plugin, message_text) self.autoping()
class Bot(System): MENTION_REGEX = "^<@(|[WU].+?)>(.*)" RTM_READ_DELAY = 1 OUT_CHANNEL = "bot" def __init__(self): super().__init__() self.db = DB() self.utils = Utils() channel_list = None self.info_channel_id = None self.p.subscribe("info") self.log.info('start listening redis ') self.slack_client = SlackClient(os.environ.get('SLACK_BOT_TOKEN')) if self.slack_client.rtm_connect(with_team_state=False, auto_reconnect=True): anselm_id = self.slack_client.api_call("auth.test")["user_id"] self.log.info('start listening redis ') channel_list = self.slack_client.api_call("channels.list") if channel_list and 'channels' in channel_list: for channel in channel_list.get('channels'): if channel.get('name') == 'bot': self.log.info("git info channel id") self.info_channel_id = channel.get('id') break def parse_bot_commands(self, slack_events): for event in slack_events: if event["type"] == "message" and not "subtype" in event: user_id, message = self.parse_direct_mention(event["text"]) if user_id == self.anselm_id: return message, event["channel"] return None, None def parse_direct_mention(self, message_text): matches = re.search(self.MENTION_REGEX, message_text) return (matches.group(1), matches.group(2).strip()) if matches else (None, None) def handle_command(self, command, channel): ok = False if command.startswith('to'): ok = True todo_pressures_acc = [] todo_n_acc = [] lines = self.get_lines('cal_id') for line in lines: cal_id = self.aget('cal_id', line) doc = self.db.get_doc(cal_id) todo_dict = self.utils.extract_todo_pressure(doc) todo_pressures_acc, todo_n_acc, todo_unit = self.utils.acc_pressure( todo_dict, todo_pressures_acc, todo_n_acc) if len(todo_pressures_acc) > 0: self.post( channel, "There are {no} todo pressures in total:".format( no=len(todo_pressures_acc))) for i, p in enumerate(todo_pressures_acc): self.post( channel, "№ {no}: {p} {unit} (N = {n})".format( no=i + 1, p=p, unit=todo_unit, n=todo_n_acc[i])) else: msg = "No calibrations selelected so far." self.post(channel, msg) if command.startswith('re'): ok = True todo_pressures_acc = [] todo_n_acc = [] lines = self.get_lines('cal_id') for line in lines: cal_id = self.aget('cal_id', line) doc = self.db.get_doc(cal_id) todo_dict = self.utils.extract_todo_pressure(doc) todo_pressures_acc, todo_n_acc, unit = self.utils.acc_pressure( todo_dict, todo_pressures_acc, todo_n_acc) remaining_pressure_acc = [] remaining_n_acc = [] for line in lines: cal_id = self.aget('cal_id', line) doc = self.db.get_doc(cal_id) target_dict = self.utils.extract_target_pressure(doc) remaining_pressure, remaining_n, unit = self.utils.remaining_pressure( target_dict, todo_pressures_acc, todo_n_acc) remaining_dict = { 'Value': remaining_pressure, 'N': remaining_n, 'Unit': unit } remaining_pressure_acc, remaining_n_acc, unit = self.utils.acc_pressure( remaining_dict, remaining_pressure_acc, remaining_n_acc) if len(remaining_pressure_acc) > 0: self.post( channel, "There following target pressure(s) remain:".format( no=len(remaining_pressure_acc))) for i, p in enumerate(remaining_pressure_acc): self.post( channel, "№ {no}: {p} {unit} (N = {n})".format( no=i + 1, p=p, unit=unit, n=remaining_n_acc[i])) else: msg = "There are no remaining target pressures." self.post(channel, msg) if command.startswith('cu'): ok = True p = self.aget('current_target_pressure', 0) if p: msg = "The current target pressure is {}".format(p) else: msg = "The current target pressure is not set yet" self.post(channel, msg) if command.startswith('ga'): ok = True self.post(channel, "calibration gas is {}".format(self.aget('gas', 0))) if command.startswith('ch'): ok = True self.post( channel, "I send my infos to channel #{}".format(self.OUT_CHANNEL)) if command.startswith('id'): ok = True self.post(channel, "doc ids are:") lines = self.get_lines("cal_id") for line in lines: self.post(channel, self.aget("cal_id", line)) if command.startswith('fu'): ok = True self.post(channel, "fullscale of the devices are:") lines = self.get_lines("fullscale_value") for line in lines: self.post( channel, "{} {} ".format(self.aget("fullscale_value", line), self.aget("fullscale_unit", line))) if command.startswith('he'): ok = True self.post(channel, "Beside *he[lp]* further available commands are:") self.post( channel, "*to[do pressure]*, *re[maining pressures]*, *cu[rrent target pressure]*, *ch[annel]*, *ga[s]*, *fu[llscales]* or *id[s]*." ) if not ok: self.post(channel, "Not sure what you mean. Try *help* command.") def post(self, channel, msg): self.slack_client.api_call("chat.postMessage", channel=channel, text=msg) def msg_in(self): self.log.debug("message in") self.anselm_id = self.slack_client.api_call("auth.test")["user_id"] while True: command, channel = self.parse_bot_commands( self.slack_client.rtm_read()) if command: self.handle_command(command, channel) time.sleep(self.RTM_READ_DELAY) def msg_out(self): if self.info_channel_id: for item in self.p.listen(): self.log.debug("received item: {}".format(item)) if item['type'] == 'message': self.slack_client.api_call("chat.postMessage", channel=self.info_channel_id, text=item.get('data')) else: self.log.error("got no info channel id")
class NeoSlack(object): def __init__(self, nvim): logging.info("initializing class") self.nvim = nvim self.sc = SlackClient(os.environ["SLACK_TOKEN"]) self.channels = self.sc.api_call("channels.list")["channels"] self.users = self.sc.api_call("users.list")["members"] self.channel_buffers = {} def get_buffer(self, buffer_name): ltf("get buffer") return [b for b in self.nvim.buffers if b.name == buffer_name][0] def get_channel_name(self, channel_id): logging.info("getting channel: {}".format(channel_id)) return [ch["name"] for ch in self.channels if ch["id"] == channel_id][0] def get_channel_id(self, channel_name): ltf("getting channel: {}".format(channel_name)) return [ ch["id"] for ch in self.channels if ch["name"] == channel_name ][0] def get_user_name(self, user_id): logging.info("getting users") return [us["name"] for us in self.users if us["id"] == user_id][0] def create_channel_buffers(self): ltf("Creating buffers") for channel in self.channels: ltf("Creating buffer for {}".format(channel)) channel = self.get_channel_name(channel["id"]) buff_name = "/tmp/slack_{}".format(channel) self.nvim.command("new {}".format(buff_name)) self.nvim.command("view") self.channel_buffers[channel] = self.get_buffer(buff_name) ltf("Created {} buffer.".format(channel)) buff_name = "/tmp/slack_log" self.nvim.command("new {}".format(buff_name)) self.channel_buffers["slack_log"] = self.get_buffer(buff_name) def write_event_to_buffer(self, event): ltf("writing event: {}".format(event)) ts = datetime.fromtimestamp(float(event["ts"])) ts_out = datetime.strftime(ts, "%H:%M:%S") channel = self.get_channel_name(event["channel"]) if "user" in event: username = self.get_user_name(event["user"]) else: username = event["username"] msg = "{} [{}]({}): {}".format(ts_out, channel, username, event["text"]) ltf("{}: writing {} to {}".format(datetime.now(), msg, channel)) self.channel_buffers[channel].append(msg) ltf("done writing event") @neovim.command("SlackComment", range="", nargs="*", allow_nested=True) def comment(self, args, range): ltf("range: {} args: {}".format(range, args)) channel_name = args[0] comment = " ".join(args[1:]) self.sc.api_call("chat.postMessage", channel="#{}".format(channel_name), text=comment) def process_slack_stream(self, allow_nested=True): if self.sc.rtm_connect(): while True: print("no op") for event in self.sc.rtm_read(): if event["type"] in ["message"]: self.write_event_to_buffer(event) # for buff in self.channel_buffers: # self.nvim.command( self.channel_buffers["slack_log"].append("0") sleep(0.25) @neovim.command("SlackStream") def slack_stream(self): ltf("{}: processing stream".format(datetime.now())) self.create_channel_buffers() self.process_slack_stream() p = multiprocessing.Process(target=self.process_slack_stream) # jobs.append(p) p.start() def get_summary(self): users = {u["id"]: u["real_name"] for u in self.users} return { channel["name"]: { "members": [users[m] for m in channel["members"]] } for channel in self.channels } @neovim.command("SlackSummary") def slack_summary(self): ltf("SUMMARY") buff_name = "/tmp/slack_channels" self.nvim.command("new {}".format(buff_name)) self.nvim.command("view") buff = self.get_buffer(buff_name) for line in json.dumps(self.get_summary(), indent=2).split("\n"): buff.append(line)
class SlackRTM(object): def __init__(self, sink_config, bot, loop, threaded=False): self.bot = bot self.loop = loop self.config = sink_config self.apikey = self.config['key'] self.threadname = None self.lastimg = '' self.slack = SlackClient(self.apikey) if not self.slack.rtm_connect(): raise ConnectionFailedError for key in ['self', 'team', 'users', 'channels', 'groups']: if key not in self.slack.server.login_data: raise IncompleteLoginError if threaded: if 'name' in self.config: self.name = self.config['name'] else: self.name = '%s@%s' % ( self.slack.server.login_data['self']['name'], self.slack.server.login_data['team']['domain']) logger.warning( 'no name set in config file, using computed name %s', self.name) self.threadname = 'SlackRTM:' + self.name threading.current_thread().name = self.threadname logger.info('started RTM connection for SlackRTM thread %s', pprint.pformat(threading.current_thread())) for t in threading.enumerate(): if t.name == self.threadname and t != threading.current_thread( ): logger.info('old thread found: %s - killing it', pprint.pformat(t)) t.stop() self.update_userinfos(self.slack.server.login_data['users']) self.update_channelinfos(self.slack.server.login_data['channels']) self.update_groupinfos(self.slack.server.login_data['groups']) self.update_teaminfos(self.slack.server.login_data['team']) self.dminfos = {} self.my_uid = self.slack.server.login_data['self']['id'] self.admins = [] if 'admins' in self.config: for a in self.config['admins']: if a not in self.userinfos: logger.warning( 'userid %s not found in user list, ignoring', a) else: self.admins.append(a) if not len(self.admins): logger.warning('no admins specified in config file') self.hangoutids = {} self.hangoutnames = {} for c in self.bot.list_conversations(): name = self.bot.conversations.get_name(c, truncate=True) self.hangoutids[name] = c.id_ self.hangoutnames[c.id_] = name self.syncs = [] syncs = _slackrtm_conversations_get(self.bot, self.name) if not syncs: syncs = [] for s in syncs: sync = SlackRTMSync.fromDict(self.bot, s) if sync.slacktag == 'NOT_IN_CONFIG': sync.slacktag = self.get_teamname() sync.team_name = self.name # chatbridge needs this for context self.syncs.append(sync) if 'synced_conversations' in self.config and len( self.config['synced_conversations']): logger.warning( 'defining synced_conversations in config is deprecated') for conv in self.config['synced_conversations']: if len(conv) == 3: hotag = conv[2] else: if conv[1] not in self.hangoutnames: logger.error( "could not find conv %s in bot's conversations, but used in (deprecated) synced_conversations in config!", conv[1]) hotag = conv[1] else: hotag = self.hangoutnames[conv[1]] _new_sync = SlackRTMSync(self.bot, conv[0], conv[1], hotag, self.get_teamname()) _new_sync.team_name = self.name # chatbridge needs this for context self.syncs.append(_new_sync) # As of https://github.com/slackhq/python-slackclient/commit/ac343caf6a3fd8f4b16a79246264a05a7d257760 # SlackClient.api_call returns a pre-parsed json object (a dict). # Wrap this call in a compatibility duck-hunt. def api_call(self, *args, **kwargs): response = self.slack.api_call(*args, **kwargs) if isinstance(response, str): try: response = response.decode('utf-8') except: pass response = json.loads(response) return response def get_slackDM(self, userid): if not userid in self.dminfos: self.dminfos[userid] = self.api_call('im.open', user=userid)['channel'] return self.dminfos[userid]['id'] def update_userinfos(self, users=None): if users is None: response = self.api_call('users.list') users = response['members'] userinfos = {} for u in users: userinfos[u['id']] = u self.userinfos = userinfos def get_channel_users(self, channelid, default=None): channelinfo = None if channelid.startswith('C'): if not channelid in self.channelinfos: self.update_channelinfos() if not channelid in self.channelinfos: logger.error('get_channel_users: Failed to find channel %s' % channelid) return None else: channelinfo = self.channelinfos[channelid] else: if not channelid in self.groupinfos: self.update_groupinfos() if not channelid in self.groupinfos: logger.error( 'get_channel_users: Failed to find private group %s' % channelid) return None else: channelinfo = self.groupinfos[channelid] channelusers = channelinfo['members'] users = {} for u in channelusers: username = self.get_username(u) realname = self.get_realname(u, "No real name") if username: users[username + " " + u] = realname return users def update_teaminfos(self, team=None): if team is None: response = self.api_call('team.info') team = response['team'] self.team = team def get_teamname(self): # team info is static, no need to update return self.team['name'] def get_slack_domain(self): # team info is static, no need to update return self.team['domain'] def get_realname(self, user, default=None): if user not in self.userinfos: logger.debug('user not found, reloading users') self.update_userinfos() if user not in self.userinfos: logger.warning('could not find user "%s" although reloaded', user) return default if not self.userinfos[user]['real_name']: return default return self.userinfos[user]['real_name'] def get_username(self, user, default=None): if user not in self.userinfos: logger.debug('user not found, reloading users') self.update_userinfos() if user not in self.userinfos: logger.warning('could not find user "%s" although reloaded', user) return default return self.userinfos[user]['name'] def update_channelinfos(self, channels=None): if channels is None: response = self.api_call('channels.list') channels = response['channels'] channelinfos = {} for c in channels: channelinfos[c['id']] = c self.channelinfos = channelinfos def get_channelgroupname(self, channel, default=None): if channel.startswith('C'): return self.get_channelname(channel, default) if channel.startswith('G'): return self.get_groupname(channel, default) if channel.startswith('D'): return 'DM' return default def get_channelname(self, channel, default=None): if channel not in self.channelinfos: logger.debug('channel not found, reloading channels') self.update_channelinfos() if channel not in self.channelinfos: logger.warning('could not find channel "%s" although reloaded', channel) return default return self.channelinfos[channel]['name'] def update_groupinfos(self, groups=None): if groups is None: response = self.api_call('groups.list') groups = response['groups'] groupinfos = {} for c in groups: groupinfos[c['id']] = c self.groupinfos = groupinfos def get_groupname(self, group, default=None): if group not in self.groupinfos: logger.debug('group not found, reloading groups') self.update_groupinfos() if group not in self.groupinfos: logger.warning('could not find group "%s" although reloaded', group) return default return self.groupinfos[group]['name'] def get_syncs(self, channelid=None, hangoutid=None): syncs = [] for sync in self.syncs: if channelid == sync.channelid: syncs.append(sync) elif hangoutid == sync.hangoutid: syncs.append(sync) return syncs def rtm_read(self): return self.slack.rtm_read() def ping(self): return self.slack.server.ping() def matchReference(self, match): out = "" linktext = "" if match.group(5) == '|': linktext = match.group(6) if match.group(2) == '@': if linktext != "": out = linktext else: out = "@%s" % self.get_username(match.group(3), 'unknown:%s' % match.group(3)) elif match.group(2) == '#': if linktext != "": out = "#%s" % linktext else: out = "#%s" % self.get_channelgroupname( match.group(3), 'unknown:%s' % match.group(3)) else: linktarget = match.group(1) if linktext == "": linktext = linktarget out = '[{}]({})'.format(linktext, linktarget) out = out.replace('_', '%5F') out = out.replace('*', '%2A') out = out.replace('`', '%60') return out @asyncio.coroutine def upload_image(self, image_uri, sync, username, userid, channel_name): token = self.apikey logger.info('downloading %s', image_uri) filename = os.path.basename(image_uri) request = urllib.request.Request(image_uri) request.add_header("Authorization", "Bearer %s" % token) image_response = urllib.request.urlopen(request) content_type = image_response.info().get_content_type() filename_extension = mimetypes.guess_extension( content_type).lower() # returns with "." physical_extension = "." + filename.rsplit(".", 1).pop().lower() if physical_extension == filename_extension: pass elif filename_extension == ".jpe" and physical_extension in [ ".jpg", ".jpeg", ".jpe", ".jif", ".jfif" ]: # account for mimetypes idiosyncrancy to return jpe for valid jpeg pass else: logger.warning("unable to determine extension: {} {}".format( filename_extension, physical_extension)) filename += filename_extension logger.info('uploading as %s', filename) image_id = yield from self.bot._client.upload_image(image_response, filename=filename) logger.info('sending HO message, image_id: %s', image_id) yield from sync._bridgeinstance._send_to_internal_chat( sync.hangoutid, "shared media from slack", { "sync": sync, "source_user": username, "source_uid": userid, "source_title": channel_name }, image_id=image_id) def config_syncto(self, channel, hangoutid, shortname): for sync in self.syncs: if sync.channelid == channel and sync.hangoutid == hangoutid: raise AlreadySyncingError sync = SlackRTMSync(self.bot, channel, hangoutid, shortname, self.get_teamname()) sync.team_name = self.name # chatbridge needs this for context logger.info('adding sync: %s', sync.toDict()) self.syncs.append(sync) syncs = _slackrtm_conversations_get(self.bot, self.name) if not syncs: syncs = [] logger.info('storing sync: %s', sync.toDict()) syncs.append(sync.toDict()) _slackrtm_conversations_set(self.bot, self.name, syncs) return def config_disconnect(self, channel, hangoutid): sync = None for s in self.syncs: if s.channelid == channel and s.hangoutid == hangoutid: sync = s logger.info('removing running sync: %s', s) s._bridgeinstance.close() self.syncs.remove(s) if not sync: raise NotSyncingError syncs = _slackrtm_conversations_get(self.bot, self.name) if not syncs: syncs = [] for s in syncs: if s['channelid'] == channel and s['hangoutid'] == hangoutid: logger.info('removing stored sync: %s', s) syncs.remove(s) _slackrtm_conversations_set(self.bot, self.name, syncs) return def config_setsyncjoinmsgs(self, channel, hangoutid, enable): sync = None for s in self.syncs: if s.channelid == channel and s.hangoutid == hangoutid: sync = s if not sync: raise NotSyncingError logger.info('setting sync_joins=%s for sync=%s', enable, sync.toDict()) sync.sync_joins = enable syncs = _slackrtm_conversations_get(self.bot, self.name) if not syncs: syncs = [] for s in syncs: if s['channelid'] == channel and s['hangoutid'] == hangoutid: syncs.remove(s) logger.info('storing new sync=%s with changed sync_joins', s) syncs.append(sync.toDict()) _slackrtm_conversations_set(self.bot, self.name, syncs) return def config_sethotag(self, channel, hangoutid, hotag): sync = None for s in self.syncs: if s.channelid == channel and s.hangoutid == hangoutid: sync = s if not sync: raise NotSyncingError logger.info('setting hotag="%s" for sync=%s', hotag, sync.toDict()) sync.hotag = hotag syncs = _slackrtm_conversations_get(self.bot, self.name) if not syncs: syncs = [] for s in syncs: if s['channelid'] == channel and s['hangoutid'] == hangoutid: syncs.remove(s) logger.info('storing new sync=%s with changed hotag', s) syncs.append(sync.toDict()) _slackrtm_conversations_set(self.bot, self.name, syncs) return def config_setimageupload(self, channel, hangoutid, upload): sync = None for s in self.syncs: if s.channelid == channel and s.hangoutid == hangoutid: sync = s if not sync: raise NotSyncingError logger.info('setting image_upload=%s for sync=%s', upload, sync.toDict()) sync.image_upload = upload syncs = _slackrtm_conversations_get(self.bot, self.name) if not syncs: syncs = [] for s in syncs: if s['channelid'] == channel and s['hangoutid'] == hangoutid: syncs.remove(s) logger.info('storing new sync=%s with changed hotag', s) syncs.append(sync.toDict()) _slackrtm_conversations_set(self.bot, self.name, syncs) return def config_setslacktag(self, channel, hangoutid, slacktag): sync = None for s in self.syncs: if s.channelid == channel and s.hangoutid == hangoutid: sync = s if not sync: raise NotSyncingError logger.info('setting slacktag="%s" for sync=%s', slacktag, sync.toDict()) sync.slacktag = slacktag syncs = _slackrtm_conversations_get(self.bot, self.name) if not syncs: syncs = [] for s in syncs: if s['channelid'] == channel and s['hangoutid'] == hangoutid: syncs.remove(s) logger.info('storing new sync=%s with changed slacktag', s) syncs.append(sync.toDict()) _slackrtm_conversations_set(self.bot, self.name, syncs) return def config_showslackrealnames(self, channel, hangoutid, realnames): sync = None for s in self.syncs: if s.channelid == channel and s.hangoutid == hangoutid: sync = s if not sync: raise NotSyncingError logger.info('setting showslackrealnames=%s for sync=%s', realnames, sync.toDict()) sync.showslackrealnames = realnames syncs = _slackrtm_conversations_get(self.bot, self.name) if not syncs: syncs = [] for s in syncs: if s['channelid'] == channel and s['hangoutid'] == hangoutid: syncs.remove(s) logger.info('storing new sync=%s with changed hotag', s) syncs.append(sync.toDict()) _slackrtm_conversations_set(self.bot, self.name, syncs) return def config_showhorealnames(self, channel, hangoutid, realnames): sync = None for s in self.syncs: if s.channelid == channel and s.hangoutid == hangoutid: sync = s if not sync: raise NotSyncingError logger.info('setting showhorealnames=%s for sync=%s', realnames, sync.toDict()) sync.showhorealnames = realnames syncs = _slackrtm_conversations_get(self.bot, self.name) if not syncs: syncs = [] for s in syncs: if s['channelid'] == channel and s['hangoutid'] == hangoutid: syncs.remove(s) logger.info('storing new sync=%s with changed hotag', s) syncs.append(sync.toDict()) _slackrtm_conversations_set(self.bot, self.name, syncs) return def handle_reply(self, reply): """handle incoming replies from slack""" try: msg = SlackMessage(self, reply) except ParseError as e: return except Exception as e: logger.exception('error parsing Slack reply: %s(%s)', type(e), str(e)) return # commands can be processed even from unsynced channels try: slackCommandHandler(self, msg) except Exception as e: logger.exception('error in handleCommands: %s(%s)', type(e), str(e)) syncs = self.get_syncs(channelid=msg.channel) if not syncs: # stop processing replies if no syncs are available (optimisation) return reffmt = re.compile(r'<((.)([^|>]*))((\|)([^>]*)|([^>]*))>') message = reffmt.sub(self.matchReference, msg.text) message = slack_markdown_to_hangups(message) for sync in syncs: if not sync.sync_joins and msg.is_joinleave: continue if msg.from_ho_id != sync.hangoutid: username = msg.realname4ho if sync.showslackrealnames else msg.username4ho channel_name = self.get_channelgroupname(msg.channel) if msg.file_attachment: if sync.image_upload: self.loop.call_soon_threadsafe( asyncio. async, self.upload_image(msg.file_attachment, sync, username, msg.user, channel_name)) self.lastimg = os.path.basename(msg.file_attachment) else: # we should not upload the images, so we have to send the url instead response += msg.file_attachment self.loop.call_soon_threadsafe( asyncio. async, sync._bridgeinstance._send_to_internal_chat( sync.hangoutid, message, { "sync": sync, "source_user": username, "source_uid": msg.user, "source_gid": sync.channelid, "source_title": channel_name })) @asyncio.coroutine def _send_deferred_media(self, image_link, sync, full_name, link_names, photo_url, fragment): self.api_call('chat.postMessage', channel=sync.channelid, text="{} {}".format(image_link, fragment), username=full_name, link_names=True, icon_url=photo_url) @asyncio.coroutine def handle_ho_message(self, event, conv_id, channel_id): user = event.passthru["original_request"]["user"] message = event.passthru["original_request"]["message"] if not message: message = "" message = hangups_markdown_to_slack(message) """slackrtm uses an overengineered pseudo SlackRTMSync "structure" to contain individual 1-1 syncs we rely on the chatbridge to iterate through multiple syncs, and ensure we only have to deal with a single mapping at this level XXX: the mapping SHOULD BE single, but let duplicates get through""" active_syncs = [] for sync in self.get_syncs(hangoutid=conv_id): if sync.channelid != channel_id: continue if sync.hangoutid != conv_id: continue active_syncs.append(sync) for sync in active_syncs: bridge_user = sync._bridgeinstance._get_user_details( user, {"event": event}) extras = [] if sync.showhorealnames == "nick": display_name = bridge_user["nickname"] or bridge_user[ "full_name"] else: display_name = bridge_user["full_name"] if (sync.showhorealnames == "both" and bridge_user["nickname"] and not bridge_user["full_name"] == bridge_user["nickname"]): extras.append(bridge_user["nickname"]) if sync.hotag is True: if "chatbridge" in event.passthru and event.passthru[ "chatbridge"]["source_title"]: chat_title = event.passthru["chatbridge"]["source_title"] extras.append(chat_title) elif sync.hotag: extras.append(sync.hotag) if extras: display_name = "{} ({})".format(display_name, ", ".join(extras)) slackrtm_fragment = "<ho://{}/{}| >".format( conv_id, bridge_user["chat_id"] or bridge_user["preferred_name"]) """XXX: media sending: * if media link is already available, send it immediately * real events from google servers will have the medialink in event.conv_event.attachment * media link can also be added as part of the passthru * for events raised by other external chats, wait for the public link to become available """ if "attachments" in event.passthru[ "original_request"] and event.passthru["original_request"][ "attachments"]: # automatically prioritise incoming events with attachments available media_link = event.passthru["original_request"]["attachments"][ 0] logger.info( "media link in original request: {}".format(media_link)) message = "shared media: {}".format(media_link) elif isinstance(event, FakeEvent): if ("image_id" in event.passthru["original_request"] and event.passthru["original_request"]["image_id"]): # without media link, create a deferred post until a public media link becomes available image_id = event.passthru["original_request"]["image_id"] logger.info("wait for media link: {}".format(image_id)) loop = asyncio.get_event_loop() task = loop.create_task( self.bot._handlers.image_uri_from( image_id, self._send_deferred_media, sync, display_name, True, bridge_user["photo_url"], slackrtm_fragment)) elif (hasattr(event, "conv_event") and hasattr(event.conv_event, "attachments") and len(event.conv_event.attachments) == 1): # catch actual events with media link but didn' go through the passthru media_link = event.conv_event.attachments[0] logger.info( "media link in original event: {}".format(media_link)) message = "shared media: {}".format(media_link) """standard message relay""" message = "{} {}".format(message, slackrtm_fragment) logger.info("message {}: {}".format(sync.channelid, message)) self.api_call('chat.postMessage', channel=sync.channelid, text=message, username=display_name, link_names=True, icon_url=bridge_user["photo_url"]) def handle_ho_membership(self, event): # Generate list of added or removed users links = [] for user_id in event.conv_event.participant_ids: user = event.conv.get_user(user_id) links.append(u'<https://plus.google.com/%s/about|%s>' % (user.id_.chat_id, user.full_name)) names = u', '.join(links) for sync in self.get_syncs(hangoutid=event.conv_id): if not sync.sync_joins: continue if sync.hotag: honame = sync.hotag else: honame = self.bot.conversations.get_name(event.conv) # JOIN if event.conv_event.type_ == hangups.MembershipChangeType.JOIN: invitee = u'<https://plus.google.com/%s/about|%s>' % ( event.user_id.chat_id, event.user.full_name) if invitee == names: message = u'%s has joined %s' % (invitee, honame) else: message = u'%s has added %s to %s' % (invitee, names, honame) # LEAVE else: message = u'%s has left _%s_' % (names, honame) message = u'%s <ho://%s/%s| >' % (message, event.conv_id, event.user_id.chat_id) logger.debug("sending to channel/group %s: %s", sync.channelid, message) self.api_call('chat.postMessage', channel=sync.channelid, text=message, as_user=True, link_names=True) def handle_ho_rename(self, event): name = self.bot.conversations.get_name(event.conv, truncate=False) for sync in self.get_syncs(hangoutid=event.conv_id): invitee = u'<https://plus.google.com/%s/about|%s>' % ( event.user_id.chat_id, event.user.full_name) hotagaddendum = '' if sync.hotag: hotagaddendum = ' _%s_' % sync.hotag message = u'%s has renamed the Hangout%s to _%s_' % ( invitee, hotagaddendum, name) message = u'%s <ho://%s/%s| >' % (message, event.conv_id, event.user_id.chat_id) logger.debug("sending to channel/group %s: %s", sync.channelid, message) self.api_call('chat.postMessage', channel=sync.channelid, text=message, as_user=True, link_names=True)
class slackThread(threading.Thread): def __init__(self, apikey): threading.Thread.__init__(self) self.APIKEY = apikey self.DATA = {} self.SC = SlackClient(self.APIKEY) self.CON = None self.lock = threading.Lock() self.messageId = 0 self.handledEvents = { 'message': self.__event__message, } self.ready = False def run(self): works = True self.CON = self.SC.rtm_connect() if self.CON == False: print('Failed starting a Slack RTM session.') works = False if not self.rebuildData(): print('Failed accessing slack data.') works = False if works: print('Established Slack connection') self.ready = True else: print('Slack connection not established') return countForPing = 0 while True: for event in self.SC.rtm_read(): try: self.handledEvents[event['type']](event) except: #print(event) pass countForPing += 0.1 if countForPing > 3: self.SC.server.ping() countForPing = 0 time.sleep(0.1) def rebuildData(self): self.lock.acquire() test = False try: test = json.loads((self.SC.api_call("api.test")).decode()) except: return False if test.get('ok') == False: print('API Test failed. Full response:') print(test) self.lock.release() return False self.DATA['users'] = {} for user in json.loads( (self.SC.api_call("users.list")).decode()).get('members'): self.DATA['users'][user['id']] = { 'name': user['name'], } self.DATA['channels'] = {} for channel in json.loads( (self.SC.api_call("channels.list")).decode()).get('channels'): self.DATA['channels'][channel['id']] = { 'name': channel['name'], } self.lock.release() return True def __getMessageId(self): self.lock.acquire() mId = self.messageId self.messageId += 1 self.lock.release() return mId def __getUserId(self, name): return self.__getId('users', name) def __getChannelId(self, name): return self.__getId('channels', name) def __getId(self, sub, name): if self.ready: for key in self.DATA[sub].keys(): if self.DATA[sub][key]['name'] == name: return key return None def __getPmChannelId(self, name): user = self.__getUserId(name) if user == None: return None channelId = self.DATA['users'][user].get('pm_channel_id') if channelId == None: data = json.loads((self.SC.api_call("im.open", user=user)).decode()) channelId = data["channel"]["id"] self.DATA['users'][user]['pm_channel_id'] = channelId return channelId def sendMessageToUser(self, user, text): channelId = self.__getPmChannelId(user) if channelId == None: return self.__sendMessage(channelId, text) def sendMessageToChannel(self, channel, text): channelId = self.__getChannelId(channel) if channelId == None: return self.__sendMessage(channelId, text) def __sendMessage(self, target, text): self.lock.acquire() self.SC.api_call("chat.postMessage", as_user="******", channel=target, text=text) self.lock.release() def __event__message(self, event): #print(self.DATA['users'][event['user']]['name'] + ": " + event['text']) pass
class SlackSensor(PollingSensor): def __init__(self, sensor_service, config=None, poll_interval=None): super(SlackSensor, self).__init__(sensor_service=sensor_service, config=config, poll_interval=poll_interval) self._logger = self._sensor_service.get_logger(__name__) self._token = self._config['sensor']['token'] self._handlers = { 'message': self._handle_message, } self._user_info_cache = {} self._channel_info_cache = {} def setup(self): self._client = SlackClient(self._token) data = self._client.rtm_connect() if not data: msg = 'Failed to connect to the Slack API. Invalid token?' raise Exception(msg) self._populate_cache(user_data=self._api_call('users.list'), channel_data=self._api_call('channels.list')) def poll(self): result = self._client.rtm_read() if result: self._handle_result(result=result) def cleanup(self): pass def add_trigger(self, trigger): pass def update_trigger(self, trigger): pass def remove_trigger(self, trigger): pass def _populate_cache(self, user_data, channel_data): """ Populate users and channels cache from info which is returned on rtm.start """ for user in user_data['members']: self._user_info_cache[user['id']] = user for channel in channel_data['channels']: self._channel_info_cache[channel['id']] = channel def _handle_result(self, result): for item in result: item_type = item['type'] handler_func = self._handlers.get(item_type, lambda data: data) handler_func(data=item) def _handle_message(self, data): trigger = 'slack.message' event_type = data['type'] if event_type not in EVENT_TYPE_WHITELIST or 'subtype' in data: # Skip unsupported event return # Note: We resolve user and channel information to provide more context user_info = self._get_user_info(user_id=data['user']) channel_info = self._get_channel_info(channel_id=data['channel']) payload = { 'user': { 'id': user_info['id'], 'name': user_info['name'], 'first_name': user_info['profile']['first_name'], 'last_name': user_info['profile']['last_name'], 'real_name': user_info['profile']['real_name'], 'is_admin': user_info['is_admin'], 'is_owner': user_info['is_owner'] }, 'channel': { 'id': channel_info['id'], 'name': channel_info['name'], 'topic': channel_info['topic']['value'], }, 'timestamp': int(float(data['ts'])), 'text': data['text'] } self._sensor_service.dispatch(trigger=trigger, payload=payload) def _get_user_info(self, user_id): if user_id not in self._user_info_cache: result = self._api_call('users.info', user=user_id)['user'] self._user_info_cache[user_id] = result return self._user_info_cache[user_id] def _get_channel_info(self, channel_id): if channel_id not in self._channel_info_cache: result = self._api_call('channels.info', channel=channel_id)['channel'] self._channel_info_cache[channel_id] = result return self._channel_info_cache[channel_id] def _api_call(self, method, **kwargs): result = self._client.api_call(method, **kwargs) result = json.loads(result) return result
def __init__(self, token): history_length = 20 summary_frequency = 5 summary_countdown = summary_frequency self.s = requests.Session() self.tea = TextEmotionAnalyzer(outputMode="json") self.token = token sc = SlackClient(token) self.history = bot_history(history_length) channel = "#dancewithdeanna" if sc.rtm_connect(): while True: new_evts = sc.rtm_read() for evt in new_evts: if len(evt) != 0: if evt.has_key('text') and not evt.has_key('subtype'): print evt['text'] top_emotion = self.analyse(evt['text'], evt['user'], evt['channel']) if top_emotion is None: print "top emotion is None" continue print top_emotion["docEmotions"] if top_emotion["docEmotions"].has_key('anger'): print sc.api_call( 'chat.postMessage', channel=channel, text='Calm down!', username='******', icon_emoji=':woman::skin-tone-2:', as_user='******') elif top_emotion["docEmotions"].has_key('fear'): print sc.api_call( 'chat.postMessage', channel=channel, text='Dont worry!', username='******', icon_emoji=':woman::skin-tone-2:', as_user='******') elif top_emotion["docEmotions"].has_key('joy'): print sc.api_call( 'chat.postMessage', channel=channel, text='Yay!', username='******', icon_emoji=':woman::skin-tone-2:', as_user='******') elif top_emotion["docEmotions"].has_key('sadness'): print sc.api_call( 'chat.postMessage', channel=channel, text='*hugs*!', username='******', icon_emoji=':woman::skin-tone-2:', as_user='******') elif top_emotion["docEmotions"].has_key('disgust'): print sc.api_call( 'chat.postMessage', channel=channel, text='Gross!', username='******', icon_emoji=':woman::skin-tone-2:', as_user='******') if (summary_countdown >= 0): summary_countdown -= 1 else: print_summary(self.history) time.sleep(1) else: print "Connection Failed, invalid token?"
class Slack: def __init__(self, tokenfile: str) -> None: try: with open(tokenfile) as f: token = f.readline().strip() except FileNotFoundError: exit("Unable to open the token file {}".format(tokenfile)) self.client = SlackClient(token) self._usercache = {} # type: Dict[str, User] self._usermapcache = {} # type: Dict[str, User] def away(self, is_away: bool) -> None: """ Forces the aways status or lets slack decide """ status = 'away' if is_away else 'auto' r = self.client.api_call('users.setPresence', presence=status) response = load(r, Response) if not response.ok: raise ResponseException(response) @lru_cache() def get_members(self, id_: str) -> List[str]: r = self.client.api_call('conversations.members', channel=id_, limit=5000) response = load(r, Response) if response.ok: return load(r['members'], List[str]) raise ResponseException(response) @lru_cache() def channels(self) -> List[Channel]: """ Returns the list of slack channels """ result = [] # type: List[Channel] r = self.client.api_call("channels.list", exclude_archived=True, exclude_members=True) response = load(r, Response) if response.ok: result.extend(load(r['channels'], List[Channel])) else: raise ResponseException(response) # Multiparty IM appear as both groups and mpim. # Fetch MPIM first, to exclude them in the list of groups. r = self.client.api_call("mpim.list", exclude_archived=True, exclude_members=True) response = load(r, Response) if response.ok: mpims = load(r['groups'], List[Channel]) result.extend(mpims) mpim_ids = [mpim.id for mpim in mpims] else: raise ResponseException(response) r = self.client.api_call("groups.list", exclude_archived=True, exclude_members=True) response = load(r, Response) if response.ok: result.extend([ group for group in load(r['groups'], List[Channel]) if group.id not in mpim_ids ]) else: raise ResponseException(response) return result @lru_cache() def get_channel(self, id_: str) -> Channel: """ Returns a channel object from a slack channel id raises KeyError if it doesn't exist. """ for c in self.channels(): if c.id == id_: return c raise KeyError() @lru_cache() def get_channel_by_name(self, name: str) -> Channel: """ Returns a channel object from a slack channel id raises KeyError if it doesn't exist. """ for c in self.channels(): if c.name == name: return c raise KeyError() @property def fileno(self) -> Optional[int]: return self.client.fileno def get_ims(self) -> List[IM]: """ Returns a list of the IMs Some bullshit slack invented because 1 to 1 conversations need to have an ID to send to, you can't send directly to a user. """ r = self.client.api_call("im.list", ) response = load(r, Response) if response.ok: return load(r['ims'], List[IM]) raise ResponseException(response) def get_user_by_name(self, name: str) -> User: return self._usermapcache[name] def get_usernames(self) -> List[str]: return list(self._usermapcache.keys()) def prefetch_users(self) -> None: """ Prefetch all team members for the slack team. """ r = self.client.api_call("users.list") response = load(r, Response) if response.ok: for user in load(r['members'], List[User]): self._usercache[user.id] = user self._usermapcache[user.name] = user def get_user(self, id_: str) -> User: """ Returns a user object from a slack user id raises KeyError if it does not exist """ if id_ in self._usercache: return self._usercache[id_] r = self.client.api_call("users.info", user=id_) response = load(r, Response) if response.ok: u = load(r['user'], User) self._usercache[id_] = u self._usermapcache[u.name] = u return u else: raise KeyError(response) def send_message(self, channel_id: str, msg: str) -> None: """ Send a message to a channel or group or whatever """ r = self.client.api_call( "chat.postMessage", channel=channel_id, text=msg, as_user=True, ) response = load(r, Response) if response.ok: return raise ResponseException(response) def send_message_to_user(self, user_id: str, msg: str): # Find the channel id found = False for i in self.get_ims(): if i.user == user_id: channel_id = i.id found = True break # A conversation does not exist, create one if not found: r = self.client.api_call( "im.open", return_im=True, user=user_id, ) response = load(r, Response) if not response.ok: raise ResponseException(response) channel_id = r['channel']['id'] self.send_message(channel_id, msg) def events_iter(self) -> Iterator[Optional[SlackEvent]]: """ This yields an event or None. Don't call it without sleeps """ if self.client.rtm_connect(): while True: try: events = self.client.rtm_read() except: if not self.client.rtm_connect(): sleep(10) events = [] for event in events: t = event.get('type') subt = event.get('subtype') try: if t == 'message' and not subt: yield _loadwrapper(event, Message) elif t == 'message' and subt == 'slackbot_response': yield _loadwrapper(event, Message) elif t == 'message' and subt == 'file_share': yield _loadwrapper(event, MessageFileShare) elif t == 'message' and subt == 'message_changed': event['message']['channel'] = event['channel'] event['previous_message']['channel'] = event[ 'channel'] yield MessageEdit( previous=load(event['previous_message'], Message), current=load(event['message'], Message)) elif t == 'message' and subt == 'message_deleted': event['previous_message']['channel'] = event[ 'channel'] yield _loadwrapper(event['previous_message'], MessageDelete) elif t == 'message' and subt == 'bot_message': yield _loadwrapper(event, MessageBot) elif t == 'user_change': # Changes in the user, drop it from cache u = load(event['user'], User) if u.id in self._usercache: del self._usercache[u.id] #FIXME don't know if it is wise, maybe it gets lost forever del self._usermapcache[u.name] #TODO make an event for this elif t == 'file_deleted': yield _loadwrapper(event, FileDeleted) elif t in USELESS_EVENTS: continue else: print(event) except Exception as e: print('Exception: %s' % e) yield None
class Bot(object): def __init__(self, key, codeword='iamgod'): self.slack_client = SlackClient(key) self.bot_name = "chocbot" self.bot_id = self.get_bot_id() print('My ID is:', self.bot_id) self.codeword = codeword self.scoreboard = Scoreboard() self.nominators = Scoreboard('Nominators') if self.bot_id is None: exit("Error, could not find " + self.bot_name) self.restore_state() self.event = Event(self) self.listen() def get_bot_id(self): api_call = self.slack_client.api_call("users.list") if api_call.get('ok'): # retrieve all users so we can find our bot users = api_call.get('members') for user in users: if 'name' in user and user.get('name') == self.bot_name: return "<@" + user.get('id') + ">" return None def get_user_name(self, userid): api_call = self.slack_client.api_call("users.list") if api_call.get('ok'): # retrieve all users so we can find our bot users = api_call.get('members') for user in users: if 'name' in user and user.get('id') == userid: return user.get('name') return None def listen(self): if self.slack_client.rtm_connect(with_team_state=False): print("Successfully connected, listening for commands") while True: self.event.wait_for_event() time.sleep(1) else: exit("Error, Connection Failed") def send_message(self, channel, message): #sends a message to the noted channel self.slack_client.api_call("chat.postMessage", channel=channel, text=message, as_user=True) def save_state(self, filename='bot_state.pkl'): #dumps the state out to an appropriate file try: state = { 'scoreboard': self.scoreboard, 'nominators': self.nominators } pickle.dump(state, open(filename, 'wb')) except: print('Count not save state. Is the location writeable?') def restore_state(self, filename='bot_state.pkl'): try: state = pickle.load(open(filename, 'rb')) self.scoreboard = state['scoreboard'] self.nominators = state['nominators'] print('Restored state from save file.') except: print('Save file not found')
if __name__ == "__main__": parser = argparse.ArgumentParser( formatter_class=argparse.RawDescriptionHelpFormatter, description=''' This script posts a message into a slack channel. Sample commands: post.py mychannel "This is a message." ''', epilog='''''') parser.add_argument('channel', type=str, nargs=1, help='Channel to post to') parser.add_argument('message', type=str, nargs=1, help='Message to post') args = parser.parse_args() print(args) config = configparser.RawConfigParser() config.read('creds.cfg.sample') token = config.get("Slack", "token") client = SlackClient(token) print(client.rtm_connect()) try: post(client, args.channel[0], args.message[0]) except Exception as e: sys.exit("Error: %s" % e.message)
def get_connected_slack_client(secrets_env): slack_client = SlackClient(secrets_env.str('SLACK_TOKEN')) if slack_client.rtm_connect(with_team_state=False): return slack_client raise RuntimeError('SlackClient.rtm_connect failed')
class Robotson(): def __init__(self, token): self.token = token self.slack = SlackClient(token=self.token) self.cleverbot = Cleverbot() self.botname = settings.BOT_NAME self.facebook = Facebook() self.twitter = Twitter() self.network = SocialNetwork() def run(self, interval): if self.slack.rtm_connect(): while True: full_message = self.slack.rtm_read() if full_message: content = full_message[0] if content.get("type") == 'message': sender = self.username_as_str(content.get("user")) channel = content.get("channel") message = content.get("text") try: match = re.search(r'<@[A-Z0-9]+>', message) bot_mention = match.group() if match else "" if bot_mention: # TODO: Remove hardcoding if settings.BOT_UID in bot_mention: self.talk(channel, sender, message) elif settings.SHARE_TRIGGER in message.lower(): self.share(message) except: pass time.sleep(interval) else: raise SlackNotConnected def share(self, message): try: match_share_trigger = re.search( r'%s[\:]?' % settings.SHARE_TRIGGER, message) message = message.replace(match_share_trigger.group(), "") match_lt_mt = re.search(r'[<]+(.*)[>]+', message) message = message.replace(match_lt_mt.group(0), match_lt_mt.group(1)) message = message.strip() url = re.findall( r'http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', message) if len(url) > 0: if not self.network.already_posted(url[0]): self.facebook.post(message) # self.twitter.post(message) self.network.save_message(message) except Exception: pass def talk(self, channel, user, message): try: answer = '@%s: %s' % (user, self.cleverbot.ask(message)) self.slack.rtm_send_message(channel=channel, message=answer) except Exception: pass def username_as_str(self, userid): try: response = json.loads( self.slack.api_call('users.info', user=userid)) return response.get("user").get("name") except: return ""
if __name__ == "__main__": parser = argparse.ArgumentParser( formatter_class=argparse.RawDescriptionHelpFormatter, description=''' This script posts a message into a slack channel. Sample commands: post.py mychannel "This is a message." ''', epilog='''''' ) parser.add_argument('channel', type=str, nargs=1, help='Channel to post to') parser.add_argument('message', type=str, nargs=1, help='Message to post') args = parser.parse_args() config = ConfigParser.RawConfigParser() config.read('creds.cfg') token = config.get("Slack","token") client = SlackClient(token) client.rtm_connect() try: post(client, args.channel[0], args.message[0]) except Exception as e: sys.exit("Error: %s" % e.message)
raise SystemExit(BOT_NAME + " FAILED to locate ePO API credentials; please provide credentails and try again") url = ServerLocation + '/remote/core.help' query_result = requests.get(url, auth=HTTPBasicAuth(ePO_SERVER_usr,ePO_SERVER_pass = ""), verify=False) if query_result.text.find("<title> - Error report</title>") != -1: if DEBUG == 1: print("\n Startup - Credential Check == DEBUG query_result output: \n" + query_result.text) raise SystemExit("Connected to ePO API but failed to verify credentials; check credentials and restart") except: if DEBUG == 1: print("\n Startup - Credential Check == DEBUG result output: \n" + query_result.text) raise SystemExit(BOT_NAME + " FAILED to verify ePO API credentials; please check credentials & connection before trying again") #Main Bot Operation if slack_client.rtm_connect(): #Establish connection to slack.com print(BOT_NAME + " is running and connected to slack.com") while True: #Slack Operating Loop #Search for commands try: command, channel = parse_slack_output(slack_client.rtm_read()) except: print("Connection to slack.com FAILED... " + BOT_NAME + " could not perform slack_client.rtm_read()!") #Process commands if found if command and channel: if DEBUG == 1: print(command) print(command[:MaxCommandLen])
class RtmBot(object): def __init__(self, config): ''' Params: - config (dict): - SLACK_TOKEN: your authentication token from Slack - BASE_PATH (optional: defaults to execution directory) RtmBot will look in this directory for plugins. - LOGFILE (optional: defaults to rtmbot.log) The filename for logs, will be stored inside the BASE_PATH directory - DEBUG (optional: defaults to False) with debug enabled, RtmBot will break on errors ''' # set the config object self.config = config # set slack token self.token = config.get('SLACK_TOKEN', None) if not self.token: raise ValueError("Please add a SLACK_TOKEN to your config file.") # get list of directories to search for loading plugins self.active_plugins = config.get('ACTIVE_PLUGINS', []) # set base directory for logs and plugin search working_directory = os.path.abspath(os.path.dirname(sys.argv[0])) self.directory = self.config.get('BASE_PATH', working_directory) if not self.directory.startswith('/'): path = os.path.join(os.getcwd(), self.directory) self.directory = os.path.abspath(path) self.debug = self.config.get('DEBUG', False) # establish logging log_file = config.get('LOGFILE', 'rtmbot.log') if self.debug: log_level = logging.DEBUG else: log_level = logging.INFO logging.basicConfig(filename=log_file, level=log_level, format='%(asctime)s %(message)s') logging.info('Initialized in: {}'.format(self.directory)) # initialize stateful fields self.last_ping = 0 self.bot_plugins = [] self.slack_client = SlackClient(self.token) def _dbg(self, debug_string): if self.debug: logging.debug(debug_string) def connect(self): """Convenience method that creates Server instance""" self.slack_client.rtm_connect() def _start(self): self.connect() self.load_plugins() for plugin in self.bot_plugins: try: self._dbg("Registering jobs for {}".format(plugin.name)) plugin.register_jobs() except NotImplementedError: # this plugin doesn't register jobs self._dbg("No jobs registered for {}".format(plugin.name)) except Exception as error: self._dbg("Error registering jobs for {} - {}".format( plugin.name, error) ) while True: for reply in self.slack_client.rtm_read(): self.input(reply) self.crons() self.output() self.autoping() time.sleep(.1) def start(self): if 'DAEMON' in self.config: if self.config.get('DAEMON'): import daemon with daemon.DaemonContext(): self._start() self._start() 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"] self._dbg("got {}".format(function_name)) for plugin in self.bot_plugins: 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 channel.send_message(output[1]) limiter = True def crons(self): for plugin in self.bot_plugins: plugin.do_jobs() def load_plugins(self): ''' Given a set of plugin_path strings (directory names on the python path), load any classes with Plugin in the name from any files within those dirs. ''' self._dbg("Loading plugins") if not self.active_plugins: self._dbg("No plugins specified in conf file") return # nothing to load for plugin_path in self.active_plugins: self._dbg("Importing {}".format(plugin_path)) if self.debug is True: # this makes the plugin fail with stack trace in debug mode cls = import_string(plugin_path) else: # otherwise we log the exception and carry on try: cls = import_string(plugin_path) except ImportError as error: logging.exception("Problem importing {} - {}".format( plugin_path, error) ) plugin_config = self.config.get(cls.__name__, {}) plugin = cls(slack_client=self.slack_client, plugin_config=plugin_config) # instatiate! self.bot_plugins.append(plugin) self._dbg("Plugin registered: {}".format(plugin))
class Robot(object): def __init__(self): self.client = SlackClient(SLACK_TOKEN_MGR) self.apps, self.docs = self.load_apps() def load_apps(self): docs = [''] apps = {} app = import_module('apps.system') 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 arguments = (self, channel, payloads, user, command) pool.apply_async(func=app.run, args=arguments) 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): tokens = text.split(' ', 1) if 1 < len(tokens): return tokens[0], tokens[1] else: return (text, '') def run(self): if self.client.rtm_connect(): debug_chn = '#bot' if os.path.isfile('booting_mgr'): file = open('booting_mgr', 'r') reboot_chn = file.readline() self.client.api_call('chat.postMessage', username=BOT_NAME + ' 매니저', as_user='******', icon_url=ICON_URL, channel=reboot_chn, text='재시작 완료') os.remove('booting_mgr') log_file = check_output(['ls', '-c', './log']).split('\n')[0] log_file = open('./log/' + log_file, 'r') is_debug = False while True: if os.path.isfile('DEBUG'): log_file.close() log_file = check_output(['ls', '-c', './log']).split('\n')[0] log_file = open('./log/' + log_file, 'r') dbg_file = open('DEBUG', 'r') dbg_chn = dbg_file.readline() dbg_file.close() is_debug = True os.remove('DEBUG') elif os.path.isfile('DEBUG_'): os.remove('DEBUG_') is_debug = False if is_debug: txt = '' txt = line = log_file.readline() while line: line = log_file.readline() txt += line if txt: self.client.api_call('chat.postMessage', username=BOT_NAME + ' 매니저(Debug)', as_user='******', icon_url=ICON_URL, channel=dbg_chn, text=txt) events = self.client.rtm_read() if events: messages = self.extract_messages(events) self.handle_messages(messages) gevent.sleep(0.3)
import spotipy.util as util from slackclient import SlackClient # get all required settings slack_api_key = os.environ["KLAUS_SLACK_API_KEY"] spotify_playlist_user = os.environ["KLAUS_SPOTIFY_PLAYLIST_USER"] spotify_playlist_id = os.environ["KLAUS_SPOTIFY_PLAYLIST_ID"] spotify_api_client_id = os.environ["KLAUS_SPOTIFY_API_CLIENT_ID"] spotify_api_secret = os.environ["KLAUS_SPOTIFY_API_CLIENT_SECRET"] spotify_api_callback_url = os.environ["KLAUS_SPOTIFY_API_CALLBACK_URL"] sc = SlackClient(slack_api_key) spre = re.compile(r"(?!spotify\:|http(?!s))\:\/\/[a-z]+\.spotify\.com\/(track|artist|album)(?:\:|\/)([a-zA-Z0-9]+)") # check if we're connected if sc.rtm_connect(): while True: # get latest messages msg = sc.rtm_read() for d in msg: # make sure we have an actual message if d.get('text') and len(spre.findall(d.get('text'))): # extract some required information from the spotify link rc = spre.findall(d.get('text')) for f in rc: sid = f[1] # make sure we have a track link if f[0] == 'track': # check history if we added this track already f = open('pltracks.txt', 'r+') exists = False
def handle(): team = Team.objects.first() client = SlackClient(team.bot_access_token) webhook_url = os.environ.get('SLACK_WEBHOOK_SECRET') # ------------------------------------------------------------------------- slack_data = { "text": "new image", "attachments": [{ "text": "Optional text that appears within the attachment", "image_url": "https://www.cleverfiles.com/howto/wp-content/uploads/2016/08/mini.jpg", # "thumb_url": "https://en.wikipedia.org/wiki/Linux#/media/File:Tux.svg" }] } mesg_one = { "text": "Would you like to play a game?", "attachments": [{ "text": "Choose a game to play", "fallback": "You are unable to choose a game", "callback_id": "wopr_game", "color": "#3AA3E3", "attachment_type": "default", "actions": [{ "name": "game", "text": "Chess", "type": "button", "value": "chess" }, { "name": "game", "text": "Falken's Maze", "type": "button", "value": "maze" }, { "name": "game", "text": "Thermonuclear War", "style": "danger", "type": "button", "value": "war", "confirm": { "title": "Are you sure?", "text": "Wouldn't you prefer a good game of chess?", "ok_text": "Yes", "dismiss_text": "No" } }] }] } mesg_two = { "text": "New comic book alert!", "attachments": [{ "title": "The Further Adventures of Slackbot", "fields": [{ "title": "Volume", "value": "1", "short": True }, { "title": "Issue", "value": "3", "short": True }], "author_name": "Stanford S. Strickland", "author_icon": "http://a.slack-edge.com/7f18https://a.slack-edge.com/bfaba/img/api/homepage_custom_integrations-2x.png", "image_url": "http://i.imgur.com/OJkaVOI.jpg?1" }, { "title": "Synopsis", "text": "After @episod pushed exciting changes to a devious new branch back in Issue 1, Slackbot notifies @don about an unexpected deploy..." }, { "fallback": "Would you recommend it to customers?", "title": "Would you recommend it to customers?", "callback_id": "comic_1234_xyz", "color": "#3AA3E3", "attachment_type": "default", "actions": [{ "name": "recommend", "text": "Recommend", "type": "button", "value": "recommend" }, { "name": "no", "text": "No", "type": "button", "value": "bad" }] }] } # ---------------------------------------------------------------------------- if client.rtm_connect(): while True: events = client.rtm_read() print("%s----%s" % (team, events)) for event in events: print(event) if 'type' in event and event[ 'type'] == 'message' and event['text'] == 'hi': # client.rtm_send_message(event['channel'],"hello world") client.api_call( "chat.postMessage", channel=event['channel'], text="Hello from Python! :tada:", ) elif 'type' in event and event[ 'type'] == 'message' and event['text'] == 'image': requests.post( webhook_url, data=json.dumps(slack_data), headers={'Content-Type': 'application/json'}) elif 'type' in event and event[ 'type'] == 'message' and event['text'] == 'button1': requests.post( webhook_url, data=json.dumps(mesg_one), headers={'Content-Type': 'application/json'}) elif 'type' in event and event[ 'type'] == 'message' and event['text'] == 'button2': requests.post( webhook_url, data=json.dumps(mesg_two), headers={'Content-Type': 'application/json'}) time.sleep(1)
class NewsSlackBot(object): """ News Slackbot uses extensible news clients to fetch news articles from different sources. * News topic is determined by a channel's topic or purpose. """ def __init__(self, secrets_yaml='secrets.yml'): with open(secrets_yaml, 'rb') as secrets_file: self.config = config = yaml.load(secrets_file) self.bot = bot = { 'token': config['slackbot']['token'], 'id': config['slackbot'].get('id'), 'name': config['slackbot'].get('name'), } # Instantiate Slack client. self.slack_client = SlackClient(bot['token']) # Set bot id. if not bot['id']: if not bot['name']: raise EnvironmentError users_list = self.get('users.list') if not users_list.get('ok'): raise EnvironmentError users = [u for u in users_list.get('members') if 'name' in u] bot['id'] = next( u.get('id') for u in users if u['name'] == bot['name']) bot['at'] = '<@{id}>'.format(id=bot['id']) self._clients = None self.meta_by_channel_id = defaultdict( lambda: defaultdict(lambda: None)) self._channels = None, None def get(self, name, key=None): result = self.slack_client.api_call(name) return result if key is None else result[key] @staticmethod def update_purpose(meta, purpose): for line in purpose.splitlines(): try: key, value = (t.strip() for t in line.split(':', 1)) except ValueError: continue key = key.lower() if key == 'topic': meta[key].append(value) elif key == 'frequency': if value.isdigit(): meta[key] = int(value) elif key == 'limit': if value.isdigit(): meta[key] = int(value) elif key == 'language': if len(value) == 2 and value.isalpha(): meta[key] = value def parse_meta(self, channel): meta = self.meta_by_channel_id[channel['id']] meta.update( topic=[], frequency=60, limit=2, language='en', ) topic = channel['topic']['value'] if topic: meta['topic'].append(topic) self.update_purpose(meta, channel['purpose']['value']) return channel @property def channels(self): last_update, channels = self._channels now = datetime.now() if last_update is None or now - last_update > timedelta(minutes=10): channels = [ self.parse_meta(c) for c in self.get('channels.list', 'channels') if c['is_member'] ] self._channels = now, channels return channels def loop_forever(self): if self.slack_client.rtm_connect(): LOGGER.info('StarterBot connected and running!') while True: command, channel_id = self.parse_rtm_events( self.slack_client.rtm_read()) if command and channel_id: # Respond to messages. self.handle_command(command, channel_id) else: # Try to send news updates. self.send_news() sleep(1) else: LOGGER.critical( 'Connection failed. Invalid Slack token or bot ID?') def parse_rtm_events(self, events): """ The Slack Real Time Messaging API is an events firehose. This parsing function returns any for any non-empty message. """ at_bot = self.bot['at'] for evt in events: LOGGER.debug(evt) channel_id, text = evt.get('channel'), evt.get('text') if 'purpose' in evt: self.update_purpose(self.meta_by_channel_id[channel_id], evt['purpose']) if evt.get('user') == self.bot[ 'id'] or not text or evt['type'] != 'message': continue elif at_bot in text \ or channel_id not in map(itemgetter('id'), self.channels): # Handle command only if @mention or direct channel (IM). tokens = filter(None, (t.strip() for t in text.split(at_bot))) # return text, whitespace and @mention removed return ' '.join(t.lower() for t in tokens), channel_id return None, None def handle_command(self, cmd, channel): cmd = cmd.split()[0] response = 'I am unable to process commands at this time...' self.slack_client.api_call('chat.postMessage', channel=channel, text=response, as_user=True) @property def clients(self): """ Yield all of the NewsClients existing in non-private modules of the given package. """ if not self._clients: self._clients = [] for _, name, is_pkg in iter_modules([CLIENTS_PKG]): if not is_pkg and not name.startswith('_'): module = import_module('{pkg}.{client}'.format( pkg=CLIENTS_PKG, client=name)) if hasattr(module, 'NewsClient'): self._clients.append(module.NewsClient(self.config)) return self._clients def send_news(self): now = datetime.now() for client in self.clients: for channel in self.channels: meta = self.meta_by_channel_id[channel['id']] last_update = meta['last_update'] if last_update is None \ or last_update < now - timedelta(minutes=meta['frequency']): meta['last_update'] = now else: continue for article in client.fetch(topic=' OR '.join(meta['topic']), limit=meta['limit'], lang=meta['language']): self.slack_client.api_call('chat.postMessage', channel=channel['id'], text=article, as_user=True) sleep(1)
else: s1session.headers.update( {'Authorization': 'APIToken ' + s1CorrectAPICredentials}) if s1APICredentialCheck(): handle_command(command, channel, slackUserID) else: slackAPITokenPrompt("Your S1 API Token has expired") except Exception as e: slackS1SendMessage(channel, 'You broke me because of: \n' + str(e)) time.sleep(5) #TODO: MAKE THREADS TIME OUT if __name__ == "__main__": while True: if slack_client.rtm_connect(with_team_state=False, auto_reconnect=True): print("S1Bot 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"] while True: command, channel, slackUserID = parse_bot_commands( slack_client.rtm_read()) if command: thread.start_new_thread(handle_bot_command, (command, channel, slackUserID)) time.sleep(RTM_READ_DELAY) else: print("Connection failed. Exception traceback printed above.")
class Kairo: name = None slack_token = None slack_token_name = 'KAIRO_SLACK_TOKEN' slack_client = None users = None def __init__(self, name): self.name = name self.load_token_from_env(self.slack_token_name) def load_token_from_env(self, name): slack_token = os.environ.get(name) self.slack_token = slack_token def get_user_name_by_id(self, id): users = self.users return [user["profile"]["display_name"] for user in users.get("members", {}) if user["id"] == id][0] def get_sleep_time(self): return 1 def running(self): return True def parse_slack_message(self, slack_incoming_message): output_list = slack_incoming_message if output_list and len(output_list) > 0: for output in output_list: if output and 'text' in output: return output['text'].strip(), output['channel'], output['user'] return None, None, None def parse_input(self, command): parts = command.split(' ') size = len(parts) command = parts[0] if size > 0 else None count = 1 args = [] while count < size: argument = parts[count].strip() if argument is not '': args.append(argument) count = count + 1 return command, args def handle_command(self, text, channel, user): if text is not '': command, args = self.parse_input(text) key = self.get_key(command, args) if key in self.commands: action = self.commands[key] text = action(user, *args) if text is not None: self.send_response(text, channel) def start_bot(self, token=None): slack_token = token if (token is not None) else self.slack_token if slack_token is None: raise RuntimeError('missing slack token') self.slack_client = SlackClient(slack_token) self.users = self.slack_client.api_call("users.list") bot_id = self.slack_client.api_call("auth.test")["user_id"] bot_name = self.get_user_name_by_id(bot_id) sleep_delay = self.get_sleep_time() if self.slack_client.rtm_connect(): print(bot_name + " connected and running!") while self.running(): text = self.slack_client.rtm_read() command, channel, user = self.parse_slack_message(text) if command and channel: self.handle_command(command, channel, user) time.sleep(sleep_delay) else: print("Connection failed. Invalid Slack token or bot ID?") return True def send_response(self, text, channel): self.slack_client.api_call("chat.postMessage", channel=channel, text=text, as_user=True) def get_key(self, command, args): return command + str(len(args)) commands = {} def parse_command(self, text, command_function): command, args = self.parse_input(text) key = self.get_key(command, args) self.commands[key] = command_function def command(self, text): def _command(command_function): self.parse_command(text, command_function) return command_function return _command
#!/usr/bin/env python import os from slackclient import SlackClient import time # Globally important cooldown = 28800 # 28800 seconds = 8 hours last_message_from = {} slack_token = os.environ["SLACK_TOKEN"] autoresponder_message = "Your polite message goes here" # Execution starts here client = SlackClient(slack_token) if client.rtm_connect(auto_reconnect=True): myself = client.api_call("auth.test")["user_id"] while client.server.connected is True: events = client.rtm_read() # Process events from rtm; event format is an array of events for event in events: event_type = event.get("type") # Handle messages if event_type == "message": msg_sender = event.get("user") msg_channel = event.get("channel", "N/A") # Ignore messages from ourselves
class Slackbot: def __init__(self, token, client, id): self.TOKEN = token self.ID = id self.CLIENT = client self._inputqueue = Queue.Queue(50) self._outputqueue = Queue.Queue(50) self.users = {'USLACKBOT': 'slackbot'} self.channelids = [] def start(self): self.CLIENT = SlackClient(self.TOKEN) print self.CLIENT print 'CONNECTING...' if self.CLIENT.rtm_connect(): print 'CONNECTED.' channels = json.loads(self.CLIENT.api_call('channels.list', {}))['channels'] for chan in channels: self.channelids.append(chan['id']) print 'CHANNELS: %s' % self.channelids userlist = json.loads(self.CLIENT.api_call('users.list', {}))['members'] for user in userlist: self.users[str(user['id'])] = str(user['name']) print 'USERS: %s' % self.users inp = _inputThread(self.CLIENT, self._inputqueue) self.process = _processThread(self.CLIENT, self, self.channelids, self.users) out = _outputThread(self.CLIENT, self._outputqueue) inp.start() self.process.start() out.start() else: print "Connection Failed." # # functionality def quit(self): self.process.stop() self.onQuit() def sendMessage(self, channel, text): self._outputqueue.put({u'channel': channel, u'text': text}) def setTopic(self, channel, topic): self.channels.setTopic({u'channel': channel, u'topic': topic}) # # event handling done by subclass def onMessageReceived(self, channel, sender, message): # This function must be overridden by a class that inherits IRCbot. pass def onPrivateMessageReceived(self, channel, sender, message): # this function must be overriden by a class that inherits IRCbot. pass def onQuit(self): # this function should be overridden by the class that inherits this one pass
import os from slackclient import SlackClient client = SlackClient('<slackbot token>') def say_hello(data): try: textData = data['text'] if "<" in textData and ">" in textData: channel_id = data['channel'] thread_ts = data['ts'] user = data['user'] client.api_call('chat.postMessage', channel=channel_id, text=textData, thread_ts=thread_ts) except: print("") if client.rtm_connect(): while client.server.connected is True: for data in client.rtm_read(): if "type" in data and data["type"] == "message": say_hello(data) else: print("Connection Failed")
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: at_bot = "<@%s>" % BOT_ID if output and 'text' in output and at_bot in output[ 'text'] and 'channel' in output: # return text after the @ mention, whitespace removed return output['text'].split(at_bot)[1].strip(), \ output['channel'] return None, None if __name__ == "__main__": slack_client = SlackClient(SLACK_KEY) cape_client = CapeClient() if slack_client.rtm_connect(): print("Connected") else: print("Failed to connect") sys.exit() while True: message, channel = parse_slack_output(slack_client.rtm_read()) if message and channel: handle_question(message, channel, slack_client, cape_client) time.sleep(READ_WEBSOCKET_DELAY)
text=author + " a supprimé son rappel quotidien: " + data[3] ) else: result = make_response("Erreur de suppression du rappel", 402) return result def start_app(): # Start the flask app for root management app.run() 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"] ReminderManager.init_reminder_process(slack_client) PollManager.init_poll_process(slack_client) t = threading.Thread(target=start_app) t.daemon = True t.start() while True: command, channel, author = parse_bot_commands(slack_client.rtm_read())
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}]" ) return None 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
elif SlackApiKey == "": SlackApiKey = os.environ['SlackApiKey'] else: pass if DiscordApiKey == "": logger.error("There wasn't an API key for Discord specified. Quitting...") sys.exit(1) elif DiscordApiKey == "": DiscordApiKey = os.environ['DiscordApiKey'] else: pass # Connect to Slack. sc = SlackClient(SlackApiKey) sc.rtm_connect() # Set up URL's and headers for Discord. headers = { "Authorization": "Bot {}".format(DiscordApiKey), "User-Agent": "DiscordSlackBridge (http://reddit.com/r/teendeveloper, v0.1)", "Content-Type": "application/json", } lastMessage = '' # Run the Bridge # We have to query all users in slack to map their id to their names later. Querying every message # does not have sense, so we will just do it now and then flush the cache when someone will