Files
UltraChat/twitchchatirc/twitch_chat_irc.py
sShemet ade2833df7 init
2025-12-22 14:03:10 +05:00

216 lines
6.8 KiB
Python

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()