if event.type != 'TWITCHCHATMESSAGE': return if event.message[0:2].upper() == '!Y': 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())
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 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')
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()