import socket, re, json, argparse, emoji, csv # from decouple import config class DefaultUser(Exception): """Raised when you try send a message with the default user""" pass class CallbackFunction(Exception): """Raised when the callback function does not have (only) one required positional argument""" pass class TwitchChatIRC(): __HOST = 'irc.chat.twitch.tv' __DEFAULT_NICK = 'justinfan67420' __DEFAULT_PASS = 'SCHMOOPIIE' __PORT = 6667 __PATTERN = re.compile(r'@(.+?(?=\s+:)).*PRIVMSG[^:]*:([^\r\n]*)') __CURRENT_CHANNEL = None def __init__(self, username = None, password = None): self.__NICK = self.__DEFAULT_NICK self.__PASS = self.__DEFAULT_PASS # overwrite if specified if(username is not None): self.__NICK = username if(password is not None): self.__PASS = 'oauth:'+str(password).lstrip('oauth:') # create new socket self.__SOCKET = socket.socket() # start connection self.__SOCKET.connect((self.__HOST, self.__PORT)) print('Connected to',self.__HOST,'on port',self.__PORT) # log in self.__send_raw('CAP REQ :twitch.tv/tags') self.__send_raw('PASS ' + self.__PASS) self.__send_raw('NICK ' + self.__NICK) def __send_raw(self, string): self.__SOCKET.send((string+'\r\n').encode('utf-8')) def __print_message(self, message): print('['+message['tmi-sent-ts']+']',message['display-name']+':',emoji.demojize(message['message']).encode('utf-8').decode('utf-8','ignore')) def __recvall(self, buffer_size): data = b'' while True: part = self.__SOCKET.recv(buffer_size) data += part if len(part) < buffer_size: break return data.decode('utf-8')#,'ignore' def __join_channel(self,channel_name): channel_lower = channel_name.lower() if(self.__CURRENT_CHANNEL != channel_lower): self.__send_raw('JOIN #{}'.format(channel_lower)) self.__CURRENT_CHANNEL = channel_lower def is_default_user(self): return self.__NICK == self.__DEFAULT_NICK def close_connection(self): self.__SOCKET.close() print('Connection closed') def listen(self, channel_name, messages = [], timeout=None, message_timeout=1.0, on_message = None, buffer_size = 4096, message_limit = None, output=None): self.__join_channel(channel_name) self.__SOCKET.settimeout(message_timeout) if(on_message is None): on_message = self.__print_message print('Begin retrieving messages:') time_since_last_message = 0 readbuffer = '' try: while True: try: new_info = self.__recvall(buffer_size) readbuffer += new_info if('PING :tmi.twitch.tv' in readbuffer): self.__send_raw('PONG :tmi.twitch.tv') matches = list(self.__PATTERN.finditer(readbuffer)) if(matches): time_since_last_message = 0 if(len(matches) > 1): matches = matches[:-1] # assume last one is incomplete last_index = matches[-1].span()[1] readbuffer = readbuffer[last_index:] for match in matches: data = {} for item in match.group(1).split(';'): keys = item.split('=',1) data[keys[0]]=keys[1] data['message'] = match.group(2) print(data) messages.append(data) if(callable(on_message)): try: on_message(data) except TypeError: raise Exception('Incorrect number of parameters for function '+on_message.__name__) if(message_limit is not None and len(messages) >= message_limit): return messages except socket.timeout: if(timeout != None): time_since_last_message += message_timeout if(time_since_last_message >= timeout): print('No data received in',timeout,'seconds. Timing out.') break except KeyboardInterrupt: print('Interrupted by user.') except Exception as e: print('Unknown Error:',e) raise e return messages def send(self, channel_name, message): self.__join_channel(channel_name) # check that is using custom login, not default if(self.is_default_user()): raise DefaultUser else: self.__send_raw('PRIVMSG #{} :{}'.format(channel_name.lower(),message)) print('Sent "{}" to {}'.format(message,channel_name)) if __name__ == '__main__': parser = argparse.ArgumentParser(description='Send and receive Twitch chat messages over IRC with python web sockets. For more info, go to https://dev.twitch.tv/docs/irc/guide') parser.add_argument('channel_name', help='Twitch channel name (username)') parser.add_argument('-timeout','-t', default=None, type=float, help='time in seconds needed to close connection after not receiving any new data (default: None = no timeout)') parser.add_argument('-message_timeout','-mt', default=1.0, type=float, help='time in seconds between checks for new data (default: 1 second)') parser.add_argument('-buffer_size','-b', default=4096, type=int, help='buffer size (default: 4096 bytes = 4 KB)') parser.add_argument('-message_limit','-l', default=None, type=int, help='maximum amount of messages to get (default: None = unlimited)') parser.add_argument('-username','-u', default=None, help='username (default: None)') parser.add_argument('-oauth', '-password','-p', default=None, help='oath token (default: None). Get custom one from https://twitchapps.com/tmi/') parser.add_argument('--send', action='store_true', help='send mode (default: False)') parser.add_argument('-output','-o', default=None, help='output file (default: None = print to standard output)') args = parser.parse_args() twitch_chat_irc = TwitchChatIRC(username=args.username,password=args.oauth) if(args.send): if(twitch_chat_irc.is_default_user()): print('Unable to send messages with default user. Please provide valid authentication.') else: try: while True: message = input('>>> Enter message (blank to exit): \n') if(not message): break twitch_chat_irc.send(args.channel_name, message) except KeyboardInterrupt: print('\nInterrupted by user.') else: messages = twitch_chat_irc.listen( args.channel_name, timeout=args.timeout, message_timeout=args.message_timeout, buffer_size=args.buffer_size, message_limit=args.message_limit) if(args.output != None): if(args.output.endswith('.json')): with open(args.output, 'w') as fp: json.dump(messages, fp) elif(args.output.endswith('.csv')): with open(args.output, 'w', newline='',encoding='utf-8') as fp: fieldnames = [] for message in messages: fieldnames+=message.keys() if(len(messages)>0): fc = csv.DictWriter(fp,fieldnames=list(set(fieldnames))) fc.writeheader() fc.writerows(messages) else: f = open(args.output,'w', encoding='utf-8') for message in messages: print('['+message['tmi-sent-ts']+']',message['display-name']+':',message['message'],file=f) f.close() print('Finished writing',len(messages),'messages to',args.output) twitch_chat_irc.close_connection()