Exemple #1
0
    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())
Exemple #2
0
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')
Exemple #4
0
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()