def part(self, client, channelname): try: # remove from the channel del client.channels[channelname] client.request.sendall( (":{nick}!{nick}@{nick}.tmi.twitch.tv PART {chan}\n".format( nick=self.nick, chan=channelname)).encode("utf-8")) except KeyError: self.logger.warning( eventmessage( "user", "Client %s/%s tried to leave channel %s, but it wasnt joined." % (client.nick, client.oauth, channelname))) # if there are no clients for this channel left, we leave the channel for otherclient in self.clients: if (channelname in otherclient.channels): return if channelname in self.channels: self.channels[channelname].part() # remove from channels del self.channels[channelname] else: self.logger.warning( eventmessage( "user", "Tried to leave channel %s, but it wasnt joined." % (channelname, )))
def main(argv): parser = argparse.ArgumentParser(description="TMoohI Server") parser.add_argument("--config", default="", help="Config file in YAML format") args, extraargs = parser.parse_known_args() srv = TMoohIServer(args, parse_unknown_args(extraargs)) uninterrupted = True crashtime = 1 lastcrash = 0 while uninterrupted: try: srv.run() #while True: # time.sleep(1000) except (KeyboardInterrupt, SystemExit): uninterrupted = False srv.logger.info(eventmessage("general", "Stopping TMoohI server.")) # create a quitter thread srv.quit() break except Exception: srv.logger.exception() if time.time() - lastcrash < 60 * 10: crashtime *= 8 else: crashtime = 1 srv.logger.info( eventmessage( "general", "Restarting TMoohI server in %d seconds." % (crashtime, ))) time.sleep(crashtime) lastcrash = time.time()
def handleMessageQueue(self): while self.messagequeue: # dequeue messages and handle them until we meet one that we cannot handle yet message = self.messagequeue.pop(0) user = message["user"] try: client = message["client"] except KeyError: client = None data = message["message"] self.logger.debug( eventmessage("user", "Dequeing message %s for %s" % (data, user.key))) successfulsend = self.handleClientMessage(client, data, False) self.logger.debug( eventmessage( "user", "handleClientMessage returned with value %s" % (successfulsend, ))) if successfulsend: self.logger.debug( eventmessage( "user", "handleClientMessage was successful! Queue length: %d" % (len(self.messagequeue), ))) else: self.logger.debug( eventmessage( "user", "handleClientMessage added a new item to the queue. Queue length: %d" % (len(self.messagequeue), ))) return False time.sleep(0.01) return True
def _update(self): now = time.time() dt = now - self.lastmessage if dt > 30: self.logger.error( eventmessage( "connection", "Bot %s got silently disconnected. Enabling dead mode." % (self.connid, ))) self.connected = False self.dead = True self.shutdown() elif dt > 10: if dt > 20: self.logger.info( eventmessage( "connection", "Bot %s has not received messages in %d seconds. Pinging TMI server." % (self.connid, int(dt)))) else: self.logger.debug( eventmessage( "connection", "Bot %s has not received messages in %d seconds. Pinging TMI server." % (self.connid, int(dt)))) self.sendraw("PING")
def onClose(self, wasClean, code, reason): try: self.factory.logger.writers.remove(self) except ValueError: pass if wasClean: self.factory.logger.debug(eventmessage("websocket","WebSocket connection closed: {}".format(reason))) else: self.factory.logger.debug(eventmessage("websocket","WebSocket connection closed unexpectedly: {}".format(reason)))
def _update(self): now = time.time() dt = now-self.lastmessage if dt > 30: self.logger.error(eventmessage("connect","Bot %s got silently disconnected. Enabling dead mode."%(self.connid,))) self.connected = False self.dead = True self._socket.close() elif dt > 15: self.logger.debug(eventmessage("connect","Bot %s has not received messages in %d seconds. Pinging TMI server."%(self.connid,int(dt)))) self.sendraw("PING")
def handleJoinQueue(self): while not self.quitting: try: # in each iteration, handle the joinQueue now = time.time() self._conn_join_times = [i for i in self._conn_join_times if i>now-10] # check all users on connection deficit try: for userkey,user in self.users.items(): if self.quitting: return if len(self._conn_join_times) < self.parent.config["connections-per-10"]: if user.getTotalChannels() >= self.parent.config["capacity-target"] * user.getCapacity(): self.logger.debug(eventmessage("manager","Requesting new connection for %s because of exceeded capacity"%(user.key,))) # request new connection (in a non-GIL interpreter, wrap this in try-except) user.connections.append(self.TMIConnectionFactory(user)) # handle message queues user.handleMessageQueue() except RuntimeError: # Dict changed size during iteration. Nbd, well check again in .1 secs anyways. pass # check join queue iterator = 0 while iterator < len(self.joinqueue): if self.quitting: return if len(self._conn_join_times) >= self.parent.config["connections-per-10"]: break # dequeue a channel and try to join it channeljoininfo = self.joinqueue.pop() user = channeljoininfo["user"] channelinfo = channeljoininfo["channelinfo"] self.logger.debug(eventmessage("manager","Dequeing channel %s for %s from join queue"%(channelinfo.name,user.key))) # try joining this channel seed = random.randint(0,len(user.connections)) for index in range(len(user.connections)): try: conn = user.connections[(index+seed)%len(user.connections)] conn.join(channelinfo) self._conn_join_times.append(time.time()) self.logger.debug(eventmessage("manager","Channel %s joined on connection %s"%(channelinfo.name,conn.connid))) break except (TooManyChannelsError, NotConnectedError): pass else: # put it back into the deque self.joinqueue.append(channeljoininfo) iterator += 1 self.logger.debug(eventmessage("manager","Channel %s could not be joined, requeueing"%(channelinfo.name,))) time.sleep(0.1) except Exception: self.logger.exception()
def getJSON(self,url,cooldown=3600): try: if time.time() < self.cachedJSONresponses[url][0]+cooldown: self.logger.debug(eventmessage("manager","JSON response from cache: %s"%(url,))) return self.cachedJSONresponses[url][1] except KeyError: pass self.logger.debug(eventmessage("manager","Downloading JSON from %s"%(url,))) res = urllib2.urlopen(url) jsdata = res.read().decode("utf-8") data = json.loads(jsdata) self.cachedJSONresponses[url] = (time.time(), data) return data
def join(self, user, channelinfo): # try joining this channel for conn in user.connections: try: if len(self._conn_join_times) < self.parent.config["connections-per-10"]: conn.join(channelinfo) self._conn_join_times.append(time.time()) self.logger.debug(eventmessage("manager","Channel %s joined on connection %s"%(channelinfo.name,conn.connid))) break except (TooManyChannelsError, NotConnectedError): pass else: self.logger.debug(eventmessage("manager","Channel %s could not be joined, enqueueing"%(channelinfo.name,))) self.joinqueue.append({"user":user,"channelinfo":channelinfo})
def broadcast(self,message): try: self.logger.debug(eventmessage("raw","Broadcasting message %s"%(message,))) for client in self.clients: client.request.sendall((message+"\r\n").encode("utf-8")) except Exception: self.logger.exception()
def privmsg(self,message, appendtoqueue): if not message[STATE_TRAILING]: raise TypeError("PRIVMSG: Trailing data expected") channels = [y for b in message[STATE_PARAM] for y in b.split(",") if y] allok = True for channel in channels: if channel[0] != "#": raise InvalidChannelError("PRIVMSG: Invalid channel %s."%(channel,)) channelname = channel.split(self.parent.parent.config["cluster-seperator"],1)[0] clusterinfo = self.parent.getClusterInfo(channel,self.oauth) for conn in self.connections[clusterinfo[0]]: try: conn.privmsg(channelname,message[STATE_TRAILING]) break except (RateLimitError, NotConnectedError): pass else: # If we reach this, all available connections (if any) were unable to send the message. # We create a new one (cooldown: 3 seconds) and send the message to the messagequeue. self.logger.debug(eventmessage("connection","Requesting new connection to %s because of %s"%(clusterinfo[0],message[0]))) now = time.time() if now-self._lastNewConnectionRequest[clusterinfo[0]]>3: self.connections[clusterinfo[0]].append(self.parent.TMIConnectionFactory(self,clusterinfo)) self._lastNewConnectionRequest[clusterinfo[0]] = now # (re)add to messagequeue. message[0] is the original message if appendtoqueue: self.messagequeue.append({"user":self,"message":message[0]}) else: self.messagequeue.insert(0,{"user":self,"message":message[0]}) allok = False return allok
def die(self): """ Simulates the server silently disconnecting us """ self.logger.info( eventmessage("connection", "Dieing bot %s" % (self.connid, ))) self.ignoring = True
def handleClose(self): try: websocketServer.logger.writers.remove(self) websocketServer.clients.remove(self) except ValueError: pass websocketServer.logger.debug( eventmessage("websocket", "WebSocket connection closed"))
def kill(self): """ Simulates the socket getting killed """ self.logger.info(eventmessage("kill","Killing bot %s"%(self.connid,))) self.killing = True self.connected = False self._socket.close()
def disc(self): """ Simulates the connection being closed """ self.logger.info( eventmessage("connection", "Disconnecting bot %s" % (self.connid, ))) self.sendraw("PRIVMSG #jtv :/DISCONNECT")
def handleConnected(self): self.filters = copy.deepcopy(websocketServer.defaultfilter) websocketServer.clients.append(self) websocketServer.logger.writers.append(self) websocketServer.logger.debug( eventmessage("websocket", "Websocket connected: {}".format(self.address[0]))) # when opening a connection, send the current state self.inner_write(statusmessage(websocketServer.neweststatus, "status"))
def kill(self): """ Simulates the socket getting killed """ self.logger.info( eventmessage("connection", "Killing bot %s" % (self.connid, ))) self.killing = True self.connected = False self._socket.close()
def connect(self): self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self._socket.connect((self.ip, self.port)) self._recvthread = threading.Thread(target=self.listen) self._recvthread.start() self.logger.info(eventmessage("connect","Connecting to %s/%s for %s"%(self.ip, self.port, self.connid))) self.sendraw("CAP REQ :twitch.tv/tags\r\nCAP REQ :twitch.tv/commands") if self.parent.oauth: self.sendraw("PASS %s"%(self.parent.oauth,)) self.sendraw("USER %s %s %s :%s"%(self.parent.nick,self.parent.nick,self.parent.nick,self.parent.nick,)) self.sendraw("NICK %s"%(self.parent.nick,))
def handleMessageQueue(self): while self.messagequeue: # dequeue messages and handle them until we meet one that we cannot handle yet message = self.messagequeue.pop(0) user = message["user"] try: client = message["client"] except KeyError: client = None data = message["message"] self.logger.debug(eventmessage("queue","Dequeing message %s for %s"%(data,user.key))) successfulsend = self.handleClientMessage(client,data, False) self.logger.debug(eventmessage("queue","handleClientMessage returned with value %s"%(successfulsend,))) if successfulsend: self.logger.debug(eventmessage("queue","handleClientMessage was successful! Queue length: %d"%(len(self.messagequeue),))) else: self.logger.debug(eventmessage("queue","handleClientMessage added a new item to the queue. Queue length: %d"%(len(self.messagequeue),))) return False time.sleep(0.01) return True
def shutdown(self): if self.isshutdown: self.logger.warning( eventmessage("connection", "Tried to shutdown a non-connected socket.")) else: self.isshutdown = True self.connected = False try: self.parent.connections.remove(self) except KeyError: # we have already warned about this. pass if self.killing: self.logger.info( eventmessage("connection", "Connection ID %s killed!" % (self.connid, ))) else: self.logger.error( eventmessage( "connection", "Connection ID %s disconnected!" % (self.connid, ))) # when the connection dies, rejoin the channels on different (or new) connections for channel in self.channels: self.logger.warning( eventmessage( "connection", "Readding channel %s to the joinqueue!" % (channel.name, ))) channel.conn = None self.manager.joinqueue.append({ "user": self.parent, "channelinfo": channel }) self.channels = [] try: self._socket.shutdown(socket.SHUT_RDWR) self._socket.close() except OSError: # this will usually be thrown if the process is murdered or something, aka the connection was already cut, no need to shut down in that case. pass
def join(self, client, channelname, appendtoqueue): channelinfo = None if channelname[0] != "#": raise TypeError("PRIVMSG: Invalid channel %s." % (channelname, )) self.logger.debug( eventmessage( "user", "Trying to join channel %s for client %s/%s" % (channelname, client.nick, client.oauth))) if channelname in self.channels: self.logger.debug( eventmessage( "user", "Channel %s already joined. Welcoming client %s/%s" % (channelname, client.nick, client.oauth))) channelinfo = self.channels[channelname] channelinfo.welcome(client) else: channelinfo = TMoohIChannel.TMoohIChannel(self, channelname) self.channels[channelname] = channelinfo # try to join the channel, if we are ratelimited, add to joinqueue self.parent.join(self, channelinfo) client.channels[channelname] = channelinfo
def broadcast(self, channel, message): try: for client in self.clients: if channel == None or channel.name in client.channels: try: client.request.sendall( (message + "\r\n").encode("utf-8")) except BrokenPipeError: self.logger.error( eventmessage( "user", "Client %s/%s disconnected during broadcast" % (client.nick, client.oauth))) except Exception: self.logger.exception()
def __init__(self,parent,cluster,server,connid): self.connected = False self.killing = False self.dead = False self.ignoring = False self.connid = connid self.lastmessage = time.time() self.parent = parent self.manager = parent.parent self.logger = self.manager.parent.logger self.logger.info(eventmessage("connect","Connection ID %s created!"%(self.connid,))) # list of TMoohIChannels that are supposed to be joined by this connection. self.channels = [] # list of TMoohIChannels that are actually joined by this connection. self.joinedchannels = [] # list of unique channelnames that are joined by this connection. self.channelnames = [] self.clustername = cluster srvinfo = re.split("[^\d\w\.]",server) self.port = 443 self.server = server self.ip = self.server if len(srvinfo) == 2: self.port = int(srvinfo[1]) self.ip = srvinfo[0] self.stats = { "server": "%s:%s"%(self.ip, self.port), "id": self.connid, "connected": self.getConnected, "channels": self.channels, "joinedchannels": self.joinedchannels } # internals: self._socket = None self._recvthread = None self._recvthreadid = 0 self._sentmessages = [] self._messagebuffer = "" self._authed = False # we automatically connect to said server. self.connect()
def handleMessage(self): if self.opcode != TEXT: websocketServer.logger.debug( eventmessage( "websocket", "Websocket message received: {} bytes".format( len(self.data)))) else: websocketServer.logger.debug( eventmessage( "websocket", "Websocket text message received: {}".format(self.data))) try: res = self.data.split(" ", 1) command = res[0] data = "" if len(res) == 2: data = res[1] jsondecoded = json.loads(data) if command == "SETFILTER": if data: ok = True if type(jsondecoded) == list: for x in jsondecoded: if type(x) != dict: ok = False if ok: self.filters = jsondecoded response = eventmessage( "websocket", "Filter updated to %s" % (self.filters, )) response.level = MoohLogger.DEBUG self.inner_write(response) else: ok = False if not ok: response = eventmessage( "websocket", "Could not process filter %s" % (data, )) response.level = MoohLogger.ERROR self.inner_write(response) else: response = eventmessage( "websocket", "Could not process empty filter") response.level = MoohLogger.ERROR self.inner_write(response) else: response = eventmessage("websocket", "Unknown command %s" % (command, )) response.level = MoohLogger.ERROR self.inner_write(response) except Exception: websocketServer.logger.exception()
def privmsg(self, message, appendtoqueue): if not message[STATE_TRAILING]: raise TypeError("PRIVMSG: Trailing data expected") channels = [y for b in message[STATE_PARAM] for y in b.split(",") if y] allok = True for channel in channels: if channel[0] != "#": raise InvalidChannelError("PRIVMSG: Invalid channel %s." % (channel, )) for conn in self.connections: try: conn.privmsg(channel, message[STATE_TRAILING]) break except (RateLimitError, NotConnectedError): pass else: # If we reach this, all available connections (if any) were unable to send the message. # We create a new one (cooldown: 3 seconds) and send the message to the messagequeue. self.logger.debug( eventmessage( "user", "Requesting new connection because of %s" % (message[0], ))) now = time.time() if now - self._lastNewConnectionRequest > 3: try: self._lastNewConnectionRequest = now self.connections.append( self.parent.TMIConnectionFactory(self)) except RateLimitError: pass # (re)add to messagequeue. message[0] is the original message if appendtoqueue: self.messagequeue.append({ "user": self, "message": message[0] }) else: self.messagequeue.insert(0, { "user": self, "message": message[0] }) allok = False return allok
def listen(self): try: while True: buf = self._socket.recv(2048).decode("utf-8") if not buf: break if self.killing: break if self.dead: break if self.ignoring: continue self.lastmessage = time.time() self._messagebuffer += buf s = self._messagebuffer.split("\r\n") self._messagebuffer = s[-1] for line in s[:-1]: self.logger.debug(eventmessage("raw","Got raw TMI message in connection %s: %s"%(self.connid,line))) try: ex = parseIRCMessage(line) except Exception: self.logger.exception() if(ex[STATE_COMMAND]=="PING"): self.sendraw("PONG") elif ex[STATE_COMMAND]=="376": self.connected = True self.logger.info(eventmessage("connect","Connection ID %s connected!"%(self.connid,))) elif ex[STATE_COMMAND]=="JOIN": try: self.joinedchannels.append(ex[STATE_PARAM][0]) self.parent.handleTMIMessage(self, ex) self.logger.info(eventmessage("channel","Joined channel "+ex[STATE_PARAM][0])) except Exception: self.logger.exception() else: self.parent.handleTMIMessage(self, ex) except ConnectionAbortedError: pass except Exception: self.logger.exception() self.connected = False self.parent.connections[self.clustername].remove(self) if self.killing: self.logger.info(eventmessage("connect","Connection ID %s killed!"%(self.connid,))) else: self.logger.error(eventmessage("connect","Connection ID %s disconnected!"%(self.connid,))) # when the connection dies, rejoin the channels on different (or new) connections for channel in self.channels: self.parent.part(channel.channelkey,announce = True) self.logger.error(eventmessage("queue","Readding channel %s to the joinqueue!"%(channel.channelkey,))) self.manager.joinqueue.append({"user":self.parent,"message":"JOIN %s"%(channel.channelkey,)})
def handleClientMessage(self,client,data, appendtoqueue): self.logger.debug(eventmessage("message","Handling message %s for %s"%(data,self.key))) # parse the message message = parseIRCMessage(data) cmd = message[STATE_COMMAND].lower() handler = None try: handler = getattr(self,"handle_client_%s"%(cmd,)) except AttributeError: pass else: # they return True if the message could be handled. try: return handler(client,message, appendtoqueue) except Exception: self.logger.exception() return True
def handleClientMessage(self, client, data, appendtoqueue): self.logger.debug( eventmessage("user", "Handling message %s for %s" % (data, self.key))) # parse the message message = parseIRCMessage(data) cmd = message[STATE_COMMAND].lower() handler = None try: handler = getattr(self, "handle_client_%s" % (cmd, )) except AttributeError: pass else: # they return True if the message could be handled. try: return handler(client, message, appendtoqueue) except Exception: self.logger.exception() return True
def connect(self): self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) if self.serverObj["secure"]: self._socket = ssl.wrap_socket(self._socket) self._socket.connect((self.serverObj["host"], self.serverObj["port"])) self._recvthread = threading.Thread(target=self.listen) self._recvthread.start() self.logger.info( eventmessage( "connection", "Connecting to %s/%s for %s" % (self.serverObj["host"], self.serverObj["port"], self.connid))) self.sendraw("CAP REQ :twitch.tv/tags\r\nCAP REQ :twitch.tv/commands") if self.parent.oauth: self.sendraw("PASS %s" % (self.parent.oauth, )) self.sendraw("USER %s %s %s :%s" % ( self.parent.nick, self.parent.nick, self.parent.nick, self.parent.nick, )) self.sendraw("NICK %s" % (self.parent.nick, ))
def onMessage(self, payload, isBinary): if isBinary: self.factory.logger.debug(eventmessage("websocket","Binary websocket message received: {} bytes".format(len(payload)))) else: self.factory.logger.debug(eventmessage("websocket","Websocket text message received: {}".format(payload.decode('utf8')))) try: res = payload.decode('utf8').split(" ",1) command = res[0] data = "" if len(res) == 2: data = res[1] jsondecoded = json.loads(data) if command == "SETFILTER": if data: ok = True if type(jsondecoded) == list: for x in jsondecoded: if type(x) != dict: ok = False if ok: self.filters = jsondecoded response = eventmessage("websocket","Filter updated to %s"%(self.filters,)) response.level = MoohLogger.DEBUG self.inner_write(response) else: ok = False if not ok: response = eventmessage("websocket","Could not process filter %s"%(data,)) response.level = MoohLogger.ERROR self.inner_write(response) else: response = eventmessage("websocket","Could not process empty filter") response.level = MoohLogger.ERROR self.inner_write(response) else: response = eventmessage("websocket","Unknown command %s"%(command,)) response.level = MoohLogger.ERROR self.inner_write(response) except Exception: self.factory.logger.exception()
def sendraw(self, x): self.logger.debug( eventmessage( "connection", "Sending a RAW TMI message on bot %s: %s" % (self.connid, x))) self._socket.send((x + "\r\n").encode("utf-8"))
def join(self,channel, appendtoqueue): self.logger.debug(eventmessage("channel","Trying to join channel %s"%(channel,))) if channel in self.channels: self.logger.debug(eventmessage("channel","Couldn't join channel %s: already joined."%(channel,))) return True if channel[0] != "#": raise TypeError("PRIVMSG: Invalid channel %s."%(channel,)) clusterinfo = self.parent.getClusterInfo(channel) # check the ratelimit now = time.time() self.parent._joinedchannels = [i for i in self.parent._joinedchannels if i>now-10] if len(self.parent._joinedchannels)<40: channelname = channel.split(self.parent.parent.config["cluster-seperator"])[0] if channelname in self.channelsByName[clusterinfo[0]] and len(self.channelsByName[clusterinfo[0]][channelname])>0: # if the channelname is already joined, we use its connection, no need to ratelimit: refchannel = self.channelsByName[clusterinfo[0]][channelname][0] print("Channel "+channelname+ " already joined. Adding and welcoming. Data: %s"%(refchannel.data,)) channelinfo = TMoohIChannel.TMoohIChannel(self,channel,clusterinfo[0],refchannel.conn) # add the channelinfo to channels self.channels[channel] = channelinfo # add the channelinfo to channelsByName self.channelsByName[clusterinfo[0]][channelname].append(channelinfo) # now we need to welcome the channel. for key,value in refchannel.data.items(): if value: channelinfo.data[key] = replaceChannel(value,refchannel.channelkey,channelinfo.channelkey) for client in self.clients: channelinfo.welcome(client) print("Channel "+channelname+ " already joined. Added and welcomed. Data: %s"%(channelinfo.data,)) return True else: # find a connection to use for conn in self.connections[clusterinfo[0]]: try: # create channel object - also joins the channel channelinfo = TMoohIChannel.TMoohIChannel(self,channel,clusterinfo[0],conn) # add to global ratelimiter self.parent._joinedchannels.append(now) # add the channelinfo to channels self.channels[channel] = channelinfo # add the channelinfo to channelsByName self.channelsByName[channelinfo.cluster].setdefault(channelinfo.channelname,[]).append(channelinfo) return True except (NotConnectedError, TooManyChannelsError) as e: self.logger.debug(eventmessage("channel","Couldn't join channel %s: %s."%(channel,e))) pass else: self.logger.debug(eventmessage("channel","Couldn't join channel %s: ratelimit exceeded."%(channel,))) # If we reach this, all available connections (if any) were unable to send the join or the request was ratelimited. # We create a new one and send the join to the joinqueue. This is ratelimited with 1 connection request per second. now = time.time() if now-self._lastNewConnectionRequest[clusterinfo[0]]>10: self.logger.debug(eventmessage("connection","Requesting new connection")) self.connections[clusterinfo[0]].append(self.parent.TMIConnectionFactory(self,clusterinfo)) self._lastNewConnectionRequest[clusterinfo[0]] = now self.logger.debug(eventmessage("channel","Adding JOIN %s to the resend queue. Queue length: %d"%(channel,len(self.parent.joinqueue)))) if appendtoqueue: self.parent.joinqueue.append({"user":self,"message":"JOIN %s"%(channel,)}) else: self.parent.joinqueue.insert(0,{"user":self,"message":"JOIN %s"%(channel,)}) return False
def onOpen(self): self.factory.logger.debug(eventmessage("websocket","WebSocket connection open.")) # when opening a connection, send the current state self.inner_write(statusmessage(self.factory.neweststatus))
def sendraw(self,x): self.logger.debug(eventmessage("raw","Sending a RAW TMI message on bot %s: %s"%(self.connid,x))) self._socket.send((x+"\r\n").encode("utf-8"))
def __del__(self): self.logger.info(eventmessage("general", "Stopped TMoohI server."))
def die(self): """ Simulates the server silently disconnecting us """ self.logger.info(eventmessage("kill","Dieing bot %s"%(self.connid,))) self.ignoring = True
def disc(self): """ Simulates the connection being closed """ self.logger.info(eventmessage("kill","Disconnecting bot %s"%(self.connid,))) self.sendraw("PRIVMSG #jtv :/DISCONNECT")
def listen(self): try: while True: buf = "" try: buf = self._socket.recv(2048).decode("utf-8", errors='ignore') except UnicodeDecodeError: self.logger.exception() continue if not buf: break if self.killing: break if self.dead: break if self.ignoring: continue self.lastmessage = time.time() self._messagebuffer += buf s = self._messagebuffer.split("\r\n") self._messagebuffer = s[-1] for line in s[:-1]: self.logger.debug( eventmessage( "connection", "Got raw TMI message in connection %s: %s" % (self.connid, line))) try: ex = parseIRCMessage(line) except Exception: self.logger.exception() if (ex[STATE_COMMAND] == "PING"): self.sendraw("PONG") elif ex[STATE_COMMAND] == "376": self.connected = True self.logger.info( eventmessage( "connection", "Connection ID %s connected!" % (self.connid, ))) elif ex[STATE_COMMAND] == "JOIN": try: self.parent.handleTMIMessage(self, ex) self.logger.info( eventmessage( "connection", "Joined channel " + ex[STATE_PARAM][0])) except Exception: self.logger.exception() elif ex[STATE_COMMAND] == "PART": try: self.parent.handleTMIMessage(self, ex) self.logger.info( eventmessage( "connection", "Left channel " + ex[STATE_PARAM][0])) except Exception: self.logger.exception() else: self.parent.handleTMIMessage(self, ex) except ConnectionAbortedError: pass except Exception: self.logger.exception() self.shutdown()
def __init__(self, parent, server, connid): self.connected = False self.killing = False self.dead = False self.ignoring = False self.isshutdown = False self.connid = connid self.lastmessage = time.time() self.parent = parent self.manager = parent.parent self.logger = self.manager.parent.logger self.logger.info( eventmessage("connection", "Connection ID %s created!" % (self.connid, ))) # list of TMoohIChannels that are supposed to be joined by this connection. self.channels = [] self.serverObj = None if type(server) == str: srvinfo = re.split("[^\d\w\.]", server) if len(srvinfo) == 2: port = int(srvinfo[1]) self.serverObj = { "host": srvinfo[0], "port": port, "secure": port == 443 } elif len(srvinfo) == 1: self.serverObj = { "host": srvinfo[0], "port": 6667, "secure": False } elif type(server) == dict: port = int(server.get("port", 6667)) self.serverObj = { "host": server.get("host", "irc.chat.twitch.tv"), "port": port, "secure": server.get("secure", port == 443) } if not self.serverObj: raise ArgumentException("Invalid server settings") self.stats = { "server": "%s:%s" % (self.serverObj["host"], self.serverObj["port"]), "id": self.connid, "connected": self.getConnected, "channels": self.getChannels, "secure": self.serverObj["secure"] } # internals: self._socket = None self._recvthread = None self._recvthreadid = 0 self._sentmessages = [] self._messagebuffer = "" self._authed = False # we automatically connect to said server. self.connect()
def onConnect(self, request): self.factory.logger.writers.append(self) self.level = 0 self.filters = copy.deepcopy(self.factory.defaultfilter) self.factory.logger.debug(eventmessage("websocket","Websocket connecting: {}".format(request.peer)))
def handle(self): self.buffer = "" self.nick = "" self.oauth = "" self.welcomed = False self.user = None self.starttime = time.time() self.id = "%s/%s" % (self.client_address[0], self.client_address[1]) self.commandsent = 0 self.channels = {} linesep = None self.stats = { "since": time.time(), "sent": self.getCommandsSent, "channels": self.channelList, "id": self.id } self.data = None while not self.server.TMoohIParent.quitting: try: self.data = self.request.recv(1024) if not self.data: # client disconnected break if self.server.TMoohIParent.quitting: break self.server.TMoohIParent.logger.debug( eventmessage( "client", "Got raw client message from %s (sock ID %d): %s" % (self.client_address[0], self.client_address[1], self.data))) self.buffer += self.data.decode("utf-8") if not linesep: if "\r\n" in self.buffer: linesep = "\r\n" elif "\r" in self.buffer: linesep = "\r" elif "\n" in self.buffer: linesep = "\n" else: continue lines = self.buffer.split(linesep) self.buffer = lines[-1] for line in lines[:-1]: ex = line.split(" ") if len(ex) > 1: if ex[0].upper() == "QUIT": break if self.user: if ex[0].upper() == "PRIVMSG": self.commandsent += 1 self.user.handleClientMessage(self, line, True) self.commandsent += 1 else: if ex[0] == "NICK": m = re.match("^NICK (\w+)$", line) if m: self.nick = m.group(1) self.server.TMoohIParent.logger.debug( eventmessage( "client", "NICK command %s" % (m.group(1), ))) self.user = self.server.TMoohIParent.manager.connect( self) else: self.request.sendall( b"Invalid NICK command!\r\n") elif ex[0] == "PASS": m = re.match("^PASS (oauth:[a-z0-9]+)$", line) if m: self.oauth = m.group(1) self.server.TMoohIParent.logger.debug( eventmessage( "client", "PASS command %s" % (self.oauth, ))) if self.nick: self.user = self.server.TMoohIParent.manager.connect( self) else: self.request.sendall( b"Invalid PASS command!\r\n") continue else: self.request.sendall(b"No user connected to!\r\n") except ConnectionResetError: break except Exception: self.server.TMoohIParent.logger.exception() try: self.server.TMoohIParent.manager.disconnect(self) except Exception: self.server.TMoohIParent.logger.exception() if self.data: self.server.TMoohIParent.logger.debug( eventmessage( "client", "Client %s disconnected (sock ID %d): %s" % (self.client_address[0], self.client_address[1], self.data))) else: self.server.TMoohIParent.logger.debug( eventmessage( "client", "Client %s disconnected (sock ID %d)" % (self.client_address[0], self.client_address[1])))
def __init__(self, config, extraargs): self.BuildInfo = BuildCounter.getVersionInfo( "TMoohI", ["py", "html", "css", "js"]) self.quitting = False #config options # host+port: where TMoohI listens for client connections on # reference channel: the channel to gather chat_properties from # logfile: file to log status messages to # status-: status files in the formats specified # ref-channel-: channel to check chat_properties for self.config = { "port": 6667, "host": "localhost", "websockethost": "127.0.0.1", "websocketport": 3141, "logfile": "tmoohi_%Y_%m_%d.log", "logfile-logfilter": [{ 'level__ge': 20, 'type': 'event' }], "console-logfilter": [{ 'level__ge': 0, 'type': 'event' }], "channels-per-connection": 10, "messages-per-30": 15, "connections-per-10": 45, "capacity-target": 0.75, "ratelimit-commands": True, "server": "irc.chat.twitch.tv:443" } if config.config: with open(config.config) as f: data = yaml.load(f) for k in data: self.config[k] = data[k] configerrors = [] for k in extraargs: try: value = extraargs[k] self.config[k] = type(self.config[k])(value) except KeyError: configerrors.append("Key %s not present in config" % (k, )) except Exception as e: configerrors.append("%s for configuration option --%s %s" % (e, k, value)) self.logger = MoohLogger() self.filelogger = filewriter(self.config["logfile"]) self.filelogger.filters = self.config["logfile-logfilter"] self.consolelogger = consolewriter() self.consolelogger.filters = self.config["console-logfilter"] self.logger.writers.append(self.filelogger) self.logger.writers.append(self.consolelogger) for e in configerrors: self.logger.error(eventmessage("configuration", e)) self.logger.info( eventmessage("general", "%s loaded" % (self.BuildInfo, ))) self.logger.info( eventmessage( "general", "Starting TMoohI server on port %d - CTRL+C to stop" % (self.config["port"], ))) self.websocketserver = TMoohIWebSocketLogger.TMoohIWebsocketServer( self.logger, self.config["websockethost"], self.config["websocketport"]) self.manager = TMoohIManager.TMoohIManager(self)