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 = 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: observer.send_message('The nays have it!', channel) else: observer.send_message('Its a draw!', channel) observer.leave_channel(channel) observer.stop()
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')
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()