diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..e152ff0 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# normalize text files to use lf +text eol=lf diff --git a/.gitignore b/.gitignore index 2f78cf5..84416d1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ *.pyc - +mysettings.conf \ No newline at end of file diff --git a/Flea/Flea.py b/Flea/Flea.py new file mode 100644 index 0000000..b52d335 --- /dev/null +++ b/Flea/Flea.py @@ -0,0 +1,221 @@ +# An IRC bot named Flea +# Copyright (C) 2016 Kris Lamoureux + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see + +import config +import irclib + +# Built-in to Python 2.7 +import __builtin__ +import socket +import ssl +import sys +import os +import re + + +# Allows reimporting modules +class ImportRollback: + def __init__(self, plugins_folder="/plugins"): + # Dictionary of loaded modules + self.curMods = sys.modules.copy() + self.newImport = __builtin__.__import__ + + self.plugins = os.path.join(os.getcwd(), plugins_folder) + + # Add the plugins location to the path variable + sys.path.append(self.plugins) + + # Override builtin import function with install() + __builtin__.__import__ = self.install + self.newMods = {} + + # Import modules + def install(self, mod, globals=None, locals=None, fromlist=[]): + self.newMods[mod] = 1 + return apply(self.newImport, (mod, globals, locals, fromlist)) + + # Delete modules + def reset(self): + for mod in self.newMods.keys(): + if not self.curMods.has_key(mod): + del(sys.modules[mod]) + + __builtin__.__import__ = self.newImport + + +# Print and log to logfile +def printlog(message, log=None): + print message + if log is not None: + log.write(message+"\n") + + +def PluginsImport(log=None, plugins_folder="/plugins"): + plugins = os.path.join(os.getcwd(), plugins_folder) + plugin_list = [] + + if os.path.exists(plugins): + os.chdir(plugins) + + for item in os.listdir(plugins): + if os.path.isdir(os.path.join(plugins, item)): + printlog("[Plugins] Initializing " + item, log) + plugin = __import__(item+".main") + plugin_list.append(plugin) + + else: + return None + + os.chdir(os.getcwd()) + return plugin_list + + +def init_connection(config_file="settings.conf"): + irc_conn = irclib.irc() + irc_conn.config = config.cfgParser(config_file) + + if irc_conn.config["logging"]: + log = open("log.txt", 'a') + irc_conn.log = log + else: + log = None + + irc_conn.debug = irc_conn.config["debug"] + + # Keep track of modules for a rollback + importctrl = ImportRollback() + + # Import /plugins + if irc_conn.config["plugins"]: + plugins = PluginsImport(log) + else: + plugins = None + + if plugins is not None: + # TODO: Add more debugging messages, sporadatic. + printlog("[Plugins] Failed to load.", log) + + # Create socket object and wrap with SSL object, then connect. + irc_conn.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + irc_conn.sock = ssl.wrap_socket(irc_conn.sock) + + server = (irc_conn.config["host"], irc_conn.config["port"]) + try: + printlog("Connecting to " + server[0] + ':' + str(server[1])) + irc_conn.sock.connect(server) + except: + printlog("Connection failed.", log) + return (None, None) + + # Display SSL inforation to the user + ssl_info = irc_conn.sock.cipher() + if ssl_info is not None: + printlog("[SSL] Cipher: " + ssl_info[0], log) + printlog("[SSL] Version: " + ssl_info[1], log) + printlog("[SSL] Bits: " + str(ssl_info[2]), log) + + # Establish identity on server + identity = (irc_conn.config["ident"], irc_conn.config["mode"], + irc_conn.config["unused"], irc_conn.config["realname"]) + irc_conn.User(*identity) + irc_conn.Nick(irc_conn.config["nick"]) + + return (irc_conn, plugins) + + +def client_loop(irc_conn, plugins, log=None): + wait = None + if irc_conn is None: + print "No connection established." + sys.exit(0) + + while True: + # Buffer to store data from server + data = '' + + while True: + # Receive data from connection + tmpdata = irc_conn.sock.recv(4096) + data = data + tmpdata + + if len(tmpdata) < 4096: + break + + # If no incoming data exists then connection has closed + if len(tmpdata) == 0: + print "Connection closed." + raw_input() + sys.exit(0) + + # Split data to easily deal with it + data = tmpdata.split("\r\n") + + # Parse IRC line by line + for line in data: + + # Ignore empty lines + if len(line) > 0: + printlog(line, log) + irc_conn.pack = irc_conn.Parser(line) + + # Run all plugins main() function + wait = None + if irc_conn.config["plugins"]: + if plugins is not None: + for plugin in plugins: + wait = plugin.main.main(irc_conn) + if wait == "QUIT": + break + + # Ping Pong, keep the connection alive. + if irc_conn.pack["cmd"] == "PING": + irc_conn.Pong(irc_conn.pack["text"]) + + # Send user mode message after command 001 + elif irc_conn.pack["cmd"] == "001": + irc_conn.Mode(irc_conn.config["nick"], irc_conn.config["mode"]) + + elif irc_conn.pack["cmd"] == "NOTICE": + if irc_conn.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, irc_conn.pack["text"]): + irc_conn.Identify(irc_conn.config["password"]) + irc_conn.Join(irc_conn.config["channel"]) + + if log: + log.flush() + + # Wait for QUIT to be returned from any plugin's main() function + if wait == "QUIT": + # Quit, close connection and logfile. + irc_conn.Quit("Fleabot https://github.com/Kris619/Flea") + irc_conn.sock.close() + if log: + log.close() + + print "Press the [ENTER] key to close." + raw_input() + sys.exit(0) + + +def main(): + (irc_conn, plugins) = init_connection() + client_loop(irc_conn, plugins) + +if __name__ == "__main__": + main() diff --git a/core/__init__.py b/Flea/__init__.py similarity index 100% rename from core/__init__.py rename to Flea/__init__.py diff --git a/core/config.py b/Flea/config.py similarity index 100% rename from core/config.py rename to Flea/config.py diff --git a/core/irclib.py b/Flea/irclib.py similarity index 96% rename from core/irclib.py rename to Flea/irclib.py index dbf15cf..6988ffc 100644 --- a/core/irclib.py +++ b/Flea/irclib.py @@ -19,14 +19,15 @@ import socket import re class irc: - + # TODO: Wrap in __init__ debug = False - log = False + log = None config = {} pack = {} sock = socket.socket() - # IRC Parser. Parses by line + # IRC Parser. Parses by line Functions are + # lowercase, classes uppercase def Parser(self, line): packet = {"nick":None, "ident":None, "host":None, "cmd":None, @@ -95,7 +96,7 @@ class irc: if self.debug: print output - if self.log: + if self.log is not None: self.log.write(output+"\n") def User(self, nick, mode, unused, owner): diff --git a/Flea.py b/bin/Flea similarity index 95% rename from Flea.py rename to bin/Flea index 8b95b63..d040c7c 100644 --- a/Flea.py +++ b/bin/Flea @@ -1,3 +1,4 @@ +#!/bin/python # An IRC bot named Flea # Copyright (C) 2016 Kris Lamoureux @@ -18,7 +19,7 @@ import traceback import sys try: - import core.main + import Flea.Flea except KeyboardInterrupt: print "\nGoodbye!" @@ -39,3 +40,4 @@ except: raw_input() sys.exit(0) +Flea.Flea.main() diff --git a/core/main.py b/core/main.py deleted file mode 100644 index 4696757..0000000 --- a/core/main.py +++ /dev/null @@ -1,221 +0,0 @@ -# An IRC bot named Flea -# Copyright (C) 2016 Kris Lamoureux - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. - -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see - -from core.config import * -import core.irclib as irclib - -# Built-in to Python 2.7 -import __builtin__ -import socket -import ssl -import sys -import os -import re - - -# Allows reimporting modules -class ImportRollback: - def __init__(self): - # Dictionary of loaded modules - self.curMods = sys.modules.copy() - self.newImport = __builtin__.__import__ - - # Directory of plugins - self.plugins = os.getcwd()+"/plugins/" - - # Add the plugins location to the path variable - # Helps the system find the plugin modules - sys.path.append(self.plugins) - - # Override builtin import function with install() - __builtin__.__import__ = self.install - self.newMods = {} - - # Import modules - def install(self, mod, globals=None, locals=None, fromlist=[]): - self.newMods[mod] = 1 - return apply(self.newImport, (mod, globals, locals, fromlist)) - - # Delete modules - def reset(self): - for mod in self.newMods.keys(): - if not self.curMods.has_key(mod): - del(sys.modules[mod]) - - __builtin__.__import__ = self.newImport - - -# Print and log to logfile -def prntlog(message, logfile): - print message - if logfile: - logfile.write(message+"\n") - - -def PluginsImport(log=False): - # Get root of Flea - current = os.getcwd() - # 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) - - # Go through every item in /plugins/ - for item in os.listdir(plugins): - - # Only import directory plugins (no single files) - if os.path.isdir(plugins+item): - prntlog("[Plugins] Initializing "+item, log) - plugin = __import__(item+".main") - plugin_list.append(plugin) - - else: - return False - - os.chdir(current) - return plugin_list - - -def main(): - - # Create irclib irc object - irc = irclib.irc() - - # Parse main settings.conf file - irc.config = cfgParser("settings.conf") - - # If logging is enabled, open log file. - if irc.config["logging"]: - log = open("log.txt", 'a') - irc.log = log - else: - log = False - - # Keep track of modules for a rollback - importctrl = ImportRollback() - - # Import /plugins/ - if irc.config["plugins"]: - plugins = PluginsImport(log) - - if not plugins: - prntlog("[Plugins] Failed to load.", log) - - # Set debug to true/false inside irc() object - irc.debug = irc.config["debug"] - - # Create socket object - irc.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - - # Wrap socket object to create SSLSocket object - irc.sock = ssl.wrap_socket(irc.sock) - - # Connect to IRC server - host = irc.config["host"] - port = irc.config["port"] - - irc.sock.connect((host, port)) - prntlog("Connecting to "+host+':'+str(port), log) - - # Display SSL information to the user - ssl_info = irc.sock.cipher() - if ssl_info != None: - prntlog("[SSL] Cipher: "+ssl_info[0], log) - prntlog("[SSL] Version: "+ssl_info[1], log) - prntlog("[SSL] Bits: "+str(ssl_info[2]), log) - - # Send User/Nick message to establish user on the server - irc.User(irc.config["ident"], irc.config["mode"], - irc.config["unused"], irc.config["realname"]) - - irc.Nick(irc.config["nick"]) - - while True: - # Buffer to store data from server - data = '' - - while True: - # Receive data from connection - tmpdata = irc.sock.recv(4096) - data = data + tmpdata - - if len(tmpdata) < 4096: - break - - # If no incoming data exists then connection has closed - if len(tmpdata) == 0: - print "Connection closed." - raw_input() - sys.exit(0) - - # Split data to easily deal with it - data = tmpdata.split("\r\n") - - # Parse IRC line by line - for line in data: - - # Ignore empty lines - if len(line) > 0: - - # Print/log line, parse it and respond - prntlog(line, log) - irc.pack = irc.Parser(line) - - # Run all plugins main() function - wait = '' - if irc.config["plugins"]: - for plugin in plugins: - wait = plugin.main.main(irc) - if wait == "QUIT": - break - - # Ping Pong, keep the connection alive. - if irc.pack["cmd"] == "PING": - irc.Pong(irc.pack["text"]) - - # Send user mode message after command 001 - elif irc.pack["cmd"] == "001": - irc.Mode(irc.config["nick"], irc.config["mode"]) - - 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, irc.pack["text"]): - irc.Identify(irc.config["password"]) - irc.Join(irc.config["channel"]) - - if log: log.flush() - - # Wait for QUIT to be returned from any plugin's main() function - if wait == "QUIT": - # Quit, close connection and logfile. - irc.Quit("Fleabot https://github.com/Kris619/Flea") - irc.sock.close() - if log: log.close() - - print "Press the [ENTER] key to close." - raw_input() - sys.exit(0) - - -main()