예제 #1
0
    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())

if tally > 0:
    observer.send_message('The yeas have it!', channel)
예제 #2
0
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')
예제 #3
0
                            # duplicate message
                            continue
                        else:
                            seenBefore.add(event.tags.get("id"))
                print(event)
                if event.type == 'TWITCHCHATMESSAGE':
                    if CHANNEL_1 == event.channel:
                        chan_ind = 0  #came from first chan
                    else:
                        chan_ind = 1  #came from first chan
                    # add to appropriate list
                    chan_replies[chan_ind].append(event.message)
                    # add to master list
                    all_replies.append(event.message)
                    reply = message(event.message, event.channel)
                    if (reply):
                        all_replies.append(reply)
                        observer.send_message(reply, event.channel)

            # never post too fast, even in response to messages that arrive. Twitch will block us.
            time.sleep(0.1)

    except KeyboardInterrupt:
        observer.leave_channel(CHANNEL_1)
        observer.leave_channel(CHANNEL_2)

while len(all_replies) < 1024:
    all_replies = all_replies + all_replies
with open('allreplies.txt', 'w') as filehandle:
    filehandle.writelines("%s\n" % place for place in all_replies)
예제 #4
0
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])
예제 #5
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'])
예제 #6
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()