def handle_events(self, msg): if msg["command"] != "PRIVMSG" or not msg["origin"]: return sender_nick = msg["origin"].split('@')[0].split('!')[0] recipient = msg["args"][0] text = msg["args"][1] if recipient != self.config.get("nick"): return #: HANDLE CTCP ANTI FLOOD/BOT PROTECTION if text[0] == '\x01' and text[-1] == '\x01': #: CTCP ctcp_data = text[1:-1].split(' ', 1) ctcp_command = ctcp_data[0] ctcp_args = ctcp_data[1] if len(ctcp_data) > 1 else "" if ctcp_command == "VERSION": self.log_debug("Sending CTCP VERSION") self.sock.send( to_bytes("NOTICE {} :{}\r\n".format( msg["origin"], "pyLoad! IRC Interface"))) return elif ctcp_command == "TIME": self.log_debug("Sending CTCP TIME") self.sock.send( to_bytes("NOTICE {} :{}\r\n".format( msg["origin"], time.time()))) return elif ctcp_command == "PING": self.log_debug("[{}] Ping? Pong!".format(sender_nick)) self.sock.send( to_bytes("NOTICE {} :\x01PING {}\x01\r\n".format( sender_nick, ctcp_args))) #@NOTE: PING is not a typo elif ctcp_command == "LAG": self.log_debug( "Received CTCP LAG") #: don't know how to answer return if sender_nick not in self.config.get("owner").split(): return temp = text.split() try: command = temp[0] args = text.split()[1:] except IndexError: command = "error" args = [] try: res = self.do_bot_command(command, args) for line in res: self.response(line, msg["origin"]) time.sleep(1) #: avoid Excess Flood except Exception as exc: self.log_error(exc)
def run(self): #: Connect to IRC etc. self.sock = socket.socket() host = self.config.get("host") self.sock.connect((host, self.config.get("port"))) if self.config.get("ssl"): self.sock = ssl.wrap_socket( self.sock, cert_reqs=ssl.CERT_NONE) # TODO: support certificate nick = self.config.get("nick") self.log_info(self._("Connecting as"), nick) self.sock.send(to_bytes("NICK {}\r\n".format(nick))) self.sock.send( to_bytes("USER {} {} bla :{}\r\n".format(nick, host, nick))) self.log_info(self._("Connected to"), host) for t in self.config.get("owner").split(): if t.startswith("#"): self.sock.send(to_bytes("JOIN {}\r\n".format(t))) self.log_info(self._("Joined channel {}").format(to_str(t))) self.log_info(self._("Switching to listening mode!")) try: self.main_loop() except IRCError: self.sock.send(b"QUIT :byebye\r\n") self.sock.close()
def response(self, msg, origin=""): if origin == "": for t in self.config.get("owner").split(): self.sock.send( to_bytes("PRIVMSG {} :{}\r\n".format(t.strip(), msg))) else: self.sock.send( to_bytes("PRIVMSG {} :{}\r\n".format( origin.split("!", 1)[0], msg)))
def get_irc_command(self): origin, command, args = None, None, None while True: line = self._get_response_line() origin, command, args = self._parse_irc_msg(line) if command == "PING": self.plugin.log_debug(f"[{args[0]}] Ping? Pong!") self.irc_sock.send(to_bytes("PONG :{}\r\n".format(args[0]))) elif origin and command == "PRIVMSG": sender_nick = origin.split("@")[0].split("!")[0] recipient = args[0] text = args[1] if text[0] == "\x01" and text[-1] == "\x01": #: CTCP ctcp_data = text[1:-1].split(" ", 1) ctcp_command = ctcp_data[0] ctcp_args = ctcp_data[1] if len(ctcp_data) > 1 else "" if recipient[0:len(self.nick)] == self.nick: if ctcp_command == "VERSION": self.plugin.log_debug( self._("[{}] CTCP VERSION").format( sender_nick)) self.irc_sock.send( to_bytes( "NOTICE {} :\x01VERSION {}\x01\r\n".format( sender_nick, "pyLoad! IRC Interface"))) elif ctcp_command == "TIME": self.plugin.log_debug( self._("[{}] CTCP TIME").format(sender_nick)) self.irc_sock.send( to_bytes("NOTICE {} :\x01{}\x01\r\n".format( sender_nick, time.strftime("%a %b %d %H:%M:%S %Y")))) elif ctcp_command == "PING": self.plugin.log_debug( self._("[{}] Ping? Pong!").format(sender_nick)) self.irc_sock.send( to_bytes( "NOTICE {} :\x01PING {}\x01\r\n".format( sender_nick, ctcp_args)) ) # NOTE: PING is not a typo else: break else: break else: break return origin, command, args
def main_loop(self): readbuffer = b"" while True: time.sleep(1) fdset = select.select([self.sock], [], [], 0) if self.sock not in fdset[0]: continue if self.abort: raise IRCError("quit") readbuffer += self.sock.recv(1 << 10) temp = readbuffer.split(b"\n") readbuffer = temp.pop() for line in temp: line = line.rstrip() origin, command, args = parse_irc_msg(line) if command == "PING": self.log_debug("[{}] Ping? Pong!".format(args[0])) self.sock.send(to_bytes("PONG :{}\r\n".format(args[0]))) if command == "ERROR": raise IRCError(line) msg = { "origin": origin, "command": command, "args": args, } self.handle_events(msg)
def is_bot_online(self, bot): self.plugin.log_info(self._("Checking if bot '{}' is online").format(bot)) self.irc_sock.send(to_bytes("WHOIS {}\r\n".format(bot))) start_time = time.time() while time.time() - start_time < 30: origin, command, args = self.get_irc_command() if ( command == "401" and args[0] == self.nick and args[1].lower() == bot.lower() ): #: ERR_NOSUCHNICK self.plugin.log_debug(f"Bot '{bot}' is offline") return False #: RPL_WHOISUSER elif ( command == "311" and args[0] == self.nick and args[1].lower() == bot.lower() ): self.plugin.log_debug(f"Bot '{bot}' is online") self.bot_host[bot] = args[3] #: bot host return True else: time.sleep(0.1) else: self.plugin.log_error(self._("Server did not respond in a reasonable time")) return False
def join_channel(self, chan): chan = "#" + chan if chan[0] != "#" else chan self.plugin.log_info(self._("Joining channel {}").format(chan)) self.irc_sock.send(to_bytes("JOIN {}\r\n".format(chan))) start_time = time.time() while time.time() - start_time < 30: origin, command, args = self.get_irc_command() #: ERR_KEYSET, ERR_CHANNELISFULL, ERR_INVITEONLYCHAN, ERR_BANNEDFROMCHAN, ERR_BADCHANNELKEY if ( command in ("467", "471", "473", "474", "475") and args[1].lower() == chan.lower() ): self.plugin.log_error( self._("Cannot join channel {} (error {}: '{}')").format( chan, command, args[2] ) ) return False elif command == "353" and args[2].lower() == chan.lower(): #: RPL_NAMREPLY self.plugin.log_debug(f"Successfully joined channel {chan}") return True return False
def xdcc_get_pack_info(self, bot, pack): bot_host = self.get_bot_host(bot) self.plugin.log_info(self._("Requesting pack #{} info").format(pack)) self.irc_sock.send(to_bytes("PRIVMSG {} :xdcc info #{}\r\n".format(bot, pack))) info = {} start_time = time.time() while time.time() - start_time < 90: origin, command, args = self.get_irc_command() # Private message from bot to us? if ( origin and command and args and ( origin[0 : len(bot)] == bot or bot_host and origin.split("@")[1] == bot_host ) and args[0][0 : len(self.nick)] == self.nick and command in ("PRIVMSG", "NOTICE") ): try: text = str(args[1], "utf-8") except UnicodeDecodeError: text = str(args[1], "latin1", "replace") pack_info = text.split() if pack_info[0].lower() == "filename": self.plugin.log_debug(f"Filename: '{pack_info[1]}'") info.update({"status": "online", "name": pack_info[1]}) elif pack_info[0].lower() == "filesize": self.plugin.log_debug(f"Filesize: '{pack_info[1]}'") info.update({"status": "online", "size": pack_info[1]}) else: sender_nick = origin.split("@")[0].split("!")[0] self.plugin.log_debug( self._("PrivMsg: <{}> {}").format(sender_nick, text) ) else: if len(info) > 2: #: got both name and size break time.sleep(0.1) else: if len(info) == 0: self.plugin.log_error( self._("XDCC Bot '{}' did not answer").format(bot) ) return {"status": "offline", "msg": "XDCC Bot did not answer"} return info
def xdcc_request_resume(self, bot, dcc_port, file_name, resume_position): if self.xdcc_request_time: bot_host = self.get_bot_host(bot) self.plugin.log_info( self._("Requesting XDCC resume of '{}' at position {}").format( file_name, resume_position)) self.irc_sock.send( to_bytes( 'PRIVMSG {} :\x01DCC RESUME "{}" {} {}\x01\r\n'.format( bot, os.fsdecode(file_name), dcc_port, resume_position))) start_time = time.time() while time.time() - start_time < 30: origin, command, args = self.get_irc_command() # Private message from bot to us? if (origin and command and args and "@" in origin and (origin[0:len(bot)] == bot or bot_host and origin.split("@")[1] == bot_host) and args[0][0:len(self.nick)] == self.nick and command in ("PRIVMSG", "NOTICE")): try: text = str(args[1], "utf-8") except UnicodeDecodeError: text = str(args[1], "latin1", "replace") sender_nick = origin.split("@")[0].split("!")[0] self.plugin.log_debug( self._("PrivMsg: <{}> {}").format(sender_nick, text)) m = re.match( r"\x01DCC ACCEPT .*? {} (?P<RESUME_POS>\d+)\x01". format(dcc_port), text, ) if m: self.plugin.log_debug( self._( "Bot '{}' acknowledged resume at position {}"). format(sender_nick, m.group("RESUME_POS"))) return int(m.group("RESUME_POS")) else: time.sleep(0.1) self.plugin.log_warning( self. _("Timeout while waiting for resume acknowledge, not resuming" )) else: self.plugin.log_error( self._("No XDCC request pending, cannot resume")) return 0
def xdcc_cancel_pack(self, bot): if self.xdcc_request_time: self.plugin.log_info(self._("Requesting XDCC cancellation")) self.xdcc_request_time = None self.irc_sock.send(to_bytes("PRIVMSG {} :xdcc cancel\r\n".format(bot))) else: self.plugin.log_warning(self._("No XDCC request pending, cannot cancel"))
def connect_server(self, host, port): """ Connect to the IRC server and wait for RPL_WELCOME message. """ if self.connected: self.plugin.log_warning( self._("Already connected to server, not connecting") ) return False self.plugin.log_info(self._("Connecting to: {}:{}").format(host, port)) self.irc_sock.settimeout(30) self.irc_sock.connect((host, port)) self.irc_sock.settimeout(None) self.irc_sock.send(to_bytes("NICK {}\r\n".format(self.nick))) self.irc_sock.send( to_bytes("USER {} {} bla :{}\r\n".format(self.ident, host, self.realname)) ) start_time = time.time() while time.time() - start_time < 30: origin, command, args = self.get_irc_command() if command == "001": #: RPL_WELCOME self.connected = True self.host = host self.port = port start_time = time.time() while ( self._get_response_line() and time.time() - start_time < 30 ): #: Skip MOTD pass self.plugin.log_debug( self._("Successfully connected to {}:{}").format(host, port) ) return True self.plugin.log_error(self._("Connection to {}:{} failed").format(host, port)) return False
def check_download(self): check = self.scan_download( {"dl_limit": to_bytes(self.DL_LIMIT_PATTERN)}) if check == "dl_limit": self.log_warning(self._("Free download limit reached")) os.remove(self.last_download) self.retry(wait=10800, msg=self._("Free download limit reached")) return super().check_download()
def send_invite_request(self, bot, chan, password): bot_host = self.get_bot_host(bot) if bot_host: self.plugin.log_info( self._("Sending invite request for #{} to '{}'").format(chan, bot) ) else: self.plugin.log_warning(self._("Cannot send invite request")) return self.irc_sock.send( to_bytes("PRIVMSG {} :enter #{} {} {}\r\n".format(bot, chan, self.nick, password)) ) start_time = time.time() while time.time() - start_time < 30: origin, command, args = self.get_irc_command() if origin is None or command is None or args is None: return # Private message from bot to us? if ( "@" not in origin or (origin[0 : len(bot)] != bot and origin.split("@")[1] != bot_host) or args[0][0 : len(self.nick)] != self.nick or command not in ("PRIVMSG", "NOTICE", "INVITE") ): continue try: text = str(args[1], "utf-8") except UnicodeDecodeError: text = str(args[1], "latin1", "replace") sender_nick = origin.split("@")[0].split("!")[0] if command == "INVITE": self.plugin.log_info(self._("Got invite to #{}").format(chan)) else: self.plugin.log_info( self._("PrivMsg: <{}> {}").format(sender_nick, text) ) break else: self.plugin.log_warning( self._("'{}' did not respond to the request").format(bot) )
def nickserv_identify(self, password): self.plugin.log_info(self._("Authenticating nickname")) bot = "nickserv" bot_host = self.get_bot_host(bot) if not bot_host: self.plugin.log_warning( self._("Server does not seems to support nickserv commands") ) return self.irc_sock.send(to_bytes("PRIVMSG {} :identify {}\r\n".format(bot, password))) start_time = time.time() while time.time() - start_time < 30: origin, command, args = self.get_irc_command() if origin is None or command is None or args is None: return # Private message from bot to us? if ( "@" not in origin or (origin[0 : len(bot)] != bot and origin.split("@")[1] != bot_host) or args[0][0 : len(self.nick)] != self.nick or command not in ("PRIVMSG", "NOTICE") ): continue try: text = str(args[1], "utf-8") except UnicodeDecodeError: text = str(args[1], "latin1", "replace") sender_nick = origin.split("@")[0].split("!")[0] self.plugin.log_info(self._("PrivMsg: <{}> {}").format(sender_nick, text)) break else: self.plugin.log_warning( self._("'{}' did not respond to the request").format(bot) )
def xdcc_request_pack(self, bot, pack): self.plugin.log_info(self._("Requesting pack #{}").format(pack)) self.xdcc_request_time = time.time() self.irc_sock.send(to_bytes("PRIVMSG {} :xdcc send #{}\r\n".format(bot, pack)))