TwitchPlays_Connection.py
for Python 3.x
[ published on 2021-12-16 ]

  1. # DougDoug Note: 

  2. # This is the code that connects to Twitch and checks for new messages.

  3. # You should not need to modify anything in this file, just use as is.

  4. # Original code by Wituz, updated by DDarknut

  5.  

  6. import sys

  7. import socket

  8. import re

  9. import random

  10. import time

  11.  

  12. MAX_TIME_TO_WAIT_FOR_LOGIN = 3

  13.  

  14. class Twitch:

  15.     re_prog = None

  16.     sock = None

  17.     partial = b''

  18.     login_ok = False

  19.     channel = ''

  20.     login_timestamp = 0

  21.  

  22.     def twitch_connect(self, channel):

  23.         if self.sock: self.sock.close()

  24.         self.sock = None

  25.         self.partial = b''

  26.         self.login_ok = False

  27.         self.channel = channel

  28.  

  29.         # Compile regular expression

  30.         self.re_prog = re.compile(b'^(?::(?:([^ !\r\n]+)![^ \r\n]*|[^ \r\n]*) )?([^ \r\n]+)(?: ([^:\r\n]*))?(?: :([^\r\n]*))?\r\n', re.MULTILINE)

  31.  

  32.         # Create socket

  33.         print('Connecting to Twitch...')

  34.         self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

  35.  

  36.         # Attempt to connect socket

  37.         self.sock.connect(('irc.chat.twitch.tv', 6667))

  38.  

  39.         # Log in anonymously

  40.         user = 'justinfan%i' % random.randint(10000, 99999)

  41.         print('Connected to Twitch. Logging in anonymously...')

  42.         self.sock.send(('PASS asdf\r\nNICK %s\r\n' % user).encode())

  43.  

  44.         self.sock.settimeout(1.0/60.0)

  45.  

  46.         self.login_timestamp = time.time()

  47.  

  48.     # Attempt to reconnect after a delay

  49.     def reconnect(self, delay):

  50.         time.sleep(delay)

  51.         self.twitch_connect(self.channel)

  52.  

  53.     # Returns a list of irc messages received

  54.     def receive_and_parse_data(self):

  55.         buffer = b''

  56.         while True:

  57.             received = b''

  58.             try:

  59.                 received = self.sock.recv(4096)

  60.             except socket.timeout:

  61.                 break

  62.             # except OSError as e:

  63.             #     if e.winerror == 10035:

  64.             #         # This "error" is expected -- we receive it if timeout is set to zero, and there is no data to read on the socket.

  65.             #         break

  66.             except Exception as e:

  67.                 print('Unexpected connection error. Reconnecting in a second...', e)

  68.                 self.reconnect(1)

  69.                 return []

  70.             if not received:

  71.                 print('Connection closed by Twitch. Reconnecting in 5 seconds...')

  72.                 self.reconnect(5)

  73.                 return []

  74.             buffer += received

  75.  

  76.         if buffer:

  77.             # Prepend unparsed data from previous iterations

  78.             if self.partial:

  79.                 buffer = self.partial + buffer

  80.                 self.partial = []

  81.  

  82.             # Parse irc messages

  83.             res = []

  84.             matches = list(self.re_prog.finditer(buffer))

  85.             for match in matches:

  86.                 res.append({

  87.                     'name':     (match.group(1) or b'').decode(errors='replace'),

  88.                     'command':  (match.group(2) or b'').decode(errors='replace'),

  89.                     'params':   list(map(lambda p: p.decode(errors='replace'), (match.group(3) or b'').split(b' '))),

  90.                     'trailing': (match.group(4) or b'').decode(errors='replace'),

  91.                 })

  92.  

  93.             # Save any data we couldn't parse for the next iteration

  94.             if not matches:

  95.                 self.partial += buffer

  96.             else:

  97.                 end = matches[-1].end()

  98.                 if end < len(buffer):

  99.                     self.partial = buffer[end:]

  1.                 if matches[0].start() != 0:

  2.                     # If we get here, we might have missed a message. pepeW

  3.                     # ⣿⣿⣿⣿⣿⣿⣿⠿⢛⢛⡛⡻⢿⣿⣿⣿⣿⠟⠛⢛⠻⢿⣿⣿⣿⣿⣿⣿⣿⣿

  4.                     # ⣿⣿⣿⣿⢟⢱⡔⡝⣜⣜⢜⢜⡲⡬⡉⢕⢆⢏⢎⢇⢇⣧⡉⠿⣿⣿⣿⣿⣿⣿

  5.                     # ⣿⣿⡟⡱⣸⠸⢝⢅⢆⢖⣜⣲⣵⣴⣱⣈⡣⣋⢣⠭⣢⣒⣬⣕⣄⣝⡻⢿⣿⣿

  6.                     # ⣿⠟⡜⣎⢎⢇⢇⣵⣷⣿⣿⡿⠛⠉⠉⠛⢿⣦⢵⣷⣿⣿⣿⠟⠛⠋⠓⢲⡝⣿

  7.                     # ⢏⢰⢱⣞⢜⢵⣿⣿⣿⣿⣿⠁⠐⠄⠄⠄⠄⢹⣻⣿⣿⣿⠡⠄⠄⠄⠄⠄⠹⣺

  8.                     # ⢕⢜⢕⢕⢵⠹⢿⣿⣿⣿⣿⡀⠸⠗⣀⠄⠄⣼⣻⣿⣿⣿⡀⢾⠆⣀⠄⠄⣰⢳

  9.                     # ⡕⣝⢜⡕⣕⢝⣜⢙⢿⣿⣿⣷⣦⣤⣥⣤⣾⢟⠸⢿⣿⣿⣿⣦⣄⣉⣤⡴⢫⣾

  10.                     # ⡪⡪⣪⢪⢎⢮⢪⡪⡲⢬⢩⢩⢩⠩⢍⡪⢔⢆⢏⡒⠮⠭⡙⡙⠭⢝⣨⣶⣿⣿

  11.                     # ⡪⡪⡎⡮⡪⡎⡮⡪⣪⢣⢳⢱⢪⢝⢜⢜⢕⢝⢜⢎⢧⢸⢱⡹⡍⡆⢿⣿⣿⣿

  12.                     # ⡪⡺⡸⡪⡺⣸⠪⠚⡘⠊⠓⠕⢧⢳⢹⡸⣱⢹⡸⡱⡱⡕⡵⡱⡕⣝⠜⢿⣿⣿

  13.                     # ⡪⡺⡸⡪⡺⢐⢪⢑⢈⢁⢋⢊⠆⠲⠰⠬⡨⡡⣁⣉⠨⡈⡌⢥⢱⠐⢕⣼⣿⣿

  14.                     # ⡪⣪⢣⢫⠪⢢⢅⢥⢡⢅⢅⣑⡨⡑⠅⠕⠔⠔⠄⠤⢨⠠⡰⠠⡂⣎⣼⣿⣿⣿

  15.                     # ⠪⣪⡪⡣⡫⡢⡣⡣⡣⡣⡣⣣⢪⡪⡣⡣⡲⣑⡒⡎⡖⢒⣢⣥⣶⣿⣿⣿⣿⣿

  16.                     # ⢁⢂⠲⠬⠩⣁⣙⢊⡓⠝⠎⠮⠮⠚⢎⡣⡳⠕⡉⣬⣶⣿⣿⣿⣿⣿⣿⣿⣿⣿

  17.                     # ⢐⠐⢌⠐⠅⡂⠄⠄⢌⢉⠩⠡⡉⠍⠄⢄⠢⡁⡢⠠⠻⣿⣿⣿⣿⣿⣿⣿⣿⣿

  18.                     print('either ddarknut fucked up or twitch is bonkers, or both I mean who really knows anything at this point')

  19.  

  20.             return res

  21.  

  22.         return []

  23.  

  24.     def twitch_receive_messages(self):

  25.         privmsgs = []

  26.         for irc_message in self.receive_and_parse_data():

  27.             cmd = irc_message['command']

  28.             if cmd == 'PRIVMSG':

  29.                 privmsgs.append({

  30.                     'username': irc_message['name'],

  31.                     'message': irc_message['trailing'],

  32.                 })

  33.             elif cmd == 'PING':

  34.                 self.sock.send(b'PONG :tmi.twitch.tv\r\n')

  35.             elif cmd == '001':

  36.                 print('Successfully logged in. Joining channel %s.' % self.channel)

  37.                 self.sock.send(('JOIN #%s\r\n' % self.channel).encode())

  38.                 self.login_ok = True

  39.             elif cmd == 'JOIN':

  40.                 print('Successfully joined channel %s' % irc_message['params'][0])

  41.             elif cmd == 'NOTICE':

  42.                 print('Server notice:', irc_message['params'], irc_message['trailing'])

  43.             elif cmd == '002': continue

  44.             elif cmd == '003': continue

  45.             elif cmd == '004': continue

  46.             elif cmd == '375': continue

  47.             elif cmd == '372': continue

  48.             elif cmd == '376': continue

  49.             elif cmd == '353': continue

  50.             elif cmd == '366': continue

  51.             else:

  52.                 print('Unhandled irc message:', irc_message)

  53.  

  54.         if not self.login_ok:

  55.             # We are still waiting for the initial login message. If we've waited longer than we should, try to reconnect.

  56.             if time.time() - self.login_timestamp > MAX_TIME_TO_WAIT_FOR_LOGIN:

  57.                 print('No response from Twitch. Reconnecting...')

  58.                 self.reconnect(0)

  59.                 return []

  60.  

  61.         return privmsgs

End of Document