diff --git a/README.md b/README.md index c28c838..dc5b6b3 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ # Flea -Flea aims to be a modular Python IRC bot. This software was inspired from older +Flea aims to be a modular Python IRC bot. This software was inspired from older IRC bots I wrote. ## License -Flea is released under the -[GNU AGPLv3](https://www.gnu.org/licenses/why-affero-gpl.html), please **direct -your attention to the LICENSE file in the root of the repository** before +Flea is released under the +[GNU AGPLv3](https://www.gnu.org/licenses/why-affero-gpl.html), please **direct +your attention to the LICENSE file in the root of the repository** before continuing. ## Requirements @@ -14,7 +14,7 @@ Flea was written in [Python 2.7](http://www.python.org/download/releases/2.7/) ## Quick Start 1. Edit the *settings.conf* file to appropriate settings on the server you would like to use - -2. Run the terminal/console in the root of the project and type: + +2. Run the terminal/console in the root of the project and type: *python Flea.py* diff --git a/core/irclib.py b/core/irclib.py index fa6cda6..820e170 100644 --- a/core/irclib.py +++ b/core/irclib.py @@ -14,10 +14,14 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see +# Built-in to Python 2.7 import socket class irc: debug = False + + config = {} + pack = {} sock = socket.socket() # IRC Parser. Parses by line @@ -114,7 +118,7 @@ class irc: self.msg("NOTICE "+user+" :\001VERSION "+version+"\001") def Whois(self, query): - self.msg("WHOIS "+query+"\r\n") + self.msg("WHOIS "+query) def SetMode(self, channel, nick, mode): self.msg("MODE "+channel+" "+mode+" "+nick) diff --git a/core/main.py b/core/main.py index 71483a4..e333db2 100644 --- a/core/main.py +++ b/core/main.py @@ -23,6 +23,7 @@ import socket import ssl import sys import os +import re # Allows reimporting modules @@ -63,6 +64,9 @@ def PluginsImport(): # Path to /plugins/ under /Flea/ plugins = current+"/plugins/" + # List of plugins + plugin_list = [] + # If /plugins/ exists change directory to it if os.path.exists(plugins): os.chdir(plugins) @@ -73,33 +77,36 @@ def PluginsImport(): # Only import directory plugins (no single files) if os.path.isdir(plugins+item): print "[Plugins] Initializing "+item - __import__(item+".main") + plugin = __import__(item+".main") + plugin_list.append(plugin) else: return False os.chdir(current) - return True + return plugin_list + def main(): + # Create irclib irc object + irc = irclib.irc() + # Parse main settings.conf file - config = cfgParser("settings.conf") + irc.config = cfgParser("settings.conf") # Keep track of modules for a rollback importctrl = ImportRollback() # Import /plugins/ - if config["plugins"]: - if not PluginsImport(): + if irc.config["plugins"]: + plugins = PluginsImport() + if not plugins: print "[Plugins] Failed to load." - # Create irclib irc object - irc = irclib.irc() - # Set debug to true/false inside irc() object - irc.debug = config["debug"] + irc.debug = irc.config["debug"] # Create socket object irc.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) @@ -108,8 +115,8 @@ def main(): irc.sock = ssl.wrap_socket(irc.sock) # Connect to IRC server - irc.sock.connect((config["host"], config["port"])) - print "Connecting to "+config["host"]+':'+str(config["port"]) + irc.sock.connect((irc.config["host"], irc.config["port"])) + print "Connecting to "+irc.config["host"]+':'+str(irc.config["port"]) # Display SSL information to the user ssl_info = irc.sock.cipher() @@ -119,10 +126,10 @@ def main(): print "[SSL] Bits: "+str(ssl_info[2]) # Send User/Nick message to establish user on the server - irc.User(config["ident"], config["mode"], - config["unused"], config["realname"]) + irc.User(irc.config["ident"], irc.config["mode"], + irc.config["unused"], irc.config["realname"]) - irc.Nick(config["nick"]) + irc.Nick(irc.config["nick"]) while True: # Buffer to store data from server @@ -152,24 +159,29 @@ def main(): # Print line, parse it and respond print line - pack = irc.Parser(line) + irc.pack = irc.Parser(line) + + # Run all plugins main() function + if irc.config["plugins"]: + for plugin in plugins: + plugin.main.main(irc) # Ping Pong, keep the connection alive. - if pack["cmd"] == "PING": - irc.Pong(pack["text"]) + if irc.pack["cmd"] == "PING": + irc.Pong(irc.pack["text"]) # Send user mode message after command 001 - elif pack["cmd"] == "001": - irc.Mode(config["nick"], config["mode"]) + elif irc.pack["cmd"] == "001": + irc.Mode(irc.config["nick"], irc.config["mode"]) - elif pack["cmd"] == "NOTICE": - if pack["ident"] == "NickServ": + elif irc.pack["cmd"] == "NOTICE": + if irc.pack["ident"] == "NickServ": # Send password after NickServ informs you # that your nick is registered pattern = r"[Tt]his nickname is registered" - if re.search(pattern, pack["text"]): - irc.Identify(config["password"]) - irc.Join("#Flea") + if re.search(pattern, irc.pack["text"]): + irc.Identify(irc.config["password"]) + irc.Join(irc.config["channel"]) main() diff --git a/plugins/Control/__init__.py b/plugins/Control/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/plugins/Control/main.py b/plugins/Control/main.py new file mode 100644 index 0000000..72cbdad --- /dev/null +++ b/plugins/Control/main.py @@ -0,0 +1,206 @@ +import re + +# Init +controlchan = '' +chans = {} +access = [] +queue = [] + + +def chkcontrol(irc): + global access + global queue + + nick = irc.pack["nick"] + + for person in access: + if nick == person: + return True + + if queue.count(nick) == 0: + queue.append(nick) + irc.Whois(nick) + +def noaccess(irc): + msg = "You don't have access to that." + irc.Notice(msg, irc.pack["nick"]) + +def main(irc): + global controlchan + global access + global chans + global queue + + # Set control channel + controlchan = irc.config["channel"] + + # NOTICEs are used to "control" Flea + if irc.pack["cmd"] == "NOTICE": + + # Nick must match the nick in settings + # e.g. control = Kris + if irc.pack["nick"] == irc.config["control"]: + + # Split message by spaces, for functions with + # required parameters. e.g. " " + if irc.pack["text"].find(' ') != -1: + command = irc.pack["text"].lower().split(' ') + + # Command Flea to join a channel + if command[0] == "join": + if chkcontrol(irc): + irc.Join(command[1]) + else: + noaccess(irc) + + # Command Flea to part + elif command[0] == "part": + if chkcontrol(irc): + irc.Part(command[1], + "Fleabot https://github.com/Kris619/Flea") + else: + noaccess(irc) + + # Single word commands + else: + if irc.pack["text"] == "acc": + if chkcontrol(irc): + irc.Notice("You already have access to Control.", + irc.pack["nick"]) + else: + noaccess(irc) + + if irc.pack["text"] == "quit": + if chkcontrol(irc): + irc.Quit("Fleabot https://github.com/Kris619/Flea") + quit() + else: + noaccess(irc) + + # Check if commanding Nick is identified + elif irc.pack["cmd"] == "307": + if irc.pack["text"] == "is identified for this nick": + + # Check if Nick is in the queue for access list + if queue.count(irc.pack["params"][1]) == 1: + + # Remove Nick from queue + queue.remove(irc.pack["params"][1]) + + # Check if user is set to control in settings + # e.g. control = Kris + if irc.pack["params"][1] == irc.config["control"]: + + + # Is control user inside the control channel? + if chans[controlchan].count(irc.pack["params"][1]) == 1: + + # Grant access + access.append(irc.pack["params"][1]) + + print irc.pack["params"][1]+" added to Access" + + irc.Notice( + "You have been given access to Control.", + irc.pack["params"][1]) + else: + irc.Notice( + "You are not in the Control channel: "+controlchan, + irc.pack["params"][1]) + + # Keep track of users in every channel + # "353" lists users to a channel + elif irc.pack["cmd"] == "353": + + # If channel is in dictionary of channels + if chans.keys().count(irc.pack["params"][2]) == 1: + + # Split list of channel users by space + # "+Kris @Flea" > ["+Kris", "@Flea"] + if irc.pack["text"].find(' ') != -1: + chan_nicks = irc.pack["text"].split(' ') + + for aNick in chan_nicks: + if aNick != '': + + # Remove prefix; e.g. "+Kris" > "Kris" + if re.search("^[~&@%+]", aNick): + aNick = aNick[1:] + + # Add nick to it's channel + chans[irc.pack["params"][2]].append(aNick) + + # Track users joining channels + elif irc.pack["cmd"] == "JOIN": + + # Check to see if it is the bot that joined + if irc.pack["nick"] == irc.config["nick"]: + # Create channel user list if it doesn't exist + if chans.keys().count(irc.pack["text"]) == 0: + chans[irc.pack["text"]] = [] + + # Another user joined + else: + # Add user to channel; chans["#Channel"].append("Kris") + chans[irc.pack["text"]].append(irc.pack["nick"]) + + # Track users parting from the channel + elif irc.pack["cmd"] == "PART": + # Remove nick from channel they parted from + chans[irc.pack["params"][0]].remove(irc.pack["nick"]) + + # If someone parted the control channel + # check if it was someone on the access list + if irc.pack["params"][0] == controlchan: + for usr in access: + if usr == irc.pack["nick"]: + # Someone on the access list left. + access.remove(irc.pack["nick"]) + print irc.pack["nick"]+" removed from Access" + + # Track users parting from the channel + elif irc.pack["cmd"] == "QUIT": + + # Remove user from channels + for channel in chans.keys(): + if chans[channel].count(irc.pack["nick"]) == 1: + chans[channel].remove(irc.pack["nick"]) + + # Remove access if user quitting had it + for usr in access: + if usr == irc.pack["nick"]: + access.remove(irc.pack["nick"]) + print irc.pack["nick"]+" removed from Access" + + # Track users being kicked from the channel + elif irc.pack["cmd"] == "KICK": + + # Remove nick from channel they were kicked from + chans[irc.pack["params"][0]].remove(irc.pack["params"][1]) + + # If someone was kicked from the control channel + # check if it was someone on the access list + if irc.pack["params"][0] == controlchan: + if irc.pack["params"][1] == irc.config["nick"]: + print "Access list is erased, control channel is gone" + access = [] + else: + for usr in access: + if usr == irc.pack["params"][1]: + # Someone on the access list was kicked. + access.remove(irc.pack["params"][1]) + print irc.pack["params"][1]+" removed from Access" + + # Track users changing their nick + elif irc.pack["cmd"] == "NICK": + + for channel in chans.keys(): + if channel.count(irc.pack["nick"]) == 1: + channel.remove(irc.pack["nick"]) + channel.append(irc.pack["text"]) + + # Remove access to users changing their nick + for usr in access: + if access.count(irc.pack["nick"]) == 1: + access.remove(irc.pack["nick"]) + print irc.pack["nick"]+" removed from Access" diff --git a/settings.conf b/settings.conf index 59a5f7b..44babd0 100644 --- a/settings.conf +++ b/settings.conf @@ -1,5 +1,5 @@ debug = true -plugins = false +plugins = true host = irc.example.net port = 7001 @@ -11,3 +11,5 @@ mode = +B unused = * realname = your_name +control = your_nick +channel = #MyChannel \ No newline at end of file