class TwitchChatInputView(view.View): """Class for handling Twitch chat input.""" def __init__(self): super().__init__() self.observer = None pubsub.subscribe('QUIT', self.on_quit) def on_quit(self): self._running = False def init(self): working_dir = os.path.dirname(__file__) config_path = os.path.normpath( os.path.join(working_dir, './config.cfg')) if not os.path.exists(config_path): raise RuntimeError('Missing Twitch chatbot config.cfg file') # Import Twitch bot settings config = configparser.ConfigParser() config.read(config_path) nickname = config['TWITCH']['Nickname'] password = config['TWITCH']['Password'] channel = config['TWITCH']['Channel'] self.observer = Observer(nickname, password) self.observer.start() self.observer.join_channel(channel) def deinit(self): self.observer.stop() def update(self, time): for event in self.observer.get_events(): if event.type == 'TWITCHCHATMESSAGE': move = event.message # Filter Twitch chat input if re.match('[a-h][1-8][a-h][1-8]', move): move = move.lower() player_name = event.nickname player_color = event.tags['color'] if not player_color: player_color = '#FFFFFF' hex_color = player_color.strip('#') r = int(hex_color[0:2], 16) g = int(hex_color[2:4], 16) b = int(hex_color[4:6], 16) player_color = r, g, b self.send_move(move, player_name, player_color)
observer.send_message('counted YES!', channel) votes[event.nickname] = 1 print('someone voted yes') elif event.message[0:2].upper() == '!N': observer.send_message('counted NO!', channel) votes[event.nickname] = -1 print('someone voted no') observer = Observer(nick, votebot_token.token) observer.subscribe(handle_event) observer.send_message('Vote !yes or !no. You have 60 seconds!', channel) observer.start() observer.join_channel(channel) time.sleep(30) observer.send_message('... 30 seconds left ...', channel) time.sleep(30) observer.unsubscribe(handle_event) observer.send_message('Voting is over!', channel) time.sleep(2) tally = sum(votes.values()) if tally > 0: observer.send_message('The yeas have it!', channel) elif tally < 0:
class Game(object): args = None scene_root = None config = None instance = None tick_count = 0 seconds_per_tick = 0 def __init__(self, args): Game.args = args # Configure game settings config = configparser.ConfigParser() cfg_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'config.cfg') config.read(cfg_path) Game.config = config # Configure tdl tdl.set_font(Game.config['ENGINE']['font']) tdl.set_fps(int(Game.config['ENGINE']['fps'])) self.console = tdl.init(54, 30, 'lunch break roguelike', renderer=Game.config['ENGINE']['renderer']) self._last_time = time.time() Game.seconds_per_tick = float(Game.config['GAME']['turn']) if not Game.instance: Game.instance = self instances.register('game', self) # Twitch Observer nickname = Game.config['TWITCH']['Nickname'] password = Game.config['TWITCH']['Password'] self.channel = Game.config['TWITCH']['Channel'] self.observer = Observer(nickname, password) self.observer.start() self.observer.join_channel(self.channel) self.start_time = time.time() Game.scene_root = gamescene.GameScene() @property def time_since_start(self): return time.time() - self.start_time def run(self): timer = 0 last_time = 0 running = True self.start_time = time.time() while running: # Draw the scene self.console.clear() Game.scene_root.draw(self.console) tdl.flush() # Handle input/events for event in list(tdl.event.get()) + self.observer.get_events(): Game.scene_root.handle_events(event) if event.type == 'QUIT': running = False self.observer.stop() # Update scene time_elapsed = time.time() - last_time timer += time_elapsed last_time = time.time() Game.scene_root.update(time_elapsed) # Send out tick event if timer > Game.seconds_per_tick: timer = 0 Game.tick_count += 1 Game.scene_root.tick(Game.tick_count)
class TestBasicFunctionality(unittest.TestCase): def setUp(self): self.observer = Observer('nickname', 'password123') self.observer._inbound_poll_interval = 0 self.observer._outbound_send_interval = 0 self._patcher = mock.patch('socket.socket', spec=True) self.mock_socket = self._patcher.start() self.mock_socket.return_value.connect.return_value = None self.mock_socket.return_value.recv.side_effect = [''.encode('utf-8')] def tearDown(self): if self.observer: self.observer.stop(force_stop=True) self.observer = None self._patcher.stop() def test_connect(self): self.mock_socket.return_value.recv.side_effect = [ SUCCESSFUL_LOGIN_MESSAGE ] self.observer.start() self.assertEqual(self.observer._nickname, 'nickname', 'Nickname should be set') self.assertEqual(self.observer._password, 'password123', 'Password should be set') self.assertIsNotNone(self.observer._inbound_worker_thread, 'Inbound worker thread should be running') self.assertIsNotNone(self.observer._outbound_worker_thread, 'Outbound worker thread should be running') self.assertTrue(self.observer._is_running, 'The observer should be running') self.observer.stop(force_stop=True) self.assertIsNone(self.observer._inbound_worker_thread, 'Inbound worker thread should not be running') self.assertIsNone(self.observer._outbound_worker_thread, 'Outbound worker thread should not be running') self.assertFalse(self.observer._is_running, 'The observer should be stopped') def test_failed_connect(self): self.mock_socket.return_value.recv.side_effect = [ UNSUCCESSFUL_LOGIN_MESSAGE ] with self.assertRaises(RuntimeError): self.observer.start() def test_server_ping(self): self.mock_socket.return_value.recv.side_effect = [ SUCCESSFUL_LOGIN_MESSAGE, SERVER_PING_MESSAGE ] self.observer.start() self.assertEqual(self.mock_socket.return_value.send.call_args[0][0], CLIENT_PONG_MESSAGE, 'Observer should respond with PONG response') def test_subscribe_unsubscribe(self): def handler(event): pass self.assertEqual(len(self.observer._subscribers), 0, 'There should be no subscribers') self.observer.subscribe(handler) self.assertEqual(len(self.observer._subscribers), 1, 'There should be a single subscriber') self.observer.unsubscribe(handler) self.assertEqual(len(self.observer._subscribers), 0, 'The subscriber should be removed') def test_receive_privmsg(self): self.mock_socket.return_value.recv.side_effect = [ SERVER_PRIVMSG_MESSAGE ] self.callback_invoked = False def verify_event(event): self.callback_invoked = True self.assertEqual(event.type, EventType.MESSAGE, "Type should be " + EventType.MESSAGE) self.assertEqual(event.nickname, 'nickname', "Nickname should be 'nickname'") self.assertEqual(event.message, 'message', "Message should be 'message'") self.assertEqual(event.channel, 'channel', "Channel should be 'channel'") self.observer.subscribe(verify_event) self.observer.start() self.assertTrue(self.callback_invoked, 'Subscriber callback should be invoked') def test_send_privmsg(self): self.observer.start() self.observer.send_message('message', 'channel') self.observer.stop() self.assertEqual(self.mock_socket.return_value.send.call_args[0][0], CLIENT_PRIVMSG_MESSAGE, 'Observer should respond with PRIVMSG response') def test_receive_join(self): self.mock_socket.return_value.recv.side_effect = [SERVER_JOIN_MESSAGE] self.callback_invoked = False def verify_event(event): self.callback_invoked = True self.assertEqual(event.type, EventType.JOIN, "Type should be " + EventType.JOIN) self.assertEqual(event.nickname, 'nickname', "Nickname should be 'nickname'") self.assertEqual(event.channel, 'channel', "Channel should be 'channel'") self.observer.subscribe(verify_event) self.observer.start() self.assertTrue(self.callback_invoked, 'Subscriber callback should be invoked') def test_send_join(self): self.observer.start() self.observer.join_channel('channel') self.observer.stop() self.assertEqual(self.mock_socket.return_value.send.call_args[0][0], CLIENT_JOIN_MESSAGE, 'Observer should respond with JOIN response') def test_receive_part(self): self.mock_socket.return_value.recv.side_effect = [SERVER_PART_MESSAGE] self.callback_invoked = False def verify_event(event): self.callback_invoked = True self.assertEqual(event.type, EventType.LEAVE, "Type should be " + EventType.LEAVE) self.assertEqual(event.nickname, 'nickname', "Nickname should be 'nickname'") self.assertEqual(event.channel, 'channel', "Channel should be 'channel'") self.observer.subscribe(verify_event) self.observer.start() self.assertTrue(self.callback_invoked, 'Subscriber callback should be invoked') def test_send_part(self): self.observer.start() self.observer.leave_channel('channel') self.observer.stop() self.assertEqual(self.mock_socket.return_value.send.call_args[0][0], CLIENT_PART_MESSAGE, 'Observer should respond with PART response') def test_receive_whisper(self): self.mock_socket.return_value.recv.side_effect = [ SERVER_WHISPER_MESSAGE ] self.callback_invoked = False def verify_event(event): self.callback_invoked = True self.assertEqual(event.type, EventType.WHISPER, "Type should be " + EventType.WHISPER) self.assertEqual(event.nickname, 'nickname', "Nickname should be 'nickname'") self.assertEqual(event.message, 'message', "Message should be 'message'") self.observer.subscribe(verify_event) self.observer.start() self.assertTrue(self.callback_invoked, 'Subscriber callback should be invoked') def test_send_whisper(self): self.observer.start() self.observer.send_whisper('nickname', 'message') self.observer.stop() self.assertEqual(self.mock_socket.return_value.send.call_args[0][0], CLIENT_WHISPER_MESSAGE, 'Observer should respond with PRIVMSG response') def test_receive_userstate_tags(self): self.mock_socket.return_value.recv.side_effect = [ SERVER_USERSTATE_TAGS_MESSAGE ] self.callback_invoked = False def verify_event(event): self.callback_invoked = True expected_tags = { 'display-name': 'nickname', 'emote-sets': '0', 'mod': '1', 'color': '', 'badges': 'moderator/1', 'user-type': 'mod', 'subscriber': '0' } self.assertEqual(event.type, EventType.USERSTATE, "Type should be " + EventType.USERSTATE) self.assertEqual(event.nickname, 'tmi.twitch.tv', "Nickname should be 'tmi.twitch.tv'") self.assertEqual(event.tags, expected_tags, 'Event tags should be equal') self.observer.subscribe(verify_event) self.observer.start() self.assertTrue(self.callback_invoked, 'Subscriber callback should be invoked') def test_truncated_messages(self): # Bit of a hack. Because the main thread handles consuming the first # server response, we need to first supply a dummy message that gets # ignored. self.mock_socket.return_value.recv.side_effect = [ ''.encode('utf-8'), SERVER_PRIVMSG_MESSAGE[:18], SERVER_PRIVMSG_MESSAGE[18:] ] self.callback_invoked = False def verify_event(event): self.callback_invoked = True self.assertEqual(event.type, EventType.MESSAGE, "Type should be " + EventType.MESSAGE) self.assertEqual(event.nickname, 'nickname', "Nickname should be 'nickname'") self.assertEqual(event.message, 'message', "Message should be 'message'") self.assertEqual(event.channel, 'channel', "Channel should be 'channel'") self.observer.subscribe(verify_event) self.observer.start() self.assertTrue(self.callback_invoked, 'Subscriber callback should be invoked') def test_context_manager_force_stop(self): with Observer('nickname', 'password123') as observer: observer.stop(force_stop=True) self.assertTrue( len(observer._outbound_event_queue) == 0, 'Outbound event queue should be empty') self.assertTrue( len(observer._inbound_event_queue) == 0, 'Inbound event queue should be empty') def test_on_event_decorator(self): self.mock_socket.return_value.recv.side_effect = [ SERVER_PRIVMSG_MESSAGE ] self.callback_invoked = False self.assertEqual(len(self.observer._subscribers), 0, 'There should be no subscribers') @self.observer.on_event(EventType.MESSAGE) def verify_event(event): self.callback_invoked = True self.assertEqual(event.type, EventType.MESSAGE, "Type should be " + EventType.MESSAGE) self.assertEqual(len(self.observer._subscribers), 1, 'There should be a single subscriber') self.observer.start() self.assertTrue(self.callback_invoked, 'Subscriber callback should be invoked')
def main(): # Networking functions, joins channel and sends a message, retrieves modlist # To do: # add custom commands obs = Observer(cfg.NICK, cfg.PASS) obs.start() obs.join_channel(cfg.CHAN) #obs.send_message("Hi everyone <3", cfg.CHAN) obs.send_message("/mods", cfg.CHAN) def get_votes(): if event.type == 'TWITCHCHATMESSAGE': votes = {} votes.clear() timeout = 70 timeout_start = time.time() while time.time() < timeout_start + timeout: time.sleep(0.25) try: if event.message in str(voteOptions): votes[event.nickname] = event.message except: 'do nothing' c = Counter(votes.values()) c = str(c) c = c.split('{') c = c[1] c = c.split('}') c = c[0] c = str(c) results = c.split(',') for result in results: i = result.split(':') message = 'the result of option ' + i[ 0] + ' is a total of ' + i[1] + ' votes' botmsg(message) obs.send_message(message, cfg.CHAN) #botmsg('The results of the voting is: ' + str(message)) print(votes) print(c) ######################################################################## ############ Log, Modlist, Caps Filter while True: for event in obs.get_events(): # print Messages in Terminal # for Debugging reasons if event.type == 'TWITCHCHATMESSAGE': print(event.nickname, ": ", event.message) # get modlist # gets a list of all mods of the channel # and saves them in modlist if event.type == 'TWITCHCHATNOTICE' and "The moderators of this channel are:" in event.message: modlist = event.message.split(":") modlist = modlist[1] # to Do get active list username + ID # create active viewerlist if event.type == 'TWITCHCHATUSERSTATE': activeViewer = {} name = str(event._params) name = name.split('#') name = name[1] client = TwitchClient(cfg.ClientID) try: nameid = client.users.translate_usernames_to_ids(name) except: continue nameid = str(nameid) nameid = nameid.split(",") nameid = nameid[1] nameid = nameid.split(":") nameid = nameid[1] nameid = nameid.split("'") nameid = nameid[1] activeViewer[name] = nameid activeViewer.setdefault(name, nameid) # delete user from list if they leave if event.type == 'TWITCHCHATLEAVE': name = str(event.nickname) try: del activeViewer[name] except: 'do nothing' ######################################################################## ####### Kuu commands # Hype if event.type == 'TWITCHCHATMESSAGE' and event.message == "!hype" and event.nickname == 'chris_lee_bear': message = "sunbroLewd sunbroLewd sunbroLewd sunbroLewd sunbroLewd sunbroLewd sunbroLewd sunbroLewd sunbroLewd sunbroLewd " obs.send_message(message, cfg.CHAN) botmsg(message) # Bye if event.type == 'TWITCHCHATMESSAGE' and "say goodbye bot" in event.message and event.nickname == 'chris_lee_bear': message = "master said I have to say goodbye... see you soon everyone <3" obs.send_message(message, cfg.CHAN) botmsg(message) #Sub notice if event.type == 'TWITCHCHATNOTICE' and "subscribed" in event.message: message1 = "Thanks for the Sub <3" message2 = "sunbroLewd sunbroLewd sunbroLewd sunbroLewd sunbroLewd sunbroLewd sunbroLewd sunbroLewd sunbroLewd sunbroLewd " obs.send_message(message1, cfg.CHAN) obs.send_message(message2, cfg.CHAN) botmsg(message1) botmsg(message2) # Voting if event.type == 'TWITCHCHATMESSAGE': if "!voting" in event.message and ( event.nickname in modlist or event.nickname == 'chris_lee_bear' or event.nickname == cfg.CHAN): try: voteOptions = [] message = event.message.split(' ') message = message[1] message = int(message) + 1 voteOptions = list(range(message)) del voteOptions[0] print(voteOptions) #obs.send_message('Voting will be open for 60 seconds. To voty simply type the number in chat you want to vote for. The options are '+ str(voteOptions), cfg.CHAN) #time.sleep(1) t = threading.Thread(target=get_votes) t.daemon = True t.start() except: print('do nothing') if event.type == 'TWITCHCHATMESSAGE' and event.message == '!voteinfo': message = ( 'If voting is enabled, to vote simply type the number in chat you want to vote for and remember you can change your vote but only the last vote will be counted' ) botmsg(message) obs.send_message(message, cfg.CHAN) ############# Custom Commands # Kappa Command also known as test command if event.type == 'TWITCHCHATMESSAGE' and event.message == "!Kappa": user = str(event.nickname) message = "@" + user + " Kappa" obs.send_message(message, cfg.CHAN) botmsg(message) # Shoutout if event.type == 'TWITCHCHATMESSAGE' and "!so" in event.message and ( event.nickname in modlist or event.nickname == cfg.CHAN): try: b = event.message.split(" ") b = b[1] b = str(b) message = "please check out this awesome person @" + b + " at: https://www.twitch.tv/" + b obs.send_message(message, cfg.CHAN) botmsg(message) except: continue # mod list test if event.type == 'TWITCHCHATMESSAGE' and event.message == "!mod": message = "the awesome people that help me doing my thing :" + modlist obs.send_message(message, cfg.CHAN) botmsg(message) # User ID test if event.type == 'TWITCHCHATMESSAGE' and event.message == "!user": client = TwitchClient(cfg.ClientID) message = client.users.translate_usernames_to_ids( event.nickname) message = str(message) message = message.split(",") message = message[1] message = message.split(":") message = message[1] message = message.split("'") message = message[1] obs.send_message(message, cfg.CHAN) botmsg(message) if event.type == 'TWITCHCHATMESSAGE' and event.message == "!list": message = str(activeViewer[name]) obs.send_message(message, cfg.CHAN) botmsg(message) #### sql part #fill_viewer_list if event.type == 'TWITCHCHATMESSAGE': client = TwitchClient(cfg.ClientID) try: conn = db.connect(dbname=cfg.DBname, user=cfg.DBuser, password=cfg.DBpassword, host=cfg.DBhost, port=cfg.DBport) conn.autocommit = True viewer = str(event.nickname) viewerID = client.users.translate_usernames_to_ids( event.nickname) viewerID = str(viewerID) viewerID = viewerID.split(",") viewerID = viewerID[1] viewerID = viewerID.split(":") viewerID = viewerID[1] viewerID = viewerID.split("'") viewerID = int(viewerID[1]) joindate = str(time.strftime("%Y.%m.%d")) points = int(0) # insert data in table cursor = conn.cursor() query = """INSERT INTO {0}.viewerlist ( viewer, viewerID, joindate, points ) Values( %(viewer)s, %(viewerID)s, %(joindate)s, %(points)s ) on CONFLICT (viewerID) DO NOTHING """.format(cfg.CHAN) cursor.execute( query, { 'viewer': viewer, 'viewerID': viewerID, 'joindate': joindate, 'points': points }) cursor.close() conn.close except: print("fill list error") ### add points sql.addPoints(cfg.CHAN, activeViewer[name])
import cfg import sqlalchemy as sql import psycopg2 as db from twitchobserver import Observer from twitch import TwitchClient import time import threading obs = Observer(cfg.NICK, cfg.PASS) obs.start() obs.join_channel(cfg.CHAN) # create a schema and table for the users and their streampoints def create_viewer_list(schema): conn = db.connect(dbname=cfg.DBname, user=cfg.DBuser, password=cfg.DBpassword, host=cfg.DBhost, port=cfg.DBport) conn.autocommit = True # create schema and table viewer list cursor = conn.cursor() cursor.execute('CREATE SCHEMA IF NOT EXISTS {0}'.format(schema)) cursor.execute("""CREATE TABLE IF NOT EXISTS {0}.viewerlist ( id SERIAL, viewer VARCHAR(80) , viewerID NUMERIC PRIMARY KEY,
async def main_program(websocket, path): global inc_msg global voting_time global anarchy_mode global obs_created print(Fore.CYAN + "Websocket started on %s" % (websocket.local_address, )) if not obs_created: print(Fore.CYAN + "creating observer") obs = Observer(BotName, BotOauth) obs.start() obs.join_channel(TwitchChannel) obs.send_message('hello!', TwitchChannel) obs.subscribe(handle_event) print(Fore.CYAN + "done creating") obs_created = True async for message in websocket: if message != "ConnTest": print(Fore.YELLOW + message + " " + time.strftime("%H:%M:%S", time.gmtime())) if inc_msg != "null": print(Fore.MAGENTA + inc_msg) if message == "Connected Message!": await websocket.send("Serv connect!") print(Fore.YELLOW + "connected!") elif message == "ConnTest": if inc_msg == "votetime": print(Fore.YELLOW + "time to vote!") obs.send_message('Voting time: start!', TwitchChannel) voting_time = True await websocket.send(inc_msg) elif inc_msg == "VoteInfo": print("sending info") for i in votes: #print(i) await websocket.send("VoteInfo\n" + i.nick + "\n" + i.message) votes.clear() elif inc_msg == "PrintTwitchChat": for i in chatMessages: #print(i) await websocket.send("PrintTwitchChat\n" + i.nick + "\n" + i.message) chatMessages.clear() else: if anarchy_mode: await websocket.send(inc_msg) inc_msg = "null" elif message == "VoteTime": voting_time = True elif message.startswith("VoteActions"): actions = message.split(';') for i in range(0, len(actions)): if i == 0: continue obs.send_message(actions[i], TwitchChannel) elif message == "VoteOver": voting_time = False elif message == "anarchymode": anarchy_mode = True elif message == "democracymode": anarchy_mode = False elif message == "shutdown": obs.send_message('shutting down!', TwitchChannel) start_server.ws_server.close() asyncio.get_event_loop().stop() obs.leave_channel(TwitchChannel) obs.stop() deinit() os.execv(sys.executable, ['python'] + ['chatbot.py'])
class ChatBot(): evt_filter = [ "TWITCHCHATJOIN", "TWITCHCHATMODE", "TWITCHCHATMESSAGE", "TWITCHCHATUSERSTATE", "TWITCHCHATROOMSTATE", "TWITCHCHATLEAVE" ] evt_types = ["TWITCHCHATMESSAGE"] handled_commands = ["CLEARMSG", "RECONNECT", "HOSTTARGET", "CLEARCHAT"] def __init__(self, loop, bot_user, oauth): self.notifications = [] self.channels = set() self.observer = Observer(bot_user, oauth) self.observer._inbound_poll_interval = 0 retry = 5 while retry: try: self.observer.start() break except Exception as e: print(e) time.sleep(2) retry -= 1 continue self.loop = loop self.chat_queue = asyncio.Queue(maxsize=100) self.observer.subscribe(self.handle_event) def subscribe(self, channel): logger.debug(f"Subscribe: {channel}") if not self.observer._socket: logger.error("Twitch chat Socket not connected," " Attempting to reconnect.") self.observer.stop() self.observer.start() if channel not in self.channels: logger.info(f"Joining channel: {channel}") self.observer.join_channel(channel) self.channels.add(channel) else: logger.debug(f"Already subbed to channel: {channel}") def unsubscribe(self, channel): logger.debug(f"unsubscribe: {channel}") self.observer.leave_channel(channel) if channel not in self.channels: logger.debug(f"unsubscribing from channel not subbed: {channel}") else: logger.info(f"Leaving channel: {channel}") self.channels.remove(channel) def chat(self): return self.chat_queue def add_task(self, coro): """Add a task into a loop on another thread.""" def _async_add(func, fut): try: ret = func() fut.set_result(ret) except Exception as e: fut.set_exception(e) f = functools.partial(self.loop.create_task, coro) # We're in a non-event loop thread so we use a Future # to get the task from the event loop thread once # it's ready. fut = Future() self.loop.call_soon_threadsafe(_async_add, f, fut) return fut.result() def handle_event(self, evt): logger.debug(evt) if evt.type == 'TWITCHCHATMESSAGE': try: self.add_task(self.chat_queue.put(evt)) except asyncio.QueueFull: logger.error("Queue full, discarding message") return elif evt.type in self.evt_filter: return elif evt.type == "TWITCHCHATUSERNOTICE": msg_id = evt.tags['msg-id'] if msg_id == "charity": logger.info("Chraity stuff") elif msg_id == "sub": evt.type = "SUB" logger.info(f"SUB {evt.tags['display-name']} subscribed") elif msg_id == "resub": evt.type = "SUB" logger.info( f"SUB {evt.tags['display-name']} subscribed for " f"{evt.tags['msg-param-cumulative-months']} months") elif msg_id == "subgift": logger.info( f"SUB {evt.tags['display-name']} gifted a subscription to " f"{evt.tags['msg-param-recipient-display-name']}") evt.type = "SUBGIFT" elif msg_id == "raid": logger.info( f"RAID {evt.tags['display-name']} is raiding with a party of " f"{evt.tags['msg-param-viewerCount']}") evt.type = "RAID" elif msg_id in ["host", "host_success", "host_success_viewers"]: evt.type = "HOST" logger.info(f"HOST {evt}") try: self.add_task(self.chat_queue.put(evt)) except asyncio.QueueFull: logger.error("Queue full, discarding alert") elif evt.type == "TWITCHCHATCOMMAND" or \ evt.type == "TWITCHCHATCLEARCHAT" or \ evt.type == "TWITCHCHATHOSTTARGET": if evt._command in self.handled_commands: logger.debug(evt._command) self.add_task(self.chat_queue.put(evt)) def send_message(self, message, channel): if not message: return if channel not in self.channels: logger.warn( "Sending a message to a channel we're not in: {channel}") self.observer.join_channel(channel) self.observer.send_message(message, channel) if channel not in self.channels: self.observer.leave_channel(channel) def close(self): for c in self.channels: logger.info(f"closing chat {c}") self.observer.leave_channel(c) self.observer.stop()