فهرست منبع

rotBot refactoring & webgui extension

tBKwtWS 6 سال پیش
والد
کامیت
919dd9a0a1
37فایلهای تغییر یافته به همراه1762 افزوده شده و 908 حذف شده
  1. 19 10
      README.md
  2. 3 0
      requirements.txt
  3. 51 47
      rotbot/bot.py
  4. 83 38
      rotbot/commands/admin.py
  5. 27 25
      rotbot/commands/common.py
  6. 188 181
      rotbot/commands/games.py
  7. 23 26
      rotbot/commands/public.py
  8. 249 214
      rotbot/commands/statistics.py
  9. 121 7
      rotbot/common/queries.py
  10. 0 2
      rotbot/events/common.py
  11. 6 7
      rotbot/events/on_action.py
  12. 11 17
      rotbot/events/on_join.py
  13. 8 26
      rotbot/events/on_kick.py
  14. 113 122
      rotbot/events/on_mode.py
  15. 11 12
      rotbot/events/on_nick.py
  16. 11 6
      rotbot/events/on_pubmsg.py
  17. 3 3
      website/dancecalendar/views.py
  18. 3 3
      website/knowledgebase/views.py
  19. 16 1
      website/rotbot/forms.py
  20. 54 18
      website/rotbot/migrations/0001_initial.py
  21. 0 21
      website/rotbot/migrations/0002_auto_20191114_1739.py
  22. 34 0
      website/rotbot/migrations/0002_auto_20191115_0606.py
  23. 0 30
      website/rotbot/migrations/0003_auto_20191114_1920.py
  24. 20 0
      website/rotbot/migrations/0003_tempchannelkey_created.py
  25. 0 42
      website/rotbot/migrations/0004_auto_20191114_2005.py
  26. 56 0
      website/rotbot/migrations/0004_auto_20191116_0255.py
  27. 49 0
      website/rotbot/migrations/0005_auto_20191116_0315.py
  28. 166 3
      website/rotbot/models.py
  29. 61 0
      website/rotbot/templates/rotbot/channel.html
  30. 22 0
      website/rotbot/templates/rotbot/channel_settings.html
  31. 67 8
      website/rotbot/templates/rotbot/network.html
  32. 1 5
      website/rotbot/templates/rotbot/network_form.html
  33. 46 4
      website/rotbot/templates/rotbot/networks.html
  34. 55 0
      website/rotbot/templates/rotbot/user.html
  35. 4 0
      website/rotbot/urls.py
  36. 180 29
      website/rotbot/views.py
  37. 1 1
      website/templates/base.html

+ 19 - 10
README.md

@@ -9,24 +9,26 @@ Then activate the virtenv: `source bin/activate`
 To later deactive: `deactivate`
 
 
-# Prerequisites
+# Get started
 
-## Python 3.7.4
+## Prerequisites
+### Python 3.7.4
 
 Older versions might work, development environ and runtime environment run Python-3.7.4
 Currently newer then in the repo's, if that's still the case, you might have to compile and built it yourself.
 
 If you notice you can' t use ssl when using pip, you should have installed `libssl-dev` before building python, or any of the following packages: `libreadline-gplv2-dev libncursesw5-dev libssl-dev libsqlite3-dev tk-dev libgdbm-dev libc6-dev libbz2-dev`
 
-## System packages needed for psycopg2
+### System packages needed for psycopg2
 Install either postgresql-server-dev-X.Y or libpq-dev: `sudo apt install`...
 
-## Python packages
+## Download the project files.
+ 1. `git clone ssh://h0v1n8.nl/srv/git/h0v1n8-website.git`
 
+## Python packages
 Install required pyton packages: `pip3 install -r requirements.txt`
 
 ## Postgres database with user.
-
 Fill in the passwords, don't change the database name, it's hardcoded in pyRot, for now.
 1. `sudo apt install postrgresql`
 1. `sudo su - postgresql`
@@ -40,7 +42,6 @@ Fill in the passwords, don't change the database name, it's hardcoded in pyRot,
 1. `GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO pyrot;`
 
 ## Configuration
-
 1. Set the database authentication details in the following file, for the robot: `rotbot/bot.py`
 1. Create file `website/website/local_settings.py` and fill in the key and password:
 ```
@@ -72,12 +73,12 @@ DATABASES = {
 }
 ```
 
-## Create the database tables and static files.
-
+## Create the database tables, static files and superuser.
 1. `cd website`
 1. `python manage.py makemigrations`
 1. `python manage.py migrate`
 1. `python manage.py collectstatic`
+1. `python manage.py createsuperuser`
 
 ## uwsgi
 1. `sudo ln -s /opt/h0v1n8-website-env/website_uwsgi.ini /etc/uwsgi/vassals/website_uwsgi.ini`
@@ -99,21 +100,29 @@ TBD
 1. `sudo systemctl reload nginx`
 
 
-
 # Testing the project.
 
 ## webgui
-
 1. `cd website`
 1. `python manage.py runserver`
 
+
 # Running the project
+
 ## pyRot
 The pythonic version of RotBot, there have been 5 earlier versions including winRot and javaRot.
 
 1. `cd rotbot`
 1. `python bot.py 1` Where `1` is the ID of the server database record.
+Tip: Run it in a "screen".
+
+## h0v1n8 website
+1. `sudo systemctl start rc-`
+
 
+# Updating
+1. `git pull`
+1.
 
 # Edeting the look of the webgui
 

+ 3 - 0
requirements.txt

@@ -17,8 +17,11 @@ postgres==3.0.0
 psycopg2==2.8.4
 psycopg2-binary==2.8.4
 psycopg2-pool==1.1
+python-slugify==4.0.0
 pytz==2019.2
 six==1.13.0
 sqlparse==0.3.0
 tempora==1.14.1
+text-unidecode==1.3
+Unidecode==1.1.1
 zipp==0.6.0

+ 51 - 47
rotbot/bot.py

@@ -8,16 +8,17 @@ from postgres import Postgres   # https://postgres-py.readthedocs.io/en/latest/
 
 import commands.public, commands.admin, commands.games, commands.statistics
 import events.on_welcome, events.on_join, events.on_kick, events.on_mode, events.on_pubmsg, events.on_action, events.on_whoreply, events.on_nick
-from common import log, font
+from common import log, font, queries
 from common.networkservices import NickServ
-from events.common import Inform, MessageStatistics #Lastact,
+from events.common import Inform #, MessageStatistics #Lastact,
 
 class PyRot(irc.bot.SingleServerIRCBot):
-    def __init__(self, network, db):
+    def __init__(self, network, db, webgui):
 
         # Globals, so it's not needed to pass network and db with every call.
         self.network = network
         self.db = db
+        self.webgui = webgui
 
         # Pick a random host from the network.
         hosts = db.all("SELECT * FROM rotbot_host WHERE network_id=%(network)s", network=network.id)
@@ -66,9 +67,9 @@ class PyRot(irc.bot.SingleServerIRCBot):
     def on_join(self, connection, event):
         events.on_join.process_event(self, connection, event)
 
-#     def on_mode(self, connection, event):
-#         events.on_mode.process_event(self, connection, event)
-#
+    def on_mode(self, connection, event):
+        events.on_mode.process_event(self, connection, event)
+
     def on_kick(self, connection, event):
         events.on_kick.process_event(self, connection, event)
 #
@@ -79,11 +80,6 @@ class PyRot(irc.bot.SingleServerIRCBot):
 #         if event.target == self.homechannel and event.source.nick in self.protectees:    # Protectee parted home channel.
 #             del self.protectees[event.source.nick] # Delete from protectees.
 #
-#         # Update last act.
-#         if event.arguments:
-#             Lastact.update(self, event.source.nick, "part", channel=event.target, lastact=event.arguments[0])
-#         else:
-#             Lastact.update(self, event.source.nick, "part", channel=event.target)
 #
 #     def on_quit(self, connection, event):
 #         log.info(event)
@@ -91,12 +87,6 @@ class PyRot(irc.bot.SingleServerIRCBot):
 #         # Update protectees.
 #         if event.source.nick in self.protectees:    # Protectee parted home channel.
 #             del self.protectees[event.source.nick] # Delete from protectees.
-#
-#         # Update last act.
-#         if event.arguments:
-#             Lastact.update(self, event.source.nick, "quit", lastact=event.arguments[0])
-#         else:
-#             Lastact.update(self, event.source.nick, "quit")
 #
     def on_invite(self, connection, event):
         log.notice(event)
@@ -106,41 +96,46 @@ class PyRot(irc.bot.SingleServerIRCBot):
 #     def on_topic(self, connection, event):
 #         log.info(event)
 #
-#         # Update last act.
-#         Lastact.update(self, event.source.nick, "topic", channel=event.target, lastact=event.arguments[0])
 #
-    def on_pubmsg(self, connection,  event):
+    def on_pubmsg(self, connection,  event):    # Channel message
         events.on_pubmsg.process_event(self, connection, event)
-        commands.public.do_command(self, connection, event)
-        commands.admin.do_command(self, connection, event)
-#         commands.statistics.do_command(self, connection, event)
-#         commands.games.do_command(self, connection, event)
-#
-    def on_privmsg(self, connection, event):
+
+    def on_action(self, connection, event): # Is this both channel and private actions?
+        events.on_action.process_event(self, connection, event)
+
+    def on_privmsg(self, connection, event):    # Private message
         log.info(event)
-        commands.public.do_command(self, connection, event)
-        commands.admin.do_command(self, connection, event)
-#         commands.statistics.do_command(self, connection, event)
-#         commands.games.do_command(self, connection, event)
-        if not event.source.nick == connection.get_nickname():
-            Inform.owners(self, connection, "PM from " + font.red + font.red + event.source.nick + font.grey + ": " + font.reset + event.arguments[0])
-#
-    def on_pubnotice(self, connection, event):
+
+        user = queries.create_or_get_and_update_last_event(self, 'user', 'pm', user_name=event.source.nick, event_subject_name=event.target)
+        channel = None
+        if not event.source.nick == connection.get_nickname():  # Message is not from myself.
+            Inform.owners(self, connection, 'PM from %s%s%s: %s%s' % (font.red, event.source.nick, font.grey, font.green, event.arguments[0]))  # Forward message to users with owner rights of home channel.
+
+        commands.public.do_command(self, connection, event, user, channel)
+        commands.admin.do_command(self, connection, event, user, channel)
+        commands.statistics.do_command(self, connection, event, user, channel)
+        commands.games.do_command(self, connection, event, user, channel)
+
+
+    def on_pubnotice(self, connection, event):  # Channel notice
         log.info(event)
 
-        # # Update last act.
-        # Lastact.update(self, event.source.nick, "notice", channel=event.target, lastact=event.arguments[0])
+        user = queries.create_or_get_and_update_last_event(self, 'user', 'cn', channel_name=event.target, user_name=event.source.nick, event_content=event.arguments[0])
+        channel = queries.create_or_get_and_update_last_event(self, 'channel', 'cn', channel_name=event.target, user_name=event.source.nick, event_content=event.arguments[0])
 
-        # Save statistic to database.
-        MessageStatistics.update(self, event, 'notice')
 
-    def on_privnotice(self,  connection,  event):
+    def on_privnotice(self,  connection,  event):   # Private notice.
         log.info(event)
-        commands.public.do_command(self, connection, event)
-        commands.admin.do_command(self, connection, event)
-        # commands.statistics.do_command(self, connection, event)
-        # commands.games.do_command(self, connection, event)
-        if event.source.nick == connection.get_nickname():  # Message came from bot?
+
+        user = queries.create_or_get_and_update_last_event(self, 'user', 'pn', user_name=event.source.nick, event_subject_name=event.target)
+        channel = None
+
+        commands.public.do_command(self, connection, event, user, channel)
+        commands.admin.do_command(self, connection, event, user, channel)
+        commands.statistics.do_command(self, connection, event, user, channel)
+        commands.games.do_command(self, connection, event, user, channel)
+
+        if event.source.nick == connection.get_nickname():  # Message came from myself.
             return
         elif event.source.nick == "NickServ":
             if event.arguments[0].startswith("This nickname is registered"):
@@ -170,8 +165,6 @@ class PyRot(irc.bot.SingleServerIRCBot):
         if event.source.nick != "Global":
             Inform.notice_owners(self, connection, 'Notice from %s %s %s %s: %s %s' % (font.red, font.red, event.source.nick, font.grey, font.reset, event.arguments[0]))
 
-    def on_action(self, connection, event):
-        events.on_action.process_event(self, connection, event)
 
 #
 #     def on_whoreply(self, connection, event):
@@ -222,8 +215,19 @@ def main():
         sys.exit('To run type "python bot.py database_network_id", not: %s. Terminating program.' % (sys.argv)) # Terminate program.
     instance = sys.argv[1]  # Instance is the database network id.
 
+
+    ### SETTINGS
+    webgui = {
+        'base_url': 'https://h0v1n8.nl/rotbot/',  # The django rotbot app url.
+        'register_url': '',
+    }
+
+    # Database credentials
     username = 'pyrot'
     password = 'oGPnbiqh55QKLhmnKQgS92h74j0e9d6LE58cSsD1'
+
+
+
     db_connect_string = 'postgres://%s:%s@localhost/website' % (username, password)
     try:
         db = Postgres(db_connect_string)    # Connect to database.
@@ -238,7 +242,7 @@ def main():
     if network.enabled == False: # Network disabled in database.
         sys.exit('Network %s marked as disabled in database, use webgui to enable. Terminating program.' % (network.name)) # Exit
 
-    bot = PyRot(network, db)
+    bot = PyRot(network, db, webgui)
     bot.start()
 
 if __name__ == "__main__":

+ 83 - 38
rotbot/commands/admin.py

@@ -1,10 +1,10 @@
-import secrets, string, re
-from common import userstatus, do_everything_to, log, font
+import string, re
+from common import userstatus, do_everything_to, log, font, queries
 from commands.common import CommandHelpers as CH
 from commands.common import AdminHelpers as AH
 
 
-def do_command(self, connection, event):
+def do_command(self, connection, event, user, channel):
     cmdtype, trigger, command, replyto = CH.disect_command(self, event)
 
     # Do nothing if it's not a type of command.
@@ -35,13 +35,58 @@ def do_command(self, connection, event):
     elif not userstatus.atleast_voiced(self, event.source.nick, self.network.home_channel) and not userstatus.atleast_oper(self, event.source.nick, event.target):
         return
 
+    if one == 'settings':
+        if len(command.split()) == 1:   # No arguments.
+            if event.target == connection.get_nickname():   # Command issued directly to bot.
+                if cmdtype == 'help':
+                    connection.privmsg(replyto, 'Receive a temporary link to edit bot channel settings. Only works if you are channel operator of the specified channel or %s%s%s.' % (font.red, self.network.home_channel, font.reset))
+                    connection.privmsg(replyto, 'Usage: %s%s%s%s %schannel' % (font.blue, self.network.command_character, one, font.reset, font.italic))
+                else:   # Actual command, with no arguments.
+                    connection.privmsg(replyto, 'Specify channel, example: %s%s%s %s' % (font.blue, self.network.command_character, one, self.network.home_channel))
+            else:   # Command issued via channel.
+                if cmdtype == 'help':
+                    connection.privmsg(replyto, 'Receive a temporary link to edit bot channel settings. Only works if you are channel operator of the specified channel or %s%s%s. The channel argument is optional.' % (font.red, self.network.home_channel, font.reset))
+                    connection.privmsg(replyto, 'Usage: %s%s%s%s %schannel' % (font.blue, self.network.command_character, one, font.reset, font.italic))
+                else:   # Actual channel command with no arguments,
+                    if userstatus.atleast_oper(self, event.source.nick, self.network.home_channel) or userstatus.atleast_oper(self, event.source.nick, event.target):   # Has oper in chan or homechan.
+                        channel_id = queries.get_channel_id(self, event.target)
+                        temp_key = queries.create_tempchannelkey(self, channel_id)
+                        connection.privmsg(event.source.nick, 'Valid for the next 10 minutes: %schannelsettings/%s' % (self.webgui['base_url'], temp_key))
+                    else:   # Insufficient rights.
+                        if event.terget == self.network.home_channel:
+                            connection.privmsg(replyto, 'Insufficient rights, you need to have operator status or higher in %s%s%s.' % (font.red, event.target, font.reset))
+                        else:
+                            connection.privmsg(replyto, 'Insufficient rights, you need to have operator status or higher in %s%s%s or %s%s%s.' % (font.red, event.target, font.reset, font.red, self.network.home_channel, font.reset))
+
+        if len(command.split()) == 2:   # One argument.
+            if command.split()[1] in self.channels: # Bot inhabits specified channel
+                if cmdtype == 'help':
+                    connection.privmsg(replyto, 'Receive a temporary link to edit settings for %s%s%s.' % (font.red, command.split()[1], font.reset))
+                else:   # Actual command
+                    if userstatus.atleast_oper(self, event.source.nick, self.network.home_channel) or userstatus.atleast_oper(self, event.source.nick, command.split()[1]):   # Has oper in chan or homechan.
+                        channel_id = queries.get_channel_id(self, command.split()[1])
+                        temp_key = queries.create_tempchannelkey(self, channel_id)
+                        connection.privmsg(event.source.nick, 'Valid for the next 10 minutes: %schannelsettings/%s' % (self.webgui['base_url'], temp_key))
+                    else:   # Insufficient rights.
+                        if event.target == self.network.home_channel:
+                            connection.privmsg(replyto, 'Insufficient rights, you need to have operator status or higher in %s%s%s.' % (font.red, event.target, font.reset))
+                        else:
+                            connection.privmsg(replyto, 'Insufficient rights, you need to have operator status or higher in %s%s%s or %s%s%s.' % (font.red, event.target, font.reset, font.red, self.network.home_channel, font.reset))
+            else:   # Bot does not inhabit specified channel.
+                if cmdtype == 'help':
+                    connection.privmsg(replyto, 'Would send a link to edit the channel settings. But I\'m not on that channel and won\'t be able to check your status.')
+                else:   # Command for uninhabited channel
+                    connection.privmsg(replyto, 'I\'m not in that channel.')
+
+        if len(command.split()) > 2:    # More then one argument.
+            connection.privmsg(replyto, 'Too many arguments, specify one channel only. Example: %s%s%s %s' % (font.blue, self.network.command_character, one, self.network.home_channel))
+
+
     elif command == 'cmd' or command == 'cmds' or command == 'commands':
-        if cmdtype == 'help':    # Display help text.
-            connection.privmsg(replyto, 'Lists commands, usage: %s%s ' % self.network.command_character, command)
         if cmdtype == "cmd":
-            message = grey + "Admin: "
-            if CH.ccc(self, "channelfunctions",  {"homechan": "oper", "chan": "oper"}, event):
-                message += CH.ccc(self, "channelfunctions",  {"homechan": "oper", "chan": "oper"}, event)
+            message = font.grey + "Admin: "
+            if CH.ccc(self, "settings",  {"homechan": "oper", "chan": "oper"}, event):
+                message += CH.ccc(self, "settings",  {"homechan": "oper", "chan": "oper"}, event)
             if CH.ccc(self, "join", {"homechan": "oper",  "chan": None}, event):
                 message += CH.ccc(self, "join", {"homechan": "oper",  "chan": None}, event)
             if CH.ccc(self, "part", {"homechan": "oper",  "chan": None}, event):
@@ -60,16 +105,16 @@ def do_command(self, connection, event):
                 message += CH.ccc(self, "msg",  {"homechan": "oper",  "chan": "oper"}, event)
             if CH.ccc(self, "act", {"homechan": "oper",  "chan": "oper"}, event):
                 message += CH.ccc(self, "act", {"homechan": "oper",  "chan": "oper"}, event)
-            if message == grey + "Admin: ": # No commands to display.
+            if message == font.grey + "Admin: ": # No commands to display.
                 return
-            connection.privmsg(replyto, '%s.' % message[:-2])
+            connection.privmsg(replyto, message[:-2])
 
     elif one == 'quit':
         if cmdtype == 'help':    # Display help text.
             if len(command.split()) != 1:
                 return
             connection.privmsg(replyto, "Disconnect and terminate " + connection.get_nickname() + ". Optionally with reason.")
-            connection.privmsg(replyto,  grey + "Usage: " + blue + "!quit " + font.reset + font.italic + "reason")
+            connection.privmsg(replyto,  font.grey + "Usage: " + font.blue + "!quit " + font.reset + font.italic + "reason")
         elif cmdtype == "cmd":
             if not userstatus.atleast_admin(self, event.source.nick, self.network.home_channel): #Insufficient rights.
                 connection.privmsg(replyto, "Denied, you need to have admin (super operator) status or higher in " + font.font.red + self.network.home_channel  + font.reset + ".")
@@ -89,7 +134,7 @@ def do_command(self, connection, event):
             if len(command.split()) != 1:
                 return
             connection.privmsg(replyto, "Reconnect " + connection.get_nickname() + ". Reason optional.")
-            connection.privmsg(replyto,  grey + "Usage: " + blue + "!reconnect " + font.reset + font.italic + "reason")
+            connection.privmsg(replyto,  font.grey + "Usage: " + font.blue + "!reconnect " + font.reset + font.italic + "reason")
         elif cmdtype == "cmd":
             if len(command.split()) == 1:
                 self.disconnect(msg = "Reconnect requested by " + event.source.nick)
@@ -119,12 +164,12 @@ def do_command(self, connection, event):
             if len(command.split()) != 1:
                 return
             connection.privmsg(replyto, "Make " + connection.get_nickname() + " join a channel. Password optional.")
-            connection.privmsg(replyto, grey + "Usage: " + blue + self.cmdchar + "join " + font.font.red + font.italic + "channel " + font.reset + font.italic + "password")
+            connection.privmsg(replyto, font.grey + "Usage: " + font.blue + self.cmdchar + "join " + font.font.red + font.italic + "channel " + font.reset + font.italic + "password")
         elif cmdtype == "cmd":
             try:
                 channel = command.split()[1]
             except IndexError:
-                connection.privmsg(replyto, "Specify channel. For help type: " + blue + self.helpchar + "join")
+                connection.privmsg(replyto, "Specify channel. For help type: " + font.blue + self.network.help_character + "join")
                 return
             try:
                 key = command.split(maxsplit=2)[2]
@@ -138,7 +183,7 @@ def do_command(self, connection, event):
             if len(command.split()) != 1:
                 return
             connection.privmsg(replyto, "Make " + connection.get_nickname() + " part a channel. Reason optional.")
-            connection.privmsg(replyto, grey + "Usage: " + blue + self.cmdchar + "join " + font.font.red + font.italic + "channel " + font.reset + font.italic + "password")
+            connection.privmsg(replyto, font.grey + "Usage: " + font.blue + self.cmdchar + "join " + font.font.red + font.italic + "channel " + font.reset + font.italic + "password")
         elif cmdtype == "cmd":
 
             homeadmin = False
@@ -159,10 +204,10 @@ def do_command(self, connection, event):
                         return
                     connection.part(event.target, event.source.nick)
                 else:   # It's a PM.
-                    connection.privmsg(replyto, "Specify a channel to part. For help type " + blue + self.helpchar + "part" + font.reset + ".")
+                    connection.privmsg(replyto, "Specify a channel to part. For help type " + font.blue + self.network.help_character + "part" + font.reset + ".")
             elif len(command.split()) > 1:   # Arguments
                 if command.split()[1] not in self.channels: # First argument is not a channel the bot inhabits.
-                    connection.action(replyto, "does not inhabit " + font.red + command.split()[1] + font.reset + ". For help type " + blue + self.helpchar + "part" + font.reset + ".")
+                    connection.action(replyto, "does not inhabit " + font.red + command.split()[1] + font.reset + ". For help type " + font.blue + self.network.help_character + "part" + font.reset + ".")
                     return
                 if not homeadmin and not userstatus.atleast_oper(self, event.source.nick, command.split()[1]):  # Insufficient rights.
                     connection.privmsg(replyto, "Denied. You need to have at least operator status in " + font.red + self.homechan + font.reset + " or " + font.red + command.split()[1] + font.reset + ".")
@@ -186,19 +231,19 @@ def do_command(self, connection, event):
                 message = "Let " + connection.get_nickname() + "send a message to a channel."
                 arguments = "name message"
             connection.privmsg(replyto, message)
-            connection.privmsg(replyto, grey + "Usage: " + blue + "!" + command.split(maxsplit=1)[0] + font.reset + font.italic + " target " + arguments)
+            connection.privmsg(replyto, font.grey + "Usage: " + font.blue + "!" + command.split(maxsplit=1)[0] + font.reset + font.italic + " target " + arguments)
         elif cmdtype == "cmd":
 
             # Parse user input.
             try:
                 destination = trigger.split()[1]
             except IndexError:  # User did not specify a destination.
-                connection.privmsg(replyto, "Destination not specified. For help type: " + blue + self.helpchar + command.split(maxsplit=1)[0])
+                connection.privmsg(replyto, "Destination not specified. For help type: " + font.blue + self.network.help_character + command.split(maxsplit=1)[0])
                 return
             try:
                 message = trigger.split(maxsplit=2)[2]
             except IndexError:  # User did not specify a message.
-                connection.privmsg(replyto, "Message not specified. For help type: " + blue + self.helpchar + command.split(maxsplit=1)[0])
+                connection.privmsg(replyto, "Message not specified. For help type: " + font.blue + self.network.help_character + command.split(maxsplit=1)[0])
                 return
 
             # Send the message if user has owner status in the home channel.
@@ -235,8 +280,8 @@ def do_command(self, connection, event):
     #         if len(command.split()) != 1:
     #             return
     #         connection.privmsg(replyto, "Display or toggle the status channel functions. Channel, function and value optional. Get a description of a functio via the help argument.")
-    #         connection.privmsg(replyto, grey + "Usage: " + blue + "!channelfunctions " + font.red + font.italic + "channel " + font.reset + font.italic + "function value")
-    #         connection.privmsg(replyto, grey + "Usage: " + blue + "!channelfunctions describe " + font.reset + font.italic + "function")
+    #         connection.privmsg(replyto, font.grey + "Usage: " + font.blue + "!channelfunctions " + font.red + font.italic + "channel " + font.reset + font.italic + "function value")
+    #         connection.privmsg(replyto, font.grey + "Usage: " + font.blue + "!channelfunctions describe " + font.reset + font.italic + "function")
     #     elif cmdtype == "cmd":
     #
     #         if len(command.split()) == 1:   # No arguments.
@@ -252,18 +297,18 @@ def do_command(self, connection, event):
     #                 connection.privmsg(replyto, message)
     #             else:   # First argument is not a channel the bot inhabits.
     #                 if not command.split()[1].lower() == "help":    # Not a help request.
-    #                     connection.privmsg(replyto, command.split()[1] + " is not a channel I inhabit. For help type " + blue + self.helpchar + "channelfunctions" + font.reset + ".")
+    #                     connection.privmsg(replyto, command.split()[1] + " is not a channel I inhabit. For help type " + font.blue + self.network.help_character + "channelfunctions" + font.reset + ".")
     #                 else:   # Help request.
-    #                     connection.privmsg(replyto, "Specify a channel function to get a description of. For help type " + blue + self.helpchar + "channelfunctions" + font.reset + ".")
+    #                     connection.privmsg(replyto, "Specify a channel function to get a description of. For help type " + font.blue + self.network.help_character + "channelfunctions" + font.reset + ".")
     #
     #         elif len(command.split()) == 3: # Two arguments.
     #             channel = event.target
     #             if event.target == connection.get_nickname():   # Command issued via PM.
-    #                 connection.privmsg(replyto, "One or three arguments required. For help type " + blue + self.helpchar + "channelfunctions" + font.reset + ".")
+    #                 connection.privmsg(replyto, "One or three arguments required. For help type " + font.blue + self.network.help_character + "channelfunctions" + font.reset + ".")
     #             else:   # Command issued via channel.
     #                 if not AH.is_channelfunction(command.split()[1]):   # First argument is not a channelfunction.
     #                     if not command.split()[1].lower() == "describe":    # Not a help request.
-    #                         connection.privmsg(replyto, command.split()[1] + " is not a channel function. For help type " + blue + self.helpchar + "channelfunctions" + font.reset + ".")
+    #                         connection.privmsg(replyto, command.split()[1] + " is not a channel function. For help type " + font.blue + self.network.help_character + "channelfunctions" + font.reset + ".")
     #                         return
     #                     if not AH.is_channelfunction(command.split()[2]): # Second argument not a channel function.
     #                             connection.privmsg(replyto, command.split()[2] + " is not a channel function.")
@@ -274,10 +319,10 @@ def do_command(self, connection, event):
     #                 if not command.split()[2].lower() in ["on",  "off"]:
     #                     if command.split()[1].lower() == "aggressiveness":
     #                         if not AH.is_aggressiveness(command.split()[2].lower()):    # Is not an aggressiveness setting.
-    #                             connection.privmsg(replyto, command.split()[2] + " is not an aggressiveness setting. For help type " + blue + self.helpchar + "channelfunctions" + font.reset + ".")
+    #                             connection.privmsg(replyto, command.split()[2] + " is not an aggressiveness setting. For help type " + font.blue + self.network.help_character + "channelfunctions" + font.reset + ".")
     #                             return
     #                     else:   # Channel function is not aggresiveness.
-    #                         connection.privmsg(replyto, "The value of this channel function can only be \"on\" or \"off\". For help type " + blue + self.helpchar + "channelfunctions" + font.reset + ".")
+    #                         connection.privmsg(replyto, "The value of this channel function can only be \"on\" or \"off\". For help type " + font.blue + self.network.help_character + "channelfunctions" + font.reset + ".")
     #                         return
     #                 if not userstatus.atleast_oper(self, event.source.nick, self.network.home_channel) and not userstatus.atleast_oper(self, event.source.nick, event.target):   # Does not have operator status or higher in target or home channel.
     #                     connection.privmsg(replyto, "Denied. You need to have at least operator status in " + font.red + event.target + font.reset + " or " + font.red + self.network.home_channel + font.reset + ".")
@@ -289,14 +334,14 @@ def do_command(self, connection, event):
     #
     #         elif len(command.split()) == 4: # Three arguments.
     #             if not command.split()[1] in self.channels: # Bot does not inhabit channel to be altered.
-    #                 connection.privmsg(replyto, command.split()[1] + " is not a channel I inhabit. For help type " + blue + self.helpchar + "channelfunctions" + font.reset + ".")
+    #                 connection.privmsg(replyto, command.split()[1] + " is not a channel I inhabit. For help type " + font.blue + self.network.help_character + "channelfunctions" + font.reset + ".")
     #                 return
     #             if not AH.is_channelfunction(command.split()[2]):  # Function does not exist.
-    #                 connection.privmsg(replyto, command.split()[2] + " is not a valid channel function. For a list help type: " + blue + self.cmdchar + "channelfunctions" + font.red + font.italic + "channel")
+    #                 connection.privmsg(replyto, command.split()[2] + " is not a valid channel function. For a list help type: " + font.blue + self.cmdchar + "channelfunctions" + font.red + font.italic + "channel")
     #                 return
     #
     #             if not command.split()[3].lower() in ["on", "off"] and not command.split()[2].lower() == "aggressiveness": # Third argument unsupported.
-    #                 connection.privmsg(replyto, "The value of this channel function can only be \"on\" or \"off\". For help type " + blue + self.helpchar + "channelfunctions" + font.reset + ".")
+    #                 connection.privmsg(replyto, "The value of this channel function can only be \"on\" or \"off\". For help type " + font.blue + self.network.help_character + "channelfunctions" + font.reset + ".")
     #                 return
     #             if not userstatus.atleast_oper(self, event.source.nick, self.network.home_channel) and not userstatus.atleast_oper(self, event.source.nick, command.split()[1]):   # Does not have operator status or higher in target or home channel.
     #                 connection.privmsg(replyto, "Denied. You need to have at least operator status in " + font.red + event.target + font.reset + " or " + font.red + self.network.home_channel + font.reset + ".")
@@ -309,7 +354,7 @@ def do_command(self, connection, event):
     #                 connection.privmsg(replyto, "Error, database record not updated.")
     #                 return
     #         else:   # Too many arguments.
-    #             connection.privmsg(replyto, "Too many arguments. For help type " + blue + self.helpchar + "channelfunctions" + font.reset + ".")
+    #             connection.privmsg(replyto, "Too many arguments. For help type " + font.blue + self.network.help_character + "channelfunctions" + font.reset + ".")
 
     elif one == "registernick":
         if not self.channels[self.network.home_channel].is_owner(event.source.nick): #Insufficient rights.
@@ -319,13 +364,13 @@ def do_command(self, connection, event):
             if len(command.split()) != 1:
                 return
             connection.privmsg(replyto, "Register with NickServ.")
-            connection.privmsg(replyto, grey + "Usage: " + blue + self.cmdchar + "registernick " + font.reset + font.italic + "email")
+            connection.privmsg(replyto, font.grey + "Usage: " + font.blue + self.cmdchar + "registernick " + font.reset + font.italic + "email")
         elif cmdtype == "cmd":
 
             if len(command.split()) == 1:
-                connection.privmsg(replyto, "Insufficient arguments. For help type " + blue + self.helpchar + "registernick" + font.reset + ".")
+                connection.privmsg(replyto, "Insufficient arguments. For help type " + font.blue + self.network.help_character + "registernick" + font.reset + ".")
             elif len(command.split()) > 2:
-                connection.privmsg(replyto, "Too many arguments. For help type " + blue + self.helpchar + "registernick" + font.reset + ".")
+                connection.privmsg(replyto, "Too many arguments. For help type " + font.blue + self.network.help_character + "registernick" + font.reset + ".")
             elif not self.db.one("SELECT password FROM networks WHERE name='" + self.network + "'"):
                 alphabet = string.ascii_letters + string.digits
                 password = ''.join(secrets.choice(alphabet) for i in range(20)) # 20-character password.
@@ -342,13 +387,13 @@ def do_command(self, connection, event):
             if len(command.split()) != 1:
                 return
             connection.privmsg(replyto, "Ban all nicknames and usernames for a host in all channels.")
-            connection.privmsg(replyto, grey + "Example: " + blue + self.cmdchar + "banall " + font.reset + font.italic + "host")
+            connection.privmsg(replyto, font.grey + "Example: " + font.blue + self.cmdchar + "banall " + font.reset + font.italic + "host")
         elif cmdtype == "cmd":
 
             if len(command.split()) == 1:
-                connection.privmsg(replyto, "Insufficient arguments. For help type " + blue + self.helpchar + "banall" + font.reset + ".")
+                connection.privmsg(replyto, "Insufficient arguments. For help type " + font.blue + self.network.help_character + "banall" + font.reset + ".")
             elif len(command.split()) > 2:
-                connection.privmsg(replyto, "Too many arguments. For help type " + blue + self.helpchar + "banall" + font.reset + ".")
+                connection.privmsg(replyto, "Too many arguments. For help type " + font.blue + self.network.help_character + "banall" + font.reset + ".")
             else:
                 labels = trigger.split()[1].split(".")
                 allowed = re.compile("(?!-)[A-Z\d-]{1,63}(?<!-)$", re.IGNORECASE)

+ 27 - 25
rotbot/commands/common.py

@@ -49,34 +49,36 @@ class CommandHelpers():
                     if self.channels[event.target].is_owner(event.source.nick) or self.channels[event.target].is_admin(event.source.nick) or self.channels[event.target].is_oper(event.source.nick):
                         show = True
             if show:
-                return blue + self.cmdchar + command + grey + ", "
+                return font.blue + self.network.command_character + command + font.grey + ", "
         else:
-            return blue + self.cmdchar + command + grey + ", "
+            return font.blue + self.network.command_character + command + font.grey + ", "
 
 class AdminHelpers():
     def get_channelfunctions(self, channel):
-        channelfunctions = self.db.one("SELECT autojoin, aggressiveness, join_greeting, statistics_commands, games, chat FROM channels WHERE LOWER(name)=LOWER('" + channel + "') AND network='" + self.network + "'")
+        #channelfunctions = self.db.one("SELECT autojoin, aggressiveness, join_greeting, statistics_commands, games, chat FROM channels WHERE LOWER(name)=LOWER('" + channel + "') AND network='" + self.network + "'")
+        channelfunctions = self.db.one("SELECT autojoin FROM rotbot_channels WHERE LOWER(name)=LOWER('" + channel + "') AND network='" + self.network.id + "'")
         if channelfunctions[0]:
             autojoin = "on"
         else:
             autojoin = "off"
-        if channelfunctions[2]:
-            joingreet = "on"
-        else:
-            joingreet = "off"
-        if channelfunctions[3]:
-            statscmds = "on"
-        else:
-            statscmds = "off"
-        if channelfunctions[4]:
-            games = "on"
-        else:
-            games = "off"""
-        if channelfunctions[5]:
-            chat = "on"
-        else:
-            chat = "off"""
-        return ("autojoin " + green + autojoin + reset + ", aggressiveness " + green + channelfunctions[1] + reset +  ", join_greeting " + green + joingreet + reset + ", statistics_commands " + green + statscmds + reset + ", games " + green + games + reset + ", chat " + green + chat + reset + ".")
+        # if channelfunctions[2]:
+        #     joingreet = "on"
+        # else:
+        #     joingreet = "off"
+        # if channelfunctions[3]:
+        #     statscmds = "on"
+        # else:
+        #     statscmds = "off"
+        # if channelfunctions[4]:
+        #     games = "on"
+        # else:
+        #     games = "off"""
+        # if channelfunctions[5]:
+        #     chat = "on"
+        # else:
+        #     chat = "off"""
+        #return ("autojoin " + font.green + autojoin + font.reset + ", aggressiveness " + font.green + channelfunctions[1] + font.reset +  ", join_greeting " + font.green + joingreet + font.reset + ", statistics_commands " + font.green + statscmds + font.reset + ", games " + font.green + games + font.reset + ", chat " + font.green + chat + font.reset + ".")
+        return ('autojoin %s %s %s.' % font.green, autojoin, font.reset)
 
     def is_channelfunction(value):
         if value.lower() in ["autojoin", "aggressiveness", "join_greeting",  "statistics_commands",  "games", "chat"]:
@@ -88,7 +90,7 @@ class AdminHelpers():
         if function == "autojoin":
             message = "Join the channel automaticly after connecting."
         elif function == "aggressiveness":
-            message = "How to respond to kick, ban and mode events. Options: " + blue + "passive" + reset + ", " + blue + "defense_only" + reset + ", " + blue + "equal_retalliation" + reset + ", " + blue + "battlebot" + reset + "."
+            message = "How to respond to kick, ban and mode events. Options: " + font.blue + "passive" + font.reset + ", " + font.blue + "defense_only" + font.reset + ", " + font.blue + "equal_retalliation" + font.reset + ", " + font.blue + "battlebot" + font.reset + "."
         elif function == "join_greeting":
             message = "Greet users joining the channel on the first few joins, and later on large amounts."
         elif function == "statistics_commands":
@@ -200,18 +202,18 @@ class GameHelpers():
                 if ap < 0:
                     ap = 0
                 if sort == "level":
-                    message += red + str(record[0]) + reset + " [" + blue + "L " + green + str(level) + reset + ", X " + grey + str(xpspent) + "/" + green + str(int(xp)) + reset + ", A " + str(int(ap)) + ", C " + green + str(coin) + reset + ", K " + green + str(round(karma, 2)) + reset + "], "
+                    message += red + str(record[0]) + font.reset + " [" + font.blue + "L " + font.green + str(level) + font.reset + ", X " + font.grey + str(xpspent) + "/" + font.green + str(int(xp)) + font.reset + ", A " + str(int(ap)) + ", C " + font.green + str(coin) + font.reset + ", K " + font.green + str(round(karma, 2)) + font.reset + "], "
                 if sort == "xp_spent":
-                    message += red + str(record[0]) + reset + " [L " + green + str(level) + reset + ", " + blue + "X " + grey + str(xpspent) + "/" + green + str(int(xp)) + reset + ", A " + str(int(ap)) + ", C " + green + str(coin) + reset + ", K " + green + str(round(karma, 2)) + reset + "], "
+                    message += red + str(record[0]) + font.reset + " [L " + font.green + str(level) + font.reset + ", " + font.blue + "X " + font.grey + str(xpspent) + "/" + font.green + str(int(xp)) + font.reset + ", A " + str(int(ap)) + ", C " + font.green + str(coin) + font.reset + ", K " + font.green + str(round(karma, 2)) + font.reset + "], "
                 if sort == "coin":
-                    message += red + str(record[0]) + reset + " [L " + green + str(level) + reset + ", X " + grey + str(xpspent) + "/" + green + str(int(xp)) + reset + ", " + reset + "A " + str(int(ap)) + ", " + blue + "C " + green + str(coin) + reset + ", K " + green + str(round(karma, 2)) + reset + "], "
+                    message += red + str(record[0]) + font.reset + " [L " + font.green + str(level) + font.reset + ", X " + font.grey + str(xpspent) + "/" + font.green + str(int(xp)) + font.reset + ", " + font.reset + "A " + str(int(ap)) + ", " + font.blue + "C " + font.green + str(coin) + font.reset + ", K " + font.green + str(round(karma, 2)) + font.reset + "], "
         return message[:-2]
 
     def player_info(self, user):
         level, xp, xpspent, totalxp, karma, coin, coinspent, coingiven, ap, apspent = GameHelpers.get_info(self, user)
         if ap < 0:
             ap = 0
-        return "Level: " + str(level) + grey + ", " + reset + "XP: " + str(int(xp)) + "/" + grey + str(round(totalxp, 4)) + ", " + reset + "AP: " + str(int(ap)) + grey + ", " + reset + "coin: " + str(coin) + grey + "[S " + str(coinspent) + ", G " + str(coingiven) + "], " + reset + "karma: " + str(round(karma, 4))
+        return "Level: " + str(level) + font.grey + ", " + font.reset + "XP: " + str(int(xp)) + "/" + font.grey + str(round(totalxp, 4)) + ", " + font.reset + "AP: " + str(int(ap)) + font.grey + ", " + font.reset + "coin: " + str(coin) + font.grey + "[S " + str(coinspent) + ", G " + str(coingiven) + "], " + font.reset + "karma: " + str(round(karma, 4))
 
 class StatisticsHelpers():
     def add_message_stats(stats):

+ 188 - 181
rotbot/commands/games.py

@@ -1,32 +1,39 @@
 import random
-from common import font
+from common import font, queries
 from commands.common import CommandHelpers as CH
 from commands.common import GameHelpers
 
-def do_command(self, connection, event):
+def do_command(self, connection, event, user, channel):
     cmdtype, trigger, command, replyto = CH.disect_command(self, event)
 
+    # Do nothing if it's not a type of command.
+    if not cmdtype:
+        return
+
     # Do nothing if there is no command.
     if not command:
         return
+
+    # The first word of the command sting with arguments, is just the command without arguments.
     try:
-        command.split()[0]
+        one = command.split()[0]    # Get raw command.
     except:
         return
 
     # Do noting if the games channel function is off and it's a channel message.
-    if not self.db.one("SELECT games FROM channels WHERE name='" + event.target + "' AND network='" + self.network + "'") and not event.target == connection.get_nickname():
-        return
+    if event.target != connection.get_nickname():   # Command issued to channel.
+        if not queries.get_channel_setting_game_commands:   # Games are turned off in channel settigns.
+            return  # Do nothing.
 
-    if command == "cmd" or command == "cmds" or command == "commands":
-        if cmdtype == "cmd":
-            connection.privmsg(replyto, font.grey + "Games: " + CH.ccc(self, "8ball") + CH.ccc(self, "dice") + CH.ccc(self, "player") + CH.ccc(self, "players") + CH.ccc(self, "levelup") + CH.ccc(self, "givecoin")[:-2] + ".")
-            connection.privmsg(replyto, font.grey + "Game help: " + blue + self.helpchar + "level" + font.grey + ", " + blue + self.helpchar + "xp" + font.grey + ", " + blue + self.helpchar + "ap" + font.grey + ", " + blue + self.helpchar + "coin" + font.grey + ", " + blue + self.helpchar + "karma" + font.grey + ".")
+    if command == 'cmd' or command == 'cmds' or command == 'commands':
+        if cmdtype == 'cmd':
+            connection.privmsg(replyto, '%sGames: %s %s' % (font.grey, CH.ccc(self, "8ball"), CH.ccc(self, "dice")[:-2]))#, CH.ccc(self, "player"), CH.ccc(self, "players"), CH.ccc(self, "levelup") ,CH.ccc(self, "givecoin")[:-2]))
+            #connection.privmsg(replyto, font.grey + "Game help: " + font.blue + self.network.help_character + "level" + font.grey + ", " + font.blue + self.network.help_character + "xp" + font.grey + ", " + font.blue + self.network.help_character + "ap" + font.grey + ", " + font.blue + self.network.help_character + "coin" + font.grey + ", " + font.blue + self.network.help_character + "karma" + font.grey + ".")
 
     elif command.split()[0] == "8ball":
         if cmdtype == "help":    #Display help text.
             connection.privmsg(replyto, "Ask a question of the mighty and illusive 8-Ball.")
-            connection.privmsg(replyto, font.grey + "Usage: " + blue + self.cmdchar + "8ball " + font.reset + font.italic + "question")
+            connection.privmsg(replyto, font.grey + "Usage: " + font.blue + self.network.command_character + "8ball " + font.reset + font.italic + "question")
         elif cmdtype == "cmd":
 
             if len(command.split()) < 2:    # Command contains only !8ball.
@@ -37,7 +44,7 @@ def do_command(self, connection, event):
                     "You want me to predict nothing?",
                     "Are you intentionally not asking a question?",
                     "Ask a question you tease!",
-                    "You do not seem to grasp this, for help type: " + blue + self.helpchar + "8ball",
+                    "You do not seem to grasp this, for help type: " + font.blue + self.network.help_character + "8ball",
                     "You will die alone.",
                 ]
                 connection.privmsg(replyto, random.choice(messages))
@@ -73,8 +80,8 @@ def do_command(self, connection, event):
             if len(command.split()) != 1:
                 return
             connection.privmsg(replyto, "Rolls multiple dices of chosen type. Specifying type or amount is optional.")
-            connection.privmsg(replyto, font.grey + "Usage: " + blue + self.cmdchar + "dice " + font.reset + font.italic + "amount type")
-            connection.privmsg(replyto, font.grey + "Example: " + blue + self.cmdchar + "dice " + font.reset + font.italic + "2 d10")
+            connection.privmsg(replyto, font.grey + "Usage: " + font.blue + self.network.command_character + "dice " + font.reset + font.italic + "amount type")
+            connection.privmsg(replyto, font.grey + "Example: " + font.blue + self.network.command_character + "dice " + font.reset + font.italic + "2 d10")
             connection.privmsg(replyto, font.grey + "Note: " + font.reset + "The dice type is specified as a " + font.italic + "d " + font.reset + "followed by the amount of sides.")
         elif cmdtype == "cmd":
 
@@ -89,14 +96,14 @@ def do_command(self, connection, event):
                         try:
                             dicetype = int(command.split()[1][1:])
                         except ValueError:  # "d" not followd by interger.
-                            connection.privmsg(replyto, "Invalid number or type of dice. For help type: " + blue + self.helpchar + "dice" + font.reset + ".")
+                            connection.privmsg(replyto, "Invalid number or type of dice. For help type: " + font.blue + self.network.help_character + "dice" + font.reset + ".")
                             return
                         if dicetype < 1:
                             connection.action(replyto, "can not create objects with less then one side.")
                         else:
                             connection.privmsg(replyto, str(GameHelpers.roll_dice(1,  dicetype)[0]))
                     else:   # Argument does not start with "d" and is not an integer.
-                        connection.privmsg(replyto, "Invalid number or type of dice. For help type: " + blue + self.helpchar + "dice" + font.reset + ".")
+                        connection.privmsg(replyto, "Invalid number or type of dice. For help type: " + font.blue + self.network.help_character + "dice" + font.reset + ".")
                     return
                 if diceamount < 1:
                     connection.privmsg(replyto, "Rolling " + font.bold + "no " + font.reset + "dice.")
@@ -108,12 +115,12 @@ def do_command(self, connection, event):
                 try:
                     diceamount = int(command.split()[1])
                 except ValueError:  # First argument not an integer.
-                    connection.privmsg(replyto, "Invalid number of dice. For help type: " + blue + self.helpchar + "dice" + font.reset + ".")
+                    connection.privmsg(replyto, "Invalid number of dice. For help type: " + font.blue + self.network.help_character + "dice" + font.reset + ".")
                     return
                 try:
                     dicetype = int(command.split()[2][1:])
                 except ValueError:  # Second argument not a dice type.
-                    connection.privmsg(replyto, "Invalid type of dice. For help type: " + blue + self.helpchar + "dice" + font.reset + ".")
+                    connection.privmsg(replyto, "Invalid type of dice. For help type: " + font.blue + self.network.help_character + "dice" + font.reset + ".")
                     return
                 if diceamount < 1:
                     connection.privmsg(replyto, "Rolling " + font.bold + "no " + font.reset + "dice.")
@@ -126,176 +133,176 @@ def do_command(self, connection, event):
                 else:
                     connection.privmsg(replyto, ", ".join(str(rolls) for rolls in GameHelpers.roll_dice(diceamount,  dicetype)) + ".") # Roll x amount of x type dice.
             else:   # Invalid amount of arguments.
-                connection.privmsg(replyto, "Too many arguments. For help type: " + blue + self.helpchar + "dice" + font.reset + ".")
-
-    elif command.split()[0] == "player":
-        if cmdtype == "help":    #Display help text.
-            connection.privmsg(replyto, "Displays a users game statistics. User optional.")
-        elif cmdtype == "cmd":
-
-            if len(command.split()) == 1:
-                user = event.source.nick.lower()
-                message = font.grey + "Your statistics. " + reset
-            elif len(command.split()) == 2:
-                user = command.split()[1]
-                if not self.db.one("SELECT id FROM users WHERE LOWER(name)=%s AND network='" + self.network + "'", (user, )):
-                    connection.action(replyto, "does not know of a " + font.red + trigger.split()[1] + font.reset + ".")
-                    return
-                if user == connection.get_nickname().lower():
-                    connection.privmsg(replyto, "The game does not play the master.")
-                    return
-                if user == event.source.nick.lower():
-                    message = font.grey + "Your statistics. " + reset
-                else:
-                    message = font.grey + "Statistics for " + font.red + trigger.split()[1] + font.reset + ". "
-            else:
-                connection.privmsg(replyto, "Too many arguments, For help type " + blue + self.helpchar + "players " + font.reset + ".")
-                return
-
-            level, xp, xpspent, totalxp, karma, coin, coinspent, coingiven, ap, apspent = GameHelpers.get_info(self, user)
-            if ap < 0:
-                ap = 0
-            info = GameHelpers.player_info(self, user)
-            connection.privmsg(replyto, message + info)
-
-    elif command.split()[0] == "levelup":
-        user = event.source.nick.lower()
-        level, xp, xpspent, totalxp, karma, coin, coinspent, coingiven, ap, apspent = GameHelpers.get_info(self, user)
-        if cmdtype == "help":    #Display help text.
-            connection.privmsg(replyto, "Spend " + str(int(level * 2.7)) + " XP to gain your next level. Gain multiple levels (for the price of the lowest level) by specifying the amount.")
-        elif cmdtype == "cmd":
-            if len(command.split()) == 1:
-                if xp < int(level * 2.7):
-                    connection.privmsg(replyto, "Insufficient XP, you need at least " + str(int(level * 2.7)) + ".")
-                elif ap < 1:
-                    connection.privmsg(replyto, "Insufficient AP, you need at least 1.")
-                else:
-                    self.db.run("UPDATE users SET level=level+1, xp_spent=xp_spent+%s, ap_spent=ap_spent+1 WHERE LOWER(name)=%s AND network=%s", (int(level * 2.7), user, self.network, ))
-                    self.db.run("UPDATE users SET coin=coin+0.3 WHERE level>0 AND network=%s", (self.network, ))
-                    info = GameHelpers.player_info(self, user)
-                    connection.privmsg(replyto, "Your new statistics: " + info)
-            elif len(command.split()) == 2:
-                try:
-                    levels = int(command.split()[1])
-                except:
-                    connection.privmsg(replyto, "Invalid amount.")
-                    return
-                if levels < 1:
-                    connection.privmsg(replyto, "Invalid amount.")
-                elif xp < int(level * 2.7):
-                    connection.privmsg(replyto, "Insufficient XP, you need at least " + str(int(level * 2.7)) + ".")
-                elif ap < levels:
-                    connection.privmsg(replyto, "Insufficient AP, you need at least 1.")
-                else:
-                    self.db.run("UPDATE users SET level=level+%s, xp_spent=xp_spent+%s, ap_spent=ap_spent+%s WHERE LOWER(name)=LOWER(%s) AND network=%s", (levels, int(level * 2.7), levels, user, self.network, ))
-                    self.db.run("UPDATE users SET coin=coin+%s WHERE level>0 AND network=%s", (levels * 0.3, self.network, ))
-                    info = GameHelpers.player_info(self, user)
-                    connection.privmsg(replyto, "Your new statistics: " + info)
-            else:
-                connection.privmsg(replyto, "Too many arguments. For help type " + blue + self.helpchar + "levelup " + font.reset + ".")
-
-    elif command.split()[0] == "givecoin":
-        if cmdtype == "help":    #Display help text.
-            connection.privmsg(replyto, "Give coins to another player. Amount optional.")
-            connection.privmsg(replyto, font.grey + "Usage: " + blue + self.cmdchar + "givecoin " + font.reset + font.italic + "user amount")
-        elif cmdtype == "cmd":
+                connection.privmsg(replyto, "Too many arguments. For help type: " + font.blue + self.network.help_character + "dice" + font.reset + ".")
 
-            if len(command.split()) == 1:
-                connection.privmsg(replyto, "Insufficient arguments. For help type " + blue + self.helpchar + "givecoin" + font.reset + ".")
-                return
-            elif len(command.split()) > 3:
-                connection.privmsg(replyto, "Too many arguments. For help type " + blue + self.helpchar + "givecoin" + font.reset + ".")
-                return
-            elif command.split()[1] == event.source.nick.lower():
-                connection.privmsg(replyto, "You already have your own coin. For help type " + blue + self.helpchar + "givecoin" + font.reset + ".")
-                return
-            elif len(command.split()) == 3:
-                stop = False
-                try:
-                    if float(command.split()[2]) < 0:
-                        connection.privmsg(replyto, "You clever abuser! The " + font.red + self.cmdchar + font.bold + "give" + font.bold + "coin" + font.reset + " command is not designed for robbing. There will be consequences...")
-                        self.db.run("UPDATE users SET coin=coin-3, karma_correction=karma_correction-0.5 WHERE name=%s AND network=%s", (event.source.nick, self.network))
-                        stop = True
-                except TypeError:
-                    connection.privmsg(replyto, "Invalid amount. For help type " + blue + self.helpchar + "givecoin" + font.reset + ".")
-                    return
-                if stop:
-                    return
-            elif not event.target == connection.get_nickname():
-                if not command.split()[1] in self.channels[event.target].users():
-                    connection.privmsg(replyto, font.red + trigger.split()[1] + font.reset + " is not present.")
-                    return
-
-            level, xp, xpspent, totalxp, karma, coin, coinspent, coingiven, ap, apspent = GameHelpers.get_info(self, event.source.nick)
-            receivingrecord = self.db.one("SELECT level, away FROM users WHERE LOWER(name)=%s AND network=%s", (command.split()[1], self.network, ))
-            if level < 1:
-                connection.privmsg(replyto, "You need to " + blue + self.cmdchar + "levelup " + font.reset + "to be able to give coin.")
-                return
-            elif coin < 1:
-                connection.privmsg(replyto, "You have no coin to give.")
-                return
-            elif not receivingrecord:
-                connection.action(replyto, "does not know of any \"" + font.red + trigger.split()[1] + font.reset + "\".")
-                return
-            elif receivingrecord[0] == 0:
-                connection.privmsg(replyto, font.red + trigger.split()[1] + font.reset + " is not playing the game.")
-                return
-            elif receivingrecord[1] == True:
-                connection.privmsg(replyto, font.red + trigger.split()[1] + font.reset + "is not here right now.")
-                return
-            elif ap < 1:
-                connection.privmsg(replyto, "You have no action points, go use IRC some more...")
-                return
-            if len(command.split()) == 2:
-                self.db.run("UPDATE users SET coin=coin-1, coin_spent=coin_spent+1, coin_given=coin_given+1, ap_spent=ap_spent+1 WHERE name=%s AND network=%s", (event.source.nick, self.network, ))
-                self.db.run("UPDATE users SET coin=coin+1 WHERE LOWER(name)=%s AND network=%s", (command.split()[1], self.network, ))
-            elif len(command.split()) == 3:
-                self.db.run("UPDATE users SET coin=coin-%s, coin_spent=coin_spent+%s, coin_given=coin_given+%s, ap_spent=ap_spent+1 WHERE name=%s AND network=%s", (command.split()[2], command.split()[2], command.split()[2], event.source.nick, self.network, ))
-                self.db.run("UPDATE users SET coin=coin+%s WHERE LOWER(name)=%s AND network=%s", (command.split()[2], command.split()[1], self.network))
-            info = GameHelpers.player_info(self, event.source.nick)
-            connection.notice(event.source.nick, "Your new statistics: " + info)
-
-    elif command.split()[0] == "players":
-        if cmdtype == "help":    #Display help text.
-            connection.privmsg(replyto, "Display top player rankings.")
-        elif cmdtype == "cmd":
-
-            toplevel = GameHelpers.list_top_players(self, "level")
-            if toplevel:
-                connection.notice(event.source.nick, "Ranking level: " + toplevel)
-            topxp = GameHelpers.list_top_players(self, "xp_spent")
-            if topxp:
-                connection.notice(event.source.nick, "Ranking experience: " + topxp)
-            topcoin = GameHelpers.list_top_players(self, "coin")
-            if topcoin:
-                connection.notice(event.source.nick, "Ranking wealth: " + topcoin)
-            if not toplevel and not topxp and not topcoin:
-                connection.privmsg(replyto, "Nobody is playing the game.")
-
-    elif command.split()[0] == "level":
-        if cmdtype == "help":    #Display help text.
-            connection.privmsg(replyto, "For 10 XP you can use !levelup to gain a level. As your level increases, things become more difficult, while more capabilities and options are unlocked.")
-    elif command.split()[0] == "xp":
-        if cmdtype == "help":    #Display help text.
-            connection.privmsg(replyto, "XP is earned by using IRC and playing the game, in channels with " + font.red + connection.get_nickname() + font.reset + ". Ask any operator in " + font.red + self.homechannel + font.reset + " to add a channel. XP is used to level up, advance classes, as a limit and as a multiplier. Once XP is expended it keeps counting towards your total.")
-    elif command.split()[0] == "ap":
-        if cmdtype == "help":    #Display help text.
-            connection.privmsg(replyto, "AP is earned by using IRC and playing the game in channels with " + font.red + connection.get_nickname() + font.reset + ". Ask any operator in " + font.red + self.homechannel + font.reset + " to add a channel. AP is expended for every action you take in the game.")
-    elif command.split()[0] == "coin":
-        if cmdtype == "help":    #Display help text.
-            connection.privmsg(replyto, "Coin is earned when certain events occur, for instance when a player expends XP. Coin is used to buy items and classes. To give a player coin use " + blue + self.cmdchar + "givecoin" + font.reset + ". Coin affects karma.")
-    elif command.split()[0] == "karma":
-        if cmdtype == "help":    #Display help text.
-            connection.privmsg(replyto, "Karma is a secret formula, based upon on your IRC events and how you play the game. It does not increase like XP. Some events, skills and items are karma based.")
+    # elif command.split()[0] == "player":
+    #     if cmdtype == "help":    #Display help text.
+    #         connection.privmsg(replyto, "Displays a users game statistics. User optional.")
+    #     elif cmdtype == "cmd":
+    #
+    #         if len(command.split()) == 1:
+    #             user = event.source.nick.lower()
+    #             message = font.grey + "Your statistics. " + reset
+    #         elif len(command.split()) == 2:
+    #             user = command.split()[1]
+    #             if not self.db.one("SELECT id FROM users WHERE LOWER(name)=%s AND network='" + self.network + "'", (user, )):
+    #                 connection.action(replyto, "does not know of a " + font.red + trigger.split()[1] + font.reset + ".")
+    #                 return
+    #             if user == connection.get_nickname().lower():
+    #                 connection.privmsg(replyto, "The game does not play the master.")
+    #                 return
+    #             if user == event.source.nick.lower():
+    #                 message = font.grey + "Your statistics. " + reset
+    #             else:
+    #                 message = font.grey + "Statistics for " + font.red + trigger.split()[1] + font.reset + ". "
+    #         else:
+    #             connection.privmsg(replyto, "Too many arguments, For help type " + font.blue + self.network.help_character + "players " + font.reset + ".")
+    #             return
+    #
+    #         level, xp, xpspent, totalxp, karma, coin, coinspent, coingiven, ap, apspent = GameHelpers.get_info(self, user)
+    #         if ap < 0:
+    #             ap = 0
+    #         info = GameHelpers.player_info(self, user)
+    #         connection.privmsg(replyto, message + info)
+    #
+    # elif command.split()[0] == "levelup":
+    #     user = event.source.nick.lower()
+    #     level, xp, xpspent, totalxp, karma, coin, coinspent, coingiven, ap, apspent = GameHelpers.get_info(self, user)
+    #     if cmdtype == "help":    #Display help text.
+    #         connection.privmsg(replyto, "Spend " + str(int(level * 2.7)) + " XP to gain your next level. Gain multiple levels (for the price of the lowest level) by specifying the amount.")
+    #     elif cmdtype == "cmd":
+    #         if len(command.split()) == 1:
+    #             if xp < int(level * 2.7):
+    #                 connection.privmsg(replyto, "Insufficient XP, you need at least " + str(int(level * 2.7)) + ".")
+    #             elif ap < 1:
+    #                 connection.privmsg(replyto, "Insufficient AP, you need at least 1.")
+    #             else:
+    #                 self.db.run("UPDATE users SET level=level+1, xp_spent=xp_spent+%s, ap_spent=ap_spent+1 WHERE LOWER(name)=%s AND network=%s", (int(level * 2.7), user, self.network, ))
+    #                 self.db.run("UPDATE users SET coin=coin+0.3 WHERE level>0 AND network=%s", (self.network, ))
+    #                 info = GameHelpers.player_info(self, user)
+    #                 connection.privmsg(replyto, "Your new statistics: " + info)
+    #         elif len(command.split()) == 2:
+    #             try:
+    #                 levels = int(command.split()[1])
+    #             except:
+    #                 connection.privmsg(replyto, "Invalid amount.")
+    #                 return
+    #             if levels < 1:
+    #                 connection.privmsg(replyto, "Invalid amount.")
+    #             elif xp < int(level * 2.7):
+    #                 connection.privmsg(replyto, "Insufficient XP, you need at least " + str(int(level * 2.7)) + ".")
+    #             elif ap < levels:
+    #                 connection.privmsg(replyto, "Insufficient AP, you need at least 1.")
+    #             else:
+    #                 self.db.run("UPDATE users SET level=level+%s, xp_spent=xp_spent+%s, ap_spent=ap_spent+%s WHERE LOWER(name)=LOWER(%s) AND network=%s", (levels, int(level * 2.7), levels, user, self.network, ))
+    #                 self.db.run("UPDATE users SET coin=coin+%s WHERE level>0 AND network=%s", (levels * 0.3, self.network, ))
+    #                 info = GameHelpers.player_info(self, user)
+    #                 connection.privmsg(replyto, "Your new statistics: " + info)
+    #         else:
+    #             connection.privmsg(replyto, "Too many arguments. For help type " + font.blue + self.network.help_character + "levelup " + font.reset + ".")
+    #
+    # elif command.split()[0] == "givecoin":
+    #     if cmdtype == "help":    #Display help text.
+    #         connection.privmsg(replyto, "Give coins to another player. Amount optional.")
+    #         connection.privmsg(replyto, font.grey + "Usage: " + font.blue + self.network.command_character + "givecoin " + font.reset + font.italic + "user amount")
+    #     elif cmdtype == "cmd":
+    #
+    #         if len(command.split()) == 1:
+    #             connection.privmsg(replyto, "Insufficient arguments. For help type " + font.blue + self.network.help_character + "givecoin" + font.reset + ".")
+    #             return
+    #         elif len(command.split()) > 3:
+    #             connection.privmsg(replyto, "Too many arguments. For help type " + font.blue + self.network.help_character + "givecoin" + font.reset + ".")
+    #             return
+    #         elif command.split()[1] == event.source.nick.lower():
+    #             connection.privmsg(replyto, "You already have your own coin. For help type " + font.blue + self.network.help_character + "givecoin" + font.reset + ".")
+    #             return
+    #         elif len(command.split()) == 3:
+    #             stop = False
+    #             try:
+    #                 if float(command.split()[2]) < 0:
+    #                     connection.privmsg(replyto, "You clever abuser! The " + font.red + self.network.command_character + font.bold + "give" + font.bold + "coin" + font.reset + " command is not designed for robbing. There will be consequences...")
+    #                     self.db.run("UPDATE users SET coin=coin-3, karma_correction=karma_correction-0.5 WHERE name=%s AND network=%s", (event.source.nick, self.network))
+    #                     stop = True
+    #             except TypeError:
+    #                 connection.privmsg(replyto, "Invalid amount. For help type " + font.blue + self.network.help_character + "givecoin" + font.reset + ".")
+    #                 return
+    #             if stop:
+    #                 return
+    #         elif not event.target == connection.get_nickname():
+    #             if not command.split()[1] in self.channels[event.target].users():
+    #                 connection.privmsg(replyto, font.red + trigger.split()[1] + font.reset + " is not present.")
+    #                 return
+    #
+    #         level, xp, xpspent, totalxp, karma, coin, coinspent, coingiven, ap, apspent = GameHelpers.get_info(self, event.source.nick)
+    #         receivingrecord = self.db.one("SELECT level, away FROM users WHERE LOWER(name)=%s AND network=%s", (command.split()[1], self.network, ))
+    #         if level < 1:
+    #             connection.privmsg(replyto, "You need to " + font.blue + self.network.command_character + "levelup " + font.reset + "to be able to give coin.")
+    #             return
+    #         elif coin < 1:
+    #             connection.privmsg(replyto, "You have no coin to give.")
+    #             return
+    #         elif not receivingrecord:
+    #             connection.action(replyto, "does not know of any \"" + font.red + trigger.split()[1] + font.reset + "\".")
+    #             return
+    #         elif receivingrecord[0] == 0:
+    #             connection.privmsg(replyto, font.red + trigger.split()[1] + font.reset + " is not playing the game.")
+    #             return
+    #         elif receivingrecord[1] == True:
+    #             connection.privmsg(replyto, font.red + trigger.split()[1] + font.reset + "is not here right now.")
+    #             return
+    #         elif ap < 1:
+    #             connection.privmsg(replyto, "You have no action points, go use IRC some more...")
+    #             return
+    #         if len(command.split()) == 2:
+    #             self.db.run("UPDATE users SET coin=coin-1, coin_spent=coin_spent+1, coin_given=coin_given+1, ap_spent=ap_spent+1 WHERE name=%s AND network=%s", (event.source.nick, self.network, ))
+    #             self.db.run("UPDATE users SET coin=coin+1 WHERE LOWER(name)=%s AND network=%s", (command.split()[1], self.network, ))
+    #         elif len(command.split()) == 3:
+    #             self.db.run("UPDATE users SET coin=coin-%s, coin_spent=coin_spent+%s, coin_given=coin_given+%s, ap_spent=ap_spent+1 WHERE name=%s AND network=%s", (command.split()[2], command.split()[2], command.split()[2], event.source.nick, self.network, ))
+    #             self.db.run("UPDATE users SET coin=coin+%s WHERE LOWER(name)=%s AND network=%s", (command.split()[2], command.split()[1], self.network))
+    #         info = GameHelpers.player_info(self, event.source.nick)
+    #         connection.notice(event.source.nick, "Your new statistics: " + info)
+    #
+    # elif command.split()[0] == "players":
+    #     if cmdtype == "help":    #Display help text.
+    #         connection.privmsg(replyto, "Display top player rankings.")
+    #     elif cmdtype == "cmd":
+    #
+    #         toplevel = GameHelpers.list_top_players(self, "level")
+    #         if toplevel:
+    #             connection.notice(event.source.nick, "Ranking level: " + toplevel)
+    #         topxp = GameHelpers.list_top_players(self, "xp_spent")
+    #         if topxp:
+    #             connection.notice(event.source.nick, "Ranking experience: " + topxp)
+    #         topcoin = GameHelpers.list_top_players(self, "coin")
+    #         if topcoin:
+    #             connection.notice(event.source.nick, "Ranking wealth: " + topcoin)
+    #         if not toplevel and not topxp and not topcoin:
+    #             connection.privmsg(replyto, "Nobody is playing the game.")
+    #
+    # elif command.split()[0] == "level":
+    #     if cmdtype == "help":    #Display help text.
+    #         connection.privmsg(replyto, "For 10 XP you can use !levelup to gain a level. As your level increases, things become more difficult, while more capabilities and options are unlocked.")
+    # elif command.split()[0] == "xp":
+    #     if cmdtype == "help":    #Display help text.
+    #         connection.privmsg(replyto, "XP is earned by using IRC and playing the game, in channels with " + font.red + connection.get_nickname() + font.reset + ". Ask any operator in " + font.red + self.homechannel + font.reset + " to add a channel. XP is used to level up, advance classes, as a limit and as a multiplier. Once XP is expended it keeps counting towards your total.")
+    # elif command.split()[0] == "ap":
+    #     if cmdtype == "help":    #Display help text.
+    #         connection.privmsg(replyto, "AP is earned by using IRC and playing the game in channels with " + font.red + connection.get_nickname() + font.reset + ". Ask any operator in " + font.red + self.homechannel + font.reset + " to add a channel. AP is expended for every action you take in the game.")
+    # elif command.split()[0] == "coin":
+    #     if cmdtype == "help":    #Display help text.
+    #         connection.privmsg(replyto, "Coin is earned when certain events occur, for instance when a player expends XP. Coin is used to buy items and classes. To give a player coin use " + font.blue + self.network.command_character + "givecoin" + font.reset + ". Coin affects karma.")
+    # elif command.split()[0] == "karma":
+    #     if cmdtype == "help":    #Display help text.
+    #         connection.privmsg(replyto, "Karma is a secret formula, based upon on your IRC events and how you play the game. It does not increase like XP. Some events, skills and items are karma based.")
 
 #    elif command.split()[0] == "classup":
 #        if cmdtype == "help":    #Display help text.
-#            connection.privmsg(replyto, "Spend 10 XP to gain a class in your current level. List available classes with " + blue + self.helpchar + "classup available " + font.reset + ".")
-#            connection.privmsg(replyto, font.grey + "Usage: " + blue + self.cmdchar + "classup " + font.reset + font.italic + "class")
+#            connection.privmsg(replyto, "Spend 10 XP to gain a class in your current level. List available classes with " + font.blue + self.network.help_character + "classup available " + font.reset + ".")
+#            connection.privmsg(replyto, font.grey + "Usage: " + font.blue + self.network.command_character + "classup " + font.reset + font.italic + "class")
 #        elif cmdtype == "cmd":
 #
 #            if len(command.split()) == 1:
-#                connection.privmsg(replyto, "Insufficient arguments. For help type " + blue + self.helpchar + "classup" + font.reset + ".")
+#                connection.privmsg(replyto, "Insufficient arguments. For help type " + font.blue + self.network.help_character + "classup" + font.reset + ".")
 #            if len(command.split()) == 2:
 #                if command.split()[1] == "available":
 #                    level, xp, xpspent, karma = GameHelpers.get_info(self, event.source.nick)
@@ -313,4 +320,4 @@ def do_command(self, connection, event):
 #                        connection.privmsg(replyto, "Level 4 classes: ")
 #                else:
 #            else:
-#                connection.privmsg(replyto, "Too many arguments. For help type " + blue + self.helpchar + "classup" + font.reset + ".")
+#                connection.privmsg(replyto, "Too many arguments. For help type " + font.blue + self.network.help_character + "classup" + font.reset + ".")

+ 23 - 26
rotbot/commands/public.py

@@ -1,7 +1,7 @@
 from common import userstatus, queries, font
 from commands.common import CommandHelpers as CH
 
-def do_command(self, connection, event):
+def do_command(self, connection, event, user, channel):
     cmdtype, trigger, command, replyto = CH.disect_command(self, event)
 
     # Do nothing if it's not a type of command.
@@ -29,25 +29,22 @@ def do_command(self, connection, event):
     #         connection.privmsg(replyto, str(self.channels[self.homechannel].owners()))
     #         connection.privmsg(event.source.nick, self.channels[event.target].users())
 
-    if command == "cmd" or command == "cmds" or command == "commands":
-        if cmdtype == "help":    #Display help text.
-            if len(command.split()) != 1:
+    if command == 'cmd' or command == 'cmds' or command == 'commands':
+        if len(command.split()) == 1:   # No arguments.
+            if cmdtype == 'help':    #Display help text.
+                connection.privmsg(replyto, 'Displays a list of commands, usage: %s%s%s' % (font.blue, self.network.command_character, command))
                 return
-            connection.privmsg(replyto, "Displays a list of commands.")
-        elif cmdtype == "cmd":
-            if not event.target == connection.get_nickname() and not self.db.one("SELECT join_greeting FROM channels WHERE name='" + event.target + "' AND network='" + self.network + "'"):
-                connection.privmsg(replyto, grey + "Public: " + CH.ccc(self, "cmd") + CH.ccc(self, "help"))# + CH.ccc(self, "away")[:-2] + ".")
-            else:
-                connection.privmsg(replyto, grey + "Public: " + CH.ccc(self, "cmd") + CH.ccc(self, "help"))# + CH.ccc(self, "away") + CH.ccc(self, "stopgreet")[:-2] + ".")
+            elif cmdtype == 'cmd':
+                connection.privmsg(replyto, '%sPublic: %s %s' % (font.grey, CH.ccc(self, "cmd"), CH.ccc(self, "help")[:-2]))# + CH.ccc(self, "away") + CH.ccc(self, "stopgreet")[:-2] + ".")
 
-    elif command == "help":
-        if cmdtype == "help":    #Display help text.
+    if command == 'help':
+        if cmdtype == 'help':    #Display help text.
             if len(command.split()) != 1:
                 return
-            connection.privmsg(replyto, "Explains how to get help on any specific command and hints the user to the commandlist.")
-        elif cmdtype == "cmd":
-            connection.privmsg(replyto, "Replace the " + font.italic + "! " + reset + "prefix of any comand with " + font.italic + self.helpchar + " " + reset + "for help with a specific command. Request the command list with: " + blue + "!cmd")
-            connection.privmsg(replyto, grey + "Example: " + reset + blue + self.helpchar + "help")
+            connection.privmsg(replyto, 'Explains how to get help on any specific command and hints the user to the commandlist. Example: %s%s%s' % (font.blue, self.network.command_character, command))
+        elif cmdtype == 'cmd':
+            connection.privmsg(replyto, "Replace the " + font.italic + self.network.command_character + " " + font.reset + "prefix of any comand with " + font.italic + self.network.help_character + " " + font.reset + "for help with a specific command. Request the command list with: " + font.blue + self.network.command_character + "cmd")
+            connection.privmsg(replyto, font.grey + "Example: " + font.reset + font.blue + self.network.help_character + "help")
 
 
     # elif one == "stopgreet":
@@ -55,7 +52,7 @@ def do_command(self, connection, event):
     #         if len(command.split()) != 1:
     #             return
     #         connection.privmsg(replyto, "Stops the bot from greeting you or a specific user. Channel, user and option to resume optional.")
-    #         connection.privmsg(replyto, grey + "Usage: " + blue + "!stopgreet " + reset + font.italic + "resume " + font.red + "channel user")
+    #         connection.privmsg(replyto, font.grey + "Usage: " + font.blue + "!stopgreet " + font.reset + font.italic + "resume " + font.red + "channel user")
     #     elif cmdtype == "cmd":
     #
     #         # Check for resume variation of command.
@@ -69,7 +66,7 @@ def do_command(self, connection, event):
     #
     #         if len(command.split()) == 1:    # No arguments.
     #             if event.target == connection.get_nickname():   # PM.
-    #                 connection.privmsg(replyto, "Specify at least a channel. For help type " + blue + self.helpchar + "stopgreet" + reset + ".")
+    #                 connection.privmsg(replyto, "Specify at least a channel. For help type " + font.blue + self.network.help_character + "stopgreet" + font.reset + ".")
     #                 return
     #             user = event.source.nick
     #             channel = event.target
@@ -77,11 +74,11 @@ def do_command(self, connection, event):
     #         if len(command.split()) == 2:    # One argument.
     #             if command.split()[1] not in self.channels: # Argument is not a channel the bot inhabits.
     #                 if event.target == connection.get_nickname():   # PM.
-    #                     connection.action(replyto, "does not inhabit " + font.red + command.split()[1] + reset + ".")
+    #                     connection.action(replyto, "does not inhabit " + font.red + command.split()[1] + font.reset + ".")
     #                     return
     #                 # Channel message
     #                 if not userstatus.atleast_halfop(self, event.source.nick, self.homechannel) and not userstatus.atleast_halfop(self, event.source.nick, event.target):  # Insufficient rights.
-    #                     connection.privmsg(replyto, "Denied. You need to have at least halfop status in " + font.red + self.homechannel + reset + " or " + font.red + event.target + reset + ".")
+    #                     connection.privmsg(replyto, "Denied. You need to have at least halfop status in " + font.red + self.homechannel + font.reset + " or " + font.red + event.target + font.reset + ".")
     #                     return
     #                 # Stopgreet for x user in current channel
     #                 user = command.split()[1]
@@ -91,21 +88,21 @@ def do_command(self, connection, event):
     #                 channel = command.split()[1]
     #         if len(command.split()) == 3:    # Two arguments.
     #             if command.split()[1] not in self.channels: # Bot does not inhabit channel.
-    #                 connection.action(replyto, "does not inhabit " + font.red + command.split()[1] + reset + ".")
+    #                 connection.action(replyto, "does not inhabit " + font.red + command.split()[1] + font.reset + ".")
     #                 return
     #             if not userstatus.atleast_halfop(self, event.source.nick, self.homechannel) and not userstatus.atleast_halfop(self, event.source.nick, command.split()[1]):  # Insufficient rights.
-    #                 connection.privmsg(replyto, "Denied. You need to have at least halfop status in " + font.red + self.homechannel + reset + " or " + font.red + command.split()[1] + reset + ".")
+    #                 connection.privmsg(replyto, "Denied. You need to have at least halfop status in " + font.red + self.homechannel + font.reset + " or " + font.red + command.split()[1] + font.reset + ".")
     #                 return
     #             user = command.split()[2]
     #             channel = command.split()[1]
     #         if len(command.split()) > 3:    # Too many arguments.
-    #             connection.privmsg(replyto, "Too many arguments. For help type " + blue + self.helpchar + "stopgreet" + reset + ".")
+    #             connection.privmsg(replyto, "Too many arguments. For help type " + font.blue + self.network.help_character + "stopgreet" + font.reset + ".")
     #             return
     #
     #         # Check for database record.
     #         result = self.db.one("SELECT id, stopgreet FROM joins WHERE LOWER(\"user\")=LOWER('" + user + "') AND user_network='" + self.network + "' AND LOWER(channel)=LOWER('" + channel + "') AND channel_network='" + self.network + "'")
     #         if not result:  # No record in database.
-    #             connection.action(replyto, "has not yet had the pleasure of greeting " + font.red + user + reset + " in " + font.red + channel + reset + ".")
+    #             connection.action(replyto, "has not yet had the pleasure of greeting " + font.red + user + font.reset + " in " + font.red + channel + font.reset + ".")
     #             return
     #
     #         if resume:
@@ -115,7 +112,7 @@ def do_command(self, connection, event):
     #             stopgreet = True
     #             message = "has already stopped greeting "
     #         if result[1] == stopgreet:
-    #             connection.action(replyto, message + font.red + user + reset + " in " + font.red + channel + reset + ".")
+    #             connection.action(replyto, message + font.red + user + font.reset + " in " + font.red + channel + font.reset + ".")
     #             return
     #         print(str(stopgreet) + str(user) + str(channel))
     #         try:
@@ -128,7 +125,7 @@ def do_command(self, connection, event):
     #         if len(command.split()) != 1:
     #             return
     #         connection.privmsg(replyto, "Sets you away, optionally with reason. This affects the !seen command and the game.")
-    #         connection.privmsg(replyto, grey + "Usage: " + blue + "!away " + reset + font.italic + "reason")
+    #         connection.privmsg(replyto, font.grey + "Usage: " + font.blue + "!away " + font.reset + font.italic + "reason")
     #
     #     elif cmdtype == "cmd":
     #         queries.create_ifnot_onrecord(self, "users", event.source.nick)

+ 249 - 214
rotbot/commands/statistics.py

@@ -1,9 +1,9 @@
 from datetime import datetime
-from common import font
+from common import font, queries
 from commands.common import CommandHelpers as CH, StatisticsHelpers
 
 
-def do_command(self, connection, event):
+def do_command(self, connection, event, user, channel):
     cmdtype, trigger, command, replyto = CH.disect_command(self, event)
 
     # Do nothing if it's not a type of command.
@@ -20,224 +20,259 @@ def do_command(self, connection, event):
     except:
         return
 
-    # Do noting if the games channel function is off and it's a channel message.
-    if not self.db.one("SELECT statistics_commands FROM channels WHERE name='" + event.target + "' AND network='" + self.network + "'") and not event.target == connection.get_nickname():
-        return
-
-    if command == "cmd" or command == "cmds" or command == "commands":
-        if cmdtype == "cmd":
-            connection.privmsg(replyto, grey + "Statistics: " + CH.ccc(self, "seen") + CH.ccc(self, "joins") + CH.ccc(self, "kicks") + CH.ccc(self, "messages") + CH.ccc(self, "actions") + CH.ccc(self, "notices")[:-2] + ".")
+    # Do noting if the statistics channel function is off and it's a channel message.
+    if not event.target == connection.get_nickname():
+        if not queries.get_channel_setting_statistic_commands(self, event.target):
+            return
 
+    if command == 'cmd' or command == 'cmds' or command == 'commands':
+        if cmdtype == 'cmd':
+            connection.privmsg(replyto, '%sStatistics: %s' % (font.grey, CH.ccc(self, "stat")[:-2] + ".")) #CH.ccc(self, "seen") + CH.ccc(self, "joins") + CH.ccc(self, "kicks") + CH.ccc(self, "messages") + CH.ccc(self, "actions") + CH.ccc(self, "notices")[:-2] + "."))
 
-    elif command.split()[0] == "joins" or command.split()[0] == "kicks" or command.split()[0] == "messages" or command.split()[0] == "actions" or command.split()[0] == "notices":
-        if cmdtype == "help":    #Display help text.
-            if len(command.split()) != 1:
-                return
-            connection.privmsg(replyto, "Display amount of " + command.split()[0] + " of user and channel. Channel and user optional.")
-            connection.privmsg(replyto, grey + "Usage: " + blue + self.cmdchar + command.split()[0] + " " + font.reset + font.italic + "channel user")
-        elif cmdtype == "cmd":
 
-            # Parse user input
-            user = event.source.nick
-            channel = None
-            channelonly= False
-            if not connection.get_nickname() == event.target:   # Channel message.
-                channel = event.target
-            if len(command.split()) == 1:    # Command contains only !joins.
-                user = event.source.nick
-            elif len(command.split()) == 2:  # Command has one argument.
-                if command.split()[1] in self.channels:
-                    channel = command.split()[1]
-                    if connection.get_nickname() == event.target:   # Private message.
-                        channelonly = True
+    elif one == 'stat' or one == 'stats' or one == 'statistic' or one == 'statistics':
+        if len(command.split()) == 1:   # Command without arguments.
+            if cmdtype == 'help':
+                connection.privmsg(replyto, 'Serves a link with statistics on a channel or username. Channel and user arguments are optional')
+                connection.privmsg(replyto, 'Usage %s%s%s %s%schannel' % (font.blue, self.network.command_character, one, font.reset, font.italic))
+                if channel: # Don't advertise our channel on other channels.
+                    connection.privmsg(replyto, '%sExample %s%s%s %s%s' % (font.grey, font.reset, event.target, one, self.network.home_channel))
                 else:
-                    user = trigger.split()[1]
-            elif len(command.split()) == 3: # Command has two arguments.
-                if not command.split()[1] in self.channels: # Bot does not inhabit requested channel.
-                    if not command.split()[2] in self.channels: # User did not revert channel and user in command syntax.
-                        connection.action(replyto, "does not inhabit " + font.red + command.split()[1] + font.reset + ".")
-                        return
-                    else:   # User reverted user and channel in command syntax.
-                        user = trigger.split()[1]
-                        channel = command.split()[2]
-                else: # Bot does inhabit requested channel.
-                    user = trigger.split()[2]
-                    channel = trigger.split()[1]
-            elif len(command.split()) < 5:  # To many arguments
-                connection.privmsg(replyto, "To many arguments. For help type " + blue + self.helpchar + "joins" + font.reset + ".")
-                return
+                    connection.privmsg(replyto, '%sExample %s%s%s %s%s' % (font.grey, font.reset, self.network.command_character, one, self.network.home_channel))
+            else:   # Actual command with no aruments.
+                if not channel:   # Command sent directly to bot
+                    connection.privmsg(replyto, '%sYour statistics:%s %suser/%s' % (font.grey, font.reset, self.webgui.base_url, user.slug))
+                else:   # Command sent to channel
+                    connection.privmsg(replyto, '%sChannel statistics:%s %channel/%s %sYour statistics:%s %suser/%s' % (font.grey, font.reset, self.webgui.base_url, channel.slug, font.grey, font.reset, self.webgui.base_url, user.slug))
 
-            if command.split()[0] == "joins":
-                if channel: # User and channel.
-                    userstat = str(sum(self.db.all("SELECT " + command.split()[0] + " FROM " + command.split()[0] + " WHERE channel_network='" + self.network + "' AND LOWER(\"user\")=LOWER('" + user + "') AND user_network='" + self.network + "'")))
-                    userchannelstat = str(self.db.one("SELECT " + command.split()[0] + " FROM " + command.split()[0] + " WHERE LOWER(channel)=LOWER('" + channel + "') AND channel_network='" + self.network + "' AND LOWER(\"user\")=LOWER('" + user + "') AND user_network='" + self.network + "'"))
-                    channelstat = str(sum(self.db.all("SELECT " + command.split()[0] + " FROM " + command.split()[0] + " WHERE LOWER(channel)=LOWER('" + channel + "') AND channel_network='" + self.network + "' AND user_network='" + self.network + "'")))
-                    if userchannelstat == "None":
-                        userchannelstat = "0"
-                    if channelonly:
-                        connection.privmsg(replyto, font.red + channel + font.reset + " has " + green + channelstat + font.reset + " " + command.split()[0] + font.reset + " in total.")
-                    elif userstat == "0" and not channelonly: # No user joins on record.
-                        connection.action(replyto, "has no record of any joins by " + font.red + user + font.reset + " in " + font.red + channel + font.reset + ".")
-                    else:   # User joins on record.
-                        connection.privmsg(replyto, font.red + user + font.reset + " has " + green + userstat + font.reset + " " + command.split()[0] + ". Of which " + green + userchannelstat + font.reset + " have been in " + font.red + channel + font.reset + ", that has " + green + channelstat + font.reset + " " + command.split()[0] + font.reset + " in total.")
-                else:   # Only user.
-                    userstat = str(sum(self.db.all("SELECT " + command.split()[0] + " FROM " + command.split()[0] + " WHERE channel_network='" + self.network + "' AND LOWER(\"user\")=LOWER('" + user + "') AND user_network='" + self.network + "'")))
-                    if userstat == "0":    # No statistics on user.
-                        connection.action(replyto, "has no record of any joins by " + font.red + user + font.reset + ".")
-                    else:   # Got statistics on user.
-                        connection.privmsg(replyto, font.red + user + font.reset + " has " + green + userstat + font.reset + " " + command.split()[0] + " in channels I monitor.")
-            elif command.split()[0] == "kicks":
-                if channel: # User and channel.
-                    try:
-                        givenkicks, receivedkicks = self.db.one("SELECT given, received FROM " + command.split()[0] + " WHERE LOWER(channel)=LOWER('" + channel + "') AND channel_network='" + self.network + "' AND LOWER(\"user\")=LOWER('" + user + "') AND user_network='" + self.network + "'")
-                    except:
-                        givenkicks = 0
-                        receivedkicks = 0
-                    if channelonly:
-                        connection.privmsg(replyto, font.red + channel + font.reset + " has " + green + channelstat + font.reset + " " + command.split()[0] + font.reset + " in total.")
-                    elif givenkicks == 0 and receivedkicks == 0:  # No kicks on record.
-                        connection.action(replyto, "has no record of any kicks for " + font.red + user + font.reset + " in " + font.red + channel + font.reset + ".")
-                    else:   # Kciks on record.
-                        channelkicks = self.db.all("SELECT given FROM " + command.split()[0] + " WHERE LOWER(channel)=LOWER('" + channel + "') AND channel_network='" + self.network + "'")
-                        connection.privmsg(replyto, font.red + user + font.reset + " has kicked " + green + str(givenkicks) + font.reset + " and been kicked " + green + str(receivedkicks) + font.reset + " times in " + font.red + channel + font.reset + ", where were " + green + str(sum(channelkicks)) + font.reset + " kicks in total.")
-                else:   # Only user.
-                    userstat = self.db.all("SELECT given, received FROM kicks WHERE LOWER(\"user\")=LOWER('" + user + "') AND user_network='" + self.network + "'")
-                    kicksgiven = 0
-                    kicksreceived = 0
-                    for record in userstat:
-                        kicksgiven += record[0]
-                        kicksreceived += record[1]
-                    if kicksgiven == 0 and kicksreceived == 0:
-                        connection.action(replyto, font.red + user + font.reset + " has no record of any kicks for " + font.red + user + font.reset + " in " + font.red + channel + font.reset + ".")
-                    else:
-                        connection.privmsg(replyto, font.red + user + font.reset + " has given " + green + str(kicksgiven) + font.reset + " and received " + green + str(kicksreceived) + font.reset + " kicks")
-            elif command.split()[0] == "messages" or command.split()[0] == "actions" or command.split()[0] == "notices":
-                userstat = self.db.all("SELECT " + command.split()[0] + ", " + command.split()[0] + "_words, " + command.split()[0] + "_characters FROM messages WHERE LOWER(\"user\")=LOWER('" + user + "') AND user_network='" + self.network + "'")
+        elif len(command.split()) == 2:   # Command with one argument.
+            if command.split()[1] in self.channels: # Argument matches a inhabited channel.
+                if cmdtype == 'help':
+                    connection.privmsg(replyto, 'Send a link for statistics on channel: %s%s' % ( font.red, command.split()[1]))
+                else:   # Actual command with one argument
+                    channel_slug = queries.get_channel_slug(self, command.split()[1])
+                    connection.privmsg(replyto, '%sStatistics on channel %s%s%s:%s %schannel/%s' % (font.grey, font.red, command.split()[1], font.grey, font.reset, self.webgui.base_url, channel_slug))
+            else:   # Argument does not match a channel.
+                user_slug = queries.get_user_slug(self, command.split()[1])
+                if user:
+                    connection.privmsg(replyto, '%sStatistics on nickname %s%s%s:%s %s/user/$s' % (font.grey, font.red, command.split()[1], font.grey, font.reset, self.webgui.base_url, user_slug))
+                else:   # Argument does not match a channel or user.
+                    connection.privmsg(replyto, 'I have not had the pleasure of being aquinted with: %s%s' % (font.red, command.split()[1]))
 
-                userrecord = False
-                for record in userstat:
-                    if not record[0] == 0:
-                        userrecord = True
+        elif len(command.split()) >= 3: # Too many arguments.
+            connection.privmsg('Too many arguments, for help type: %s%s%s' % (font.blue, self.network.command_character, one))
 
-                if not userrecord:
-                    if command.split()[0] == "messages":
-                        connection.action(replyto, "has no record of " + font.red + user + font.reset + " speaking.")
-                        return
-                    if command.split()[0] == "actions":
-                        connection.action(replyto, "has no record of " + font.red + user + font.reset + " acting.")
-                        return
-                    if command.split()[0] == "notices":
-                        connection.action(replyto, "has not noticed " + font.red + user + font.reset + ".")
-                        return
-                messages, words, characters = StatisticsHelpers.add_message_stats(userstat)
-                message = "Totals of " + font.red + user + " " + green + str(messages) + font.reset + " " + blue + command.split()[0] + font.reset + ", " + green + str(words) + font.reset + " words, " + green + str(characters) + font.reset + " characters."
-                if channel: # User and channel.
-                    userchanstat = self.db.one("SELECT " + command.split()[0] + ", " + command.split()[0] + "_words, " + command.split()[0] + "_characters FROM messages WHERE lower(channel)=lower('" + channel + "') AND channel_network='" + self.network + "' AND lower(\"user\")=lower('" + user + "') AND user_network='" + self.network + "'")
-                    chanstat = self.db.all("SELECT " + command.split()[0] + ", " + command.split()[0] + "_words, " + command.split()[0] + "_characters FROM messages WHERE lower(channel)=lower('" + channel + "') AND channel_network='" + self.network + "'")
-                    if not userchanstat[0] == 0:
-                        message += " " + font.red + user + font.reset + " in " + font.red + channel + " " + green + str(userchanstat[0]) + font.reset + " " + blue + command.split()[0] + font.reset + ", " + green + str(userchanstat[1]) + font.reset + " words, " + green + str(userchanstat[2]) + font.reset + " chars."
-                    channelrecord = False
-                    for record in chanstat:
-                        if not record[0] == 0:
-                            channelrecord = True
-                    if channelrecord:
-                        messages, words, characters = StatisticsHelpers.add_message_stats(chanstat)
-                        message += " Total in " + font.red + channel + " " + green + str(messages) + font.reset + " " + blue + command.split()[0] + font.reset + ", " + green + str(words) + font.reset + " wrd, " + green + str(characters) + font.reset + " chr."
-                connection.privmsg(replyto, message)
 
-    elif command.split()[0] == "seen":
-        if cmdtype == "help":    #Display help text.
-            if len(command.split()) != 1:
-                return
-            connection.privmsg(replyto, "Report the last sighting of a user.")
-            connection.privmsg(replyto, grey + "Usage: " + blue + self.cmdchar + command.split()[0] + " " + font.reset + font.italic + "user")
-        elif cmdtype == "cmd":
 
-            if len(command.split()) == 1:
-                connection.privmsg(replyto, "I am seeing you right now. For help type " + blue + self.helpchar + font.reset + ".")
-            elif len(command.split()) == 2:
-                if not self.db.one("SELECT last_act_type FROM users WHERE LOWER(name)='" + command.split()[1] + "' AND network='" + self.network + "'"):
-                    connection.action(replyto, "has never seen " + font.red + trigger.split()[1] + font.reset + ".")
-                elif command.split()[1] == event.source.nick.lower():
-                    connection.action(replyto, "holds up a mirror to " + event.source.nick + ".")
-                elif "!" in command.split()[1] and "@" in command.split()[1]:
-                    connection.privmsg(replyto, "The looks more like a hostname then a nickname to me.")
-                else:
-                    record = self.db.one("SELECT last_act_type, last_act_datetime, last_act_channel, last_act, last_act_auxiliary, away, away_reason FROM users WHERE LOWER(name)='" + command.split()[1] + "' AND network='" + self.network + "'")
-                    if command.split()[1] == connection.get_nickname().lower():
-                        action = "last action was "
-                    else:
-                        action = "last saw " + font.red + trigger.split()[1] + font.reset + " "
-                    if record[0] == "nick":
-                        action += "changing nickname to " + font.red + record[3]
-                    elif record[0] == "join":
-                        action += "joining " + font.red + record[2]
-                    elif record[0] == "kick":
-                        if record[4]:
-                            action += "kicking " + font.red + record[4] + font.reset + " for " + green + record[3]
-                        else:
-                            action += "kicking " + font.red + record[3]
-                    elif record[0] == "kicked":
-                        if record[4]:
-                            action += "being kicked by " + font.red + record[3] + font.reset + " for " + green + record[4]
-                        else:
-                            action += "being kicked by " + font.red + record[3]
-                    elif record[0] == "mode":
-                        action += "changing modes on " + font.red + record[3]
-                    elif record[0] == "part":
-                        action += "parting " + font.red + record[2]
-                        if record[3]:
-                            action += font.reset + " for " + record[3]
-                    elif record[0] == "quit":
-                        action += "disconnecting"
-                        if record[3]:
-                            action += " due to " + green + record[3]
-                    elif record[0] == "topic":
-                        action += "changing the topic of " + font.red + record[2] + font.reset + " to " + green + record[3]
-                    elif record[0] == "msg":
-                        action += "posting " + green + record[3] + font.reset + " to " + font.red + record[2]
-                    elif record[0] == "notice":
-                        action += "posting the notice " + green + record[3] + " to " + font.red + record[2]
-                    elif record[0] == "action":
-                        action += green + record[3] + font.reset + " in " + font.red + record[2]
-                    else:
-                        connection.privmsg(replyto, "Last stored action unsupported by command code.")
-                        return
-                    action += font.reset + ", "
-                    differential = datetime.now() - record[1]
-                    if differential.seconds < 5:    # Less then 5 seconds.
-                        action += blue + "right now."
-                    elif differential.seconds < 20: # Less then 20 seconds.
-                        action += blue + "just now."
-                    elif differential.seconds < 60: # Less then a minute.
-                        action += green + str(differential.seconds) + blue + " seconds " + font.reset + "ago."
-                    elif differential.seconds / 60 == 1: # 1 minute.
-                        action += green + "1 " + blue + "minute " + font.reset + "ago."
-                    elif int(differential.seconds / 60) < 60:   # Less then an hour.
-                        action += green + str(int(differential.seconds / 60)) + blue + " minutes " + font.reset + "ago."
-                    elif int(differential.seconds / 60) ==  60: # 1 hour.
-                        action += green + "1 " + blue + "hour " + font.reset + "ago."
-                    elif int(differential.seconds / 3600) < 24 : # Less then a day.
-                        remaining_seconds = int(differential.seconds - int(differential.seconds / 3600) * 3600)
-                        action += green + str(int(differential.seconds / 3600)) + blue + " hours" + font.reset + " and " + str(int(remaining_seconds / 60)) + " minutes ago."
-                    elif int(differential.seconds / 3600) == 24 : # 1 day.
-                        action += green + "1 " + blue + "day " + font.reset + "ago."
-                    elif differential.days < 7: # Less then a week.
-                        remaining = differential - datetime.timedelta(days=differential.days)
-                        action += green + str(differential.days) + blue + " days " + font.reset + "and " + str(int(remaining.seconds / 3600)) + " hours ago."
-                    elif differential.days < 365:   # Less then a year.
-                        action += green + str(differential.days) + blue + " days " + font.reset + "ago."
-                    elif differential.days < 365:   # Less then 5 years.
-                        remaining_days = int(int(differential.days / 365) * 365)
-                        remaining = differential - datetime.timedelta(days=remaining_days)
-                        action += green + str(int(differential.days / 365)) + blue + " years " + font.reset + "and " + str(remaining.days) + " days."
-                    else:   # More then 5 years.
-                        action += green + str(int(differential.days / 365)) + blue + " years ago."
-                    connection.action(replyto, action)
-                    if record[5]:
-                        if record[6]:
-                            connection.privmsg(replyto, font.red + trigger.split()[1] + font.reset + " is away " + green + record[6] + ".")
-                        else:
-                            connection.privmsg(replyto, font.red + trigger.split()[1] + font.reset + " is away.")
-            else:   # Too many arguments.
-                connection.privmsg(replyto, "Too many arguments. For help type " + blue + self.helpchar + "seen" + font.reset + ".")
+    # elif command.split()[0] == "joins" or command.split()[0] == "kicks" or command.split()[0] == "messages" or command.split()[0] == "actions" or command.split()[0] == "notices":
+    #     if cmdtype == "help":    #Display help text.
+    #         if len(command.split()) != 1:
+    #             return
+    #         connection.privmsg(replyto, "Display amount of " + command.split()[0] + " of user and channel. Channel and user optional.")
+    #         connection.privmsg(replyto, grey + "Usage: " + blue + self.cmdchar + command.split()[0] + " " + font.reset + font.italic + "channel user")
+    #     elif cmdtype == "cmd":
+    #
+    #         # Parse user input
+    #         user = event.source.nick
+    #         channel = None
+    #         channelonly= False
+    #         if not connection.get_nickname() == event.target:   # Channel message.
+    #             channel = event.target
+    #         if len(command.split()) == 1:    # Command contains only !joins.
+    #             user = event.source.nick
+    #         elif len(command.split()) == 2:  # Command has one argument.
+    #             if command.split()[1] in self.channels:
+    #                 channel = command.split()[1]
+    #                 if connection.get_nickname() == event.target:   # Private message.
+    #                     channelonly = True
+    #             else:
+    #                 user = trigger.split()[1]
+    #         elif len(command.split()) == 3: # Command has two arguments.
+    #             if not command.split()[1] in self.channels: # Bot does not inhabit requested channel.
+    #                 if not command.split()[2] in self.channels: # User did not revert channel and user in command syntax.
+    #                     connection.action(replyto, "does not inhabit " + font.red + command.split()[1] + font.reset + ".")
+    #                     return
+    #                 else:   # User reverted user and channel in command syntax.
+    #                     user = trigger.split()[1]
+    #                     channel = command.split()[2]
+    #             else: # Bot does inhabit requested channel.
+    #                 user = trigger.split()[2]
+    #                 channel = trigger.split()[1]
+    #         elif len(command.split()) < 5:  # To many arguments
+    #             connection.privmsg(replyto, "To many arguments. For help type " + blue + self.helpchar + "joins" + font.reset + ".")
+    #             return
+    #
+    #         if command.split()[0] == "joins":
+    #             if channel: # User and channel.
+    #                 userstat = str(sum(self.db.all("SELECT " + command.split()[0] + " FROM " + command.split()[0] + " WHERE channel_network='" + self.network + "' AND LOWER(\"user\")=LOWER('" + user + "') AND user_network='" + self.network + "'")))
+    #                 userchannelstat = str(self.db.one("SELECT " + command.split()[0] + " FROM " + command.split()[0] + " WHERE LOWER(channel)=LOWER('" + channel + "') AND channel_network='" + self.network + "' AND LOWER(\"user\")=LOWER('" + user + "') AND user_network='" + self.network + "'"))
+    #                 channelstat = str(sum(self.db.all("SELECT " + command.split()[0] + " FROM " + command.split()[0] + " WHERE LOWER(channel)=LOWER('" + channel + "') AND channel_network='" + self.network + "' AND user_network='" + self.network + "'")))
+    #                 if userchannelstat == "None":
+    #                     userchannelstat = "0"
+    #                 if channelonly:
+    #                     connection.privmsg(replyto, font.red + channel + font.reset + " has " + green + channelstat + font.reset + " " + command.split()[0] + font.reset + " in total.")
+    #                 elif userstat == "0" and not channelonly: # No user joins on record.
+    #                     connection.action(replyto, "has no record of any joins by " + font.red + user + font.reset + " in " + font.red + channel + font.reset + ".")
+    #                 else:   # User joins on record.
+    #                     connection.privmsg(replyto, font.red + user + font.reset + " has " + green + userstat + font.reset + " " + command.split()[0] + ". Of which " + green + userchannelstat + font.reset + " have been in " + font.red + channel + font.reset + ", that has " + green + channelstat + font.reset + " " + command.split()[0] + font.reset + " in total.")
+    #             else:   # Only user.
+    #                 userstat = str(sum(self.db.all("SELECT " + command.split()[0] + " FROM " + command.split()[0] + " WHERE channel_network='" + self.network + "' AND LOWER(\"user\")=LOWER('" + user + "') AND user_network='" + self.network + "'")))
+    #                 if userstat == "0":    # No statistics on user.
+    #                     connection.action(replyto, "has no record of any joins by " + font.red + user + font.reset + ".")
+    #                 else:   # Got statistics on user.
+    #                     connection.privmsg(replyto, font.red + user + font.reset + " has " + green + userstat + font.reset + " " + command.split()[0] + " in channels I monitor.")
+    #         elif command.split()[0] == "kicks":
+    #             if channel: # User and channel.
+    #                 try:
+    #                     givenkicks, receivedkicks = self.db.one("SELECT given, received FROM " + command.split()[0] + " WHERE LOWER(channel)=LOWER('" + channel + "') AND channel_network='" + self.network + "' AND LOWER(\"user\")=LOWER('" + user + "') AND user_network='" + self.network + "'")
+    #                 except:
+    #                     givenkicks = 0
+    #                     receivedkicks = 0
+    #                 if channelonly:
+    #                     connection.privmsg(replyto, font.red + channel + font.reset + " has " + green + channelstat + font.reset + " " + command.split()[0] + font.reset + " in total.")
+    #                 elif givenkicks == 0 and receivedkicks == 0:  # No kicks on record.
+    #                     connection.action(replyto, "has no record of any kicks for " + font.red + user + font.reset + " in " + font.red + channel + font.reset + ".")
+    #                 else:   # Kciks on record.
+    #                     channelkicks = self.db.all("SELECT given FROM " + command.split()[0] + " WHERE LOWER(channel)=LOWER('" + channel + "') AND channel_network='" + self.network + "'")
+    #                     connection.privmsg(replyto, font.red + user + font.reset + " has kicked " + green + str(givenkicks) + font.reset + " and been kicked " + green + str(receivedkicks) + font.reset + " times in " + font.red + channel + font.reset + ", where were " + green + str(sum(channelkicks)) + font.reset + " kicks in total.")
+    #             else:   # Only user.
+    #                 userstat = self.db.all("SELECT given, received FROM kicks WHERE LOWER(\"user\")=LOWER('" + user + "') AND user_network='" + self.network + "'")
+    #                 kicksgiven = 0
+    #                 kicksreceived = 0
+    #                 for record in userstat:
+    #                     kicksgiven += record[0]
+    #                     kicksreceived += record[1]
+    #                 if kicksgiven == 0 and kicksreceived == 0:
+    #                     connection.action(replyto, font.red + user + font.reset + " has no record of any kicks for " + font.red + user + font.reset + " in " + font.red + channel + font.reset + ".")
+    #                 else:
+    #                     connection.privmsg(replyto, font.red + user + font.reset + " has given " + green + str(kicksgiven) + font.reset + " and received " + green + str(kicksreceived) + font.reset + " kicks")
+    #         elif command.split()[0] == "messages" or command.split()[0] == "actions" or command.split()[0] == "notices":
+    #             userstat = self.db.all("SELECT " + command.split()[0] + ", " + command.split()[0] + "_words, " + command.split()[0] + "_characters FROM messages WHERE LOWER(\"user\")=LOWER('" + user + "') AND user_network='" + self.network + "'")
+    #
+    #             userrecord = False
+    #             for record in userstat:
+    #                 if not record[0] == 0:
+    #                     userrecord = True
+    #
+    #             if not userrecord:
+    #                 if command.split()[0] == "messages":
+    #                     connection.action(replyto, "has no record of " + font.red + user + font.reset + " speaking.")
+    #                     return
+    #                 if command.split()[0] == "actions":
+    #                     connection.action(replyto, "has no record of " + font.red + user + font.reset + " acting.")
+    #                     return
+    #                 if command.split()[0] == "notices":
+    #                     connection.action(replyto, "has not noticed " + font.red + user + font.reset + ".")
+    #                     return
+    #             messages, words, characters = StatisticsHelpers.add_message_stats(userstat)
+    #             message = "Totals of " + font.red + user + " " + green + str(messages) + font.reset + " " + blue + command.split()[0] + font.reset + ", " + green + str(words) + font.reset + " words, " + green + str(characters) + font.reset + " characters."
+    #             if channel: # User and channel.
+    #                 userchanstat = self.db.one("SELECT " + command.split()[0] + ", " + command.split()[0] + "_words, " + command.split()[0] + "_characters FROM messages WHERE lower(channel)=lower('" + channel + "') AND channel_network='" + self.network + "' AND lower(\"user\")=lower('" + user + "') AND user_network='" + self.network + "'")
+    #                 chanstat = self.db.all("SELECT " + command.split()[0] + ", " + command.split()[0] + "_words, " + command.split()[0] + "_characters FROM messages WHERE lower(channel)=lower('" + channel + "') AND channel_network='" + self.network + "'")
+    #                 if not userchanstat[0] == 0:
+    #                     message += " " + font.red + user + font.reset + " in " + font.red + channel + " " + green + str(userchanstat[0]) + font.reset + " " + blue + command.split()[0] + font.reset + ", " + green + str(userchanstat[1]) + font.reset + " words, " + green + str(userchanstat[2]) + font.reset + " chars."
+    #                 channelrecord = False
+    #                 for record in chanstat:
+    #                     if not record[0] == 0:
+    #                         channelrecord = True
+    #                 if channelrecord:
+    #                     messages, words, characters = StatisticsHelpers.add_message_stats(chanstat)
+    #                     message += " Total in " + font.red + channel + " " + green + str(messages) + font.reset + " " + blue + command.split()[0] + font.reset + ", " + green + str(words) + font.reset + " wrd, " + green + str(characters) + font.reset + " chr."
+    #             connection.privmsg(replyto, message)
+    #
+    # elif command.split()[0] == "seen":
+    #     if cmdtype == "help":    #Display help text.
+    #         if len(command.split()) != 1:
+    #             return
+    #         connection.privmsg(replyto, "Report the last sighting of a user.")
+    #         connection.privmsg(replyto, grey + "Usage: " + blue + self.cmdchar + command.split()[0] + " " + font.reset + font.italic + "user")
+    #     elif cmdtype == "cmd":
+    #
+    #         if len(command.split()) == 1:
+    #             connection.privmsg(replyto, "I am seeing you right now. For help type " + blue + self.helpchar + font.reset + ".")
+    #         elif len(command.split()) == 2:
+    #             if not self.db.one("SELECT last_act_type FROM users WHERE LOWER(name)='" + command.split()[1] + "' AND network='" + self.network + "'"):
+    #                 connection.action(replyto, "has never seen " + font.red + trigger.split()[1] + font.reset + ".")
+    #             elif command.split()[1] == event.source.nick.lower():
+    #                 connection.action(replyto, "holds up a mirror to " + event.source.nick + ".")
+    #             elif "!" in command.split()[1] and "@" in command.split()[1]:
+    #                 connection.privmsg(replyto, "The looks more like a hostname then a nickname to me.")
+    #             else:
+    #                 record = self.db.one("SELECT last_act_type, last_act_datetime, last_act_channel, last_act, last_act_auxiliary, away, away_reason FROM users WHERE LOWER(name)='" + command.split()[1] + "' AND network='" + self.network + "'")
+    #                 if command.split()[1] == connection.get_nickname().lower():
+    #                     action = "last action was "
+    #                 else:
+    #                     action = "last saw " + font.red + trigger.split()[1] + font.reset + " "
+    #                 if record[0] == "nick":
+    #                     action += "changing nickname to " + font.red + record[3]
+    #                 elif record[0] == "join":
+    #                     action += "joining " + font.red + record[2]
+    #                 elif record[0] == "kick":
+    #                     if record[4]:
+    #                         action += "kicking " + font.red + record[4] + font.reset + " for " + green + record[3]
+    #                     else:
+    #                         action += "kicking " + font.red + record[3]
+    #                 elif record[0] == "kicked":
+    #                     if record[4]:
+    #                         action += "being kicked by " + font.red + record[3] + font.reset + " for " + green + record[4]
+    #                     else:
+    #                         action += "being kicked by " + font.red + record[3]
+    #                 elif record[0] == "mode":
+    #                     action += "changing modes on " + font.red + record[3]
+    #                 elif record[0] == "part":
+    #                     action += "parting " + font.red + record[2]
+    #                     if record[3]:
+    #                         action += font.reset + " for " + record[3]
+    #                 elif record[0] == "quit":
+    #                     action += "disconnecting"
+    #                     if record[3]:
+    #                         action += " due to " + green + record[3]
+    #                 elif record[0] == "topic":
+    #                     action += "changing the topic of " + font.red + record[2] + font.reset + " to " + green + record[3]
+    #                 elif record[0] == "msg":
+    #                     action += "posting " + green + record[3] + font.reset + " to " + font.red + record[2]
+    #                 elif record[0] == "notice":
+    #                     action += "posting the notice " + green + record[3] + " to " + font.red + record[2]
+    #                 elif record[0] == "action":
+    #                     action += green + record[3] + font.reset + " in " + font.red + record[2]
+    #                 else:
+    #                     connection.privmsg(replyto, "Last stored action unsupported by command code.")
+    #                     return
+    #                 action += font.reset + ", "
+    #                 differential = datetime.now() - record[1]
+    #                 if differential.seconds < 5:    # Less then 5 seconds.
+    #                     action += blue + "right now."
+    #                 elif differential.seconds < 20: # Less then 20 seconds.
+    #                     action += blue + "just now."
+    #                 elif differential.seconds < 60: # Less then a minute.
+    #                     action += green + str(differential.seconds) + blue + " seconds " + font.reset + "ago."
+    #                 elif differential.seconds / 60 == 1: # 1 minute.
+    #                     action += green + "1 " + blue + "minute " + font.reset + "ago."
+    #                 elif int(differential.seconds / 60) < 60:   # Less then an hour.
+    #                     action += green + str(int(differential.seconds / 60)) + blue + " minutes " + font.reset + "ago."
+    #                 elif int(differential.seconds / 60) ==  60: # 1 hour.
+    #                     action += green + "1 " + blue + "hour " + font.reset + "ago."
+    #                 elif int(differential.seconds / 3600) < 24 : # Less then a day.
+    #                     remaining_seconds = int(differential.seconds - int(differential.seconds / 3600) * 3600)
+    #                     action += green + str(int(differential.seconds / 3600)) + blue + " hours" + font.reset + " and " + str(int(remaining_seconds / 60)) + " minutes ago."
+    #                 elif int(differential.seconds / 3600) == 24 : # 1 day.
+    #                     action += green + "1 " + blue + "day " + font.reset + "ago."
+    #                 elif differential.days < 7: # Less then a week.
+    #                     remaining = differential - datetime.timedelta(days=differential.days)
+    #                     action += green + str(differential.days) + blue + " days " + font.reset + "and " + str(int(remaining.seconds / 3600)) + " hours ago."
+    #                 elif differential.days < 365:   # Less then a year.
+    #                     action += green + str(differential.days) + blue + " days " + font.reset + "ago."
+    #                 elif differential.days < 365:   # Less then 5 years.
+    #                     remaining_days = int(int(differential.days / 365) * 365)
+    #                     remaining = differential - datetime.timedelta(days=remaining_days)
+    #                     action += green + str(int(differential.days / 365)) + blue + " years " + font.reset + "and " + str(remaining.days) + " days."
+    #                 else:   # More then 5 years.
+    #                     action += green + str(int(differential.days / 365)) + blue + " years ago."
+    #                 connection.action(replyto, action)
+    #                 if record[5]:
+    #                     if record[6]:
+    #                         connection.privmsg(replyto, font.red + trigger.split()[1] + font.reset + " is away " + green + record[6] + ".")
+    #                     else:
+    #                         connection.privmsg(replyto, font.red + trigger.split()[1] + font.reset + " is away.")
+    #         else:   # Too many arguments.
+    #             connection.privmsg(replyto, "Too many arguments. For help type " + blue + self.helpchar + "seen" + font.reset + ".")

+ 121 - 7
rotbot/common/queries.py

@@ -1,11 +1,125 @@
-def create_ifnot_onrecord(self, table, name):
-    record = self.db.one('SELECT * FROM rotbot_' + table + ' WHERE LOWER(name)=LOWER(%(name)s) AND network_id=%(network_id)s', name=name, network_id=self.network.id)
+import random, secrets, datetime, uuid
+from slugify import slugify
+from common import log
+
+def create_or_get_and_update_last_event(self, table, event_type, channel_name=None, user_name=None, event_content=None, event_subject_name=None):
+    # EVENT_TYPE_CHOICES = [
+    #     ('pm', 'private message'),
+    #     ('pa', 'private action'),
+    #     ('pn', 'private notice'),
+    #     ('cm', 'channel message')
+    #     ('ca', 'channel action'),
+    #     ('cn', 'channel notice'),
+    #     ('ct', 'channel topic'),
+    #     ('ck', 'channel password'),
+    #     ('ci', 'channel invite'),
+    #     ('cj', 'channel join'),
+    #     ('cp', 'channel part'),
+    #     ('ck', 'channel kick'),
+    #     ('kd', 'channel kicked'),
+    #     ('mc', 'mode change'),
+    #     ('nc', 'nick change'),
+    #     ('sq', 'quit'),
+    # ]
+    if table == 'channel':
+        name = channel_name
+    if table == 'user':
+        name = user_name
+    record = self.db.one('SELECT * FROM rotbot_' + table + ' WHERE LOWER(name)=LOWER(%(name)s) AND network_id=%(network_id)s', name=name, network_id=self.network.id)   # Look up the record.
 
     if record:  # On record.
-        if name != record.name:
-            self.db.run('UPDATE rotbot_' + table + ' SET name=%(name)r WHERE LOWER(name)=LOWER(%(name)s) AND network_id=%(network_id)s', name=name, network_id=self.network.id)  # Correct capitalisation
-    else:   # Record created.
-        self.db.run('INSERT INTO rotbot_' + table + ' (name, network_id) VALUES (%(name)s, %(network_id)s)', name=name, network_id=self.network.id)  # Create record.
-        record = self.db.one('SELECT * FROM rotbot_' + table + ' WHERE LOWER(name)=LOWER(%(name)s) AND network_id=%(network_id)s', name=name, network_id=self.network.id)
+        fields = 'name=%(name)s, last_event_type=%(last_event_type)s' # All required fields
+        event_channel_id = None
+        event_user_id = None
+        event_subject_id = None
+        if 'event_channel' in locals():
+            event_channel_id = get_channel_id(self, channel_name)
+            fields += ', last_event_channel_id=%(last_event_channel_id)s'
+        if 'event_user' in locals():
+            event_user_id = get_user_id(self, user_name)
+            fields += ', last_event_user_id=%(last_event_user_id)s'
+        if 'event_content' in locals():
+            fields += ', last_event_content=%(last_event_content)s'
+        if 'event_subject' in locals():
+            event_subject_id = get_user_id(self, user_name)
+            fields +- ', last_event_subject_id=%(last_event_subject_id)s'
+        self.db.run('UPDATE rotbot_' + table + ' SET ' + fields + ' WHERE LOWER(name)=LOWER(%(name)s) AND network_id=%(network_id)s', name=name, network_id=self.network.id, last_event_type=event_type, last_event_channel_id=event_channel_id, last_event_user_id=event_user_id, last_event_content=event_content, last_event_subject_id=event_subject_id) # Correct capitalisation and update last event.
+
+    else:   # Not on record.
+        new_slug = slugify('%s-%s' % (name, self.network.name), max_length=50) # Create a slug from the name.
+        slug_exists = self.db.one('SELECT id FROM rotbot_' + table + ' WHERE slug=%(slug)s AND network_id=%(network_id)s', slug=new_slug, network_id=self.network.id)   # Try to lookup a record with the proposed slug.
+
+        # Keep adding random letters from the name to the slug until it is unique.
+        while slug_exists and slug_exists <= 50:
+            new_slug = new_slug + random.choice(name)
+            slug_exists = self.db.one('SELECT id FROM rotbot_' + table + ' WHERE slug=%(slug)s AND network_id=%(network_id)s', slug=new_slug, network_id=self.network.id)
+            log.notice('Requested slug already taken on table %s with network %s: %s' % (table, self.network.name, new_slug))
+        while slug_exists:
+            new_slug = name + uuid.uuid4()
+            slug_exists = self.db.one('SELECT id FROM rotbot_' + table + ' WHERE slug=%(slug)s AND network_id=%(network_id)s', slug=new_slug, network_id=self.network.id)
+            log.warn('Please clean database. Newly generated uuid4 already taken on table %s with network %s: %s' % (table, self.network.name, new_slug))
+
+        fields = 'name, network_id, slug, last_event_type, last_event_datetime'
+        values = '%(name)s, %(network_id)s, %(slug)s, %(last_event_type)s, %(last_event_datetime)s'
+        event_datetime = datetime.datetime.now
+        event_channel_id = None
+        event_user_id = None
+        event_subject_id = None
+        if 'event_channel' in locals():
+            event_channel_id = get_channel_id(self, channel_name)
+            fields += ', last_event_channel_id'
+            values += ', %(last_event_channel_id)s'
+        if 'event_user' in locals():
+            event_user_id = get_user_id(self, user_name)
+            fields += ', last_event_user_id'
+            values += ', %(last_event_user_id)s'
+        if 'event_content' in locals():
+            fields += ', last_event_content'
+            values += ', %(last_event_content)s'
+        if 'event_subject' in locals():
+            event_subject_id = get_user_id(self, user_name)
+            fields += ', last_event_subject_id'
+            values += ', %(last_event_subject_id)s'
+        if table == 'channel':
+            self.db.run('INSERT INTO rotbot_' + table + ' (' + fields + ', autojoin, key, games) VALUES (' + values + ', %(autojoin)s, %(key)s, %(games)s)', name=name, network_id=self.network.id, slug=new_slug, last_event_type=event_type, last_event_datetime=datetime.datetime.now(), last_event_channel_id=event_channel_id, last_event_user_id=event_user_id, last_event_content=event_content, last_event_subject_id=event_subject_id, autojoin=False, key='', games=False)  # Create record.
+        else:
+            self.db.run('INSERT INTO rotbot_' + table + ' (' + fields + ') VALUES (' + values + ')', name=name, network_id=self.network.id, slug=new_slug, last_event_type=event_type, last_event_datetime=datetime.datetime.now(), last_event_channel_id=event_channel_id, last_event_user_id=event_user_id, last_event_content=event_content, last_event_subject_id=event_subject_id)  # Create record.
+        record = self.db.one('SELECT * FROM rotbot_' + table + ' WHERE slug=%(slug)s', slug=new_slug)   # Lookup newly created record to return to call.
 
+        if event_type in ('cm', 'ca', 'cn'):    # It's a public message, action or notice
+            MessageStatistics.update(self, event, 'message', user, channel) # Update message statistics.
     return record
+
+
+def increment_join(self, channel, user):
+    if not self.db.one('SELECT id FROM rotbot_join WHERE network_id=%(network_id)s AND channel_id=%(channel_id)s AND user_id=%(user_id)s', network_id=self.network.id, channel_id=channel.id, user_id=user.id):   # No record yet
+        self.db.run('INSERT INTO rotbot_join (network_id, channel_id, user_id, amount) VALUES (%(network_id)s, %(channel_id)s, %(user_id)s, 1)', network_id=self.network.id, channel_id=channel.id, user_id=user.id)   # Create record.
+    else:
+        self.db.run('UPDATE rotbot_join SET amount = amount + 1 WHERE network_id=%(network_id)s AND channel_id=%(channel_id)s AND user_id=%(user_id)s', network_id=self.network.id, channel_id=channel.id, user_id=user.id)   # Update existing record.
+
+def get_channel_id(self, channel_name):
+    return self.db.one('SELECT id FROM rotbot_channel WHERE network_id=%(network_id)s AND LOWER(name)=LOWER(%(channel_name)s)', network_id=self.network.id, channel_name=channel_name)
+
+def get_channel_slug(self, channel_name):
+    return self.db.one('SELECT slug FROM rotbot_channel WHERE network_id=%(network_id)s AND LOWER(name)=LOWER(%(channel_name)s)', network_id=self.network.id, channel_name=channel_name)
+
+def get_channel_setting_statistic_commands(self, channel_name):
+    return self.db.one('SELECT statistic_commands FROM rotbot_channel WHERE LOWER(name)=LOWER(%(channel_name)s) AND network_id=%(network_id)s', channel_name=channel_name, network_id=self.network.id)
+
+def get_channel_setting_game_commands(self, channel_name):
+    return self.db.one('SELECT games FROM rotbot_channel WHERE name=%(channel_name)s AND network_id=%(network_id)', channel_name=channel_name, network_id=self.network.id)
+
+def update_channel_last_event(self, channel, event_type, event_content):
+    self.db.run('UPDATE rotbot_channel SET last_event_type=%(last_event_type)s, last_event_content=%(last_event_content)s WHERE channel.id=%(channel_id)s', last_event_type=event_type, last_event_content=event_content)
+
+def create_tempchannelkey(self, channel_id):
+    temp_key = secrets.token_urlsafe(40)[:40]
+    self.db.run('INSERT INTO rotbot_tempchannelkey (key, network_id, channel_id, created) VALUES (%(key)s, %(network_id)s, %(channel_id)s, %(created)s)', key=temp_key, network_id=self.network.id, channel_id=channel_id, created=datetime.datetime.now())
+    return temp_key
+
+
+def get_user_id(self, user_name):
+    return self.db.one('SELECT id FROM rotbot_user WHERE network_id=%(network_id)s AND LOWER(name)=LOWER(%(user_name)s)', network_id=self.network.id, user_name=user_name)
+
+def get_userl_slug(self, user_name):
+    return self.db.one('SELECT slug FROM rotbot_user WHERE network_id=%(network_id)s AND LOWER(name)=LOWER(%(user_name)s)', network_id=self.network.id, user_name=user_name)

+ 0 - 2
rotbot/events/common.py

@@ -90,8 +90,6 @@ class Lastact():
 
 class MessageStatistics():
     def update(self, event, type):
-        channel = queries.create_ifnot_onrecord(self, 'channel', event.target)
-        user = queries.create_ifnot_onrecord(self, 'user', event.source.nick)
         if not self.db.one('SELECT id FROM rotbot_' + type + ' WHERE network_id=%(network_id)s AND channel_id=%(channel_id)s AND user_id=%(user_id)s', network_id=self.network.id, channel_id=channel.id ,user_id=user.id):  # Not on record.
             self.db.run('INSERT INTO rotbot_' + type + ' (network_id, channel_id, user_id, amount) VALUES (%(network_id)s, %(channel_id)s, %(user_id)s, 1)', network_id=self.network.id, channel_id=channel.id ,user_id=user.id)   # Create record.
         else:   # On record.

+ 6 - 7
rotbot/events/on_action.py

@@ -1,13 +1,12 @@
-from common import userstatus, font
-from events.common import Replyto, Lastact, MessageStatistics
+from common import queries #userstatus, font, queries
+#from events.common import Replyto, Lastact, MessageStatistics
 
 def process_event(self, connection, event):
+    # Let's not log all actions.
 
-    # # Update last act.
-    # Lastact.update(self, event.source.nick, "action", channel=event.target, lastact=event.arguments[0])
-
-    # Save statistic to database.
-    MessageStatistics.update(self, event, 'action')
+    if event.target != connection.get_nickname():   # Channel action.
+        channel = queries.create_or_get_and_update_last_event(self, 'channel', 'ca', channel_name=event.target, user_name=event.source.nick)
+        user = queries.create_or_get_and_update_last_event(self, 'user', 'ca', channel_name=event.target, user_name=event.source.nick)
 
     # # Stop if channelfunction chat if off.
     # if not self.db.one("SELECT chat FROM channels WHERE name='" + event.target + "' AND network='" + self.network + "'"):

+ 11 - 17
rotbot/events/on_join.py

@@ -1,22 +1,16 @@
-from common import log, queries, font
-#from events.common import Lastact
+from common import log, queries
 
 def process_event(self, connection, event):
-    log.info(event)
+    log.info(event) # Log to console.
 
-    # Lastact.update(self, event.source.nick, "join", channel=event.target)  # Update last act.
+    # Get and update resources.
+    channel = queries.create_or_get_and_update_last_event(self, 'channel', 'cj', channel_name=event.target, user_name=event.source.nick)
+    user = queries.create_or_get_and_update_last_event(self, 'user', 'cj', channel_name=event.target, user_name=event.source.nick)
 
-    # Save resources that are not represented in the database.
-    channel = queries.create_ifnot_onrecord(self, 'channel', event.target)
-    user = queries.create_ifnot_onrecord(self, 'user', event.source.nick)
+    queries.increment_join(self, channel, user)
 
-    # Save to join event database.
-    if not self.db.one('SELECT id FROM rotbot_join WHERE network_id=%(network_id)s AND channel_id=%(channel_id)s AND user_id=%(user_id)s', network_id=self.network.id, channel_id=channel.id, user_id=user.id):   # No record yet
-        self.db.run('INSERT INTO rotbot_join (network_id, channel_id, user_id, amount) VALUES (%(network_id)s, %(channel_id)s, %(user_id)s, 1)', network_id=self.network.id, channel_id=channel.id, user_id=user.id)   # Create record.
-    else:
-        self.db.run('UPDATE rotbot_join SET amount = amount + 1 WHERE network_id=%(network_id)s AND channel_id=%(channel_id)s AND user_id=%(user_id)s', network_id=self.network.id, channel_id=channel.id, user_id=user.id)   # Update existing record.
 
-    if event.source.nick == connection.get_nickname():  # The bot joined a channel.
+    # if event.source.nick == connection.get_nickname():  # The bot joined a channel.
         # connection.who(self.homechannel)    # Get whoreplies for users of homechannel.
         # if self.channels[event.target].has_key():
         #     if event.target in self.channelkeys:    # New key used to join channel.
@@ -24,11 +18,11 @@ def process_event(self, connection, event):
         #         del self.channelkeys[event.target]  # Delete entry.
         # if event.target == self.homechannel:    # Home channel.
         #     connection.who(connection.get_nickname())   # get whoreply to add bot to protectees.
-        return  # Do not greet myself.
+    #    return  # Do not greet myself.
 
-    # Promote owners.
-    if event.source.nick in self.channels[self.network.home_channel].owners():
-        connection.mode(event.target, '+vhoa %s %s %s %s' % (event.source.nick, event.source.nick, event.source.nick, event.source.nick))
+    # # Promote owners.
+    # if event.source.nick in self.channels[self.network.home_channel].owners():
+    #     connection.mode(event.target, '+vhoa %s %s %s %s' % (event.source.nick, event.source.nick, event.source.nick, event.source.nick))
 
     # # Stop if greeting is not wanted.
     # joingreeting = self.db.one("SELECT join_greeting FROM channels WHERE name='" + event.target + "' AND network='" + self.network + "'")

+ 8 - 26
rotbot/events/on_kick.py

@@ -1,33 +1,15 @@
-from common.queries import create_ifnot_onrecord
-from common.networkservices import ChanServ
-from common import userstatus, do_everything_to, log, font
-from events.common import Aggressiveness, Lastact
-
-bold = "\x02"
-italic = "\x1D"
-underline = "\x1F"
-reverse = "\x16" 	# swap background and foreground colors ("reverse video")
-reset = "\x0F"
-blue = "\x0302"
-green = "\x0303"
-red = "\x0304"
-grey = "\x0314"
+#from common.networkservices import ChanServ
+from common import log, queries #userstatus, do_everything_to, log, font, queries
+#from events.common import Aggressiveness, Lastact
 
 def process_event(self, connection, event):
-    log.info(event)
+    log.info(event) # Log to console
 
-    kicker = create_ifnot_onrecord(self, 'user', event.source.nick)
-    channel = create_ifnot_onrecord(self, 'channel', event.target)
-    kicked = create_ifnot_onrecord(self, 'user', event.arguments[0])
+    # Get and update resources.
     reason = event.arguments[1]
-
-    # # Update last act.
-    # if reason:
-    #     Lastact.update(self, kicker, "kick", channel=channel, lastact=kicked, auxiliary=reason)
-    #     Lastact.update(self, kicked, "kicked", channel=channel, lastact=kicker, auxiliary=reason)
-    # else:
-    #     Lastact.update(self, kicker, "kick", channel=channel, lastact=kicked)
-    #     Lastact.update(self, kicked, "kicked", channel=channel, lastact=kicker)
+    kicker = queries.create_or_get_and_update_last_event(self, 'user', 'ck', channel_name=event.target, user_name=event.source.nick, extra_content=reason, event_subject_name=event.arguments[0])
+    channel = queries.create_or_get_and_update_last_event(self, 'channel', 'ck', channel_name=event.target, user_name=event.source.nick, extra_content=reason, event_subject_name=event.arguments[0])
+    kicked = queries.create_or_get_and_update_last_event(self, 'user','ck', channel_name=event.target, user_name=event.arguments[0], extra_content=reason, event_subject_name=event.source.nick)
 
     # Record kick event in database.
     if not self.db.one('SELECT id FROM rotbot_kick WHERE network_id=$(network_id)s AND channel_id=$(channel_id)s AND kicker_id=%(kicker_id)s AND kicked_id=%(kicked_id)s', network_id=self.network.id, channel_id=channel.id, kicker_id=kicker.id, kicked_id=kicked.id): # No records for kicker channel combination.

+ 113 - 122
rotbot/events/on_mode.py

@@ -1,126 +1,117 @@
-import fnmatch
-from irc.modes import parse_channel_modes
-from common.networkservices import ChanServ
-from common import do_everything_to, userstatus, log
-from events.common import Aggressiveness, Lastact
-
-bold = "\x02"
-italic = "\x1D"
-underline = "\x1F"
-reverse = "\x16" 	# swap background and foreground colors ("reverse video")
-reset = "\x0F"
-blue = "\x0302"
-green = "\x0303"
-red = "\x0304"
-grey = "\x0314"
+# import fnmatch
+# from irc.modes import parse_channel_modes
+# from common.networkservices import ChanServ
+from common import log #do_everything_to, userstatus, log, font
+# from events.common import Aggressiveness, Lastact
 
 def process_event(self, connection, event):
     log.info(event)
-    
-    # Update protectees.
-    if event.target == self.homechannel:    # Home channel
-        if any(mode in event.arguments[0][-1:] for mode in ("q", "a", "o", "h")):  # Atleast halfop.
-            connection.who(event.arguments[1])  # Get whorepy to update protectees.
-    
-    # Update last act.
-    Lastact.update(self, event.source.nick, "mode", lastact=event.target)
-    
-    # React.
-    modes = parse_channel_modes(" ".join(event.arguments))
-    behaviour = self.db.one("SELECT aggressiveness FROM channels WHERE name='" + event.target + "' AND network='" + self.network + "'")
-    for idx, mode in enumerate(modes):
-        
-        # Report.
-        if not event.target == self.homechannel:    # Not in home channel.
-            for protectee in self.protectees:
-                if mode[1] == "b" and fnmatch.fnmatch(self.protectees[protectee]['ident'], mode[2]) and mode[0] == "+":   # Protectee banned.
-                    connection.privmsg(self.homechannel, red + protectee + reset + " banned from " + red + event.target + reset + " by " + red + event.source.nick + reset + ": " + green + mode[2])
 
-                if mode[1] == "e" and fnmatch.fnmatch(self.protectees[protectee]['ident'], event.arguments[idx + 1]) and mode[0] == "-":   # Protectee's exception removed.
-                    connection.privmsg(self.homechannel, red + protectee + reset + " has had their exception removed from " + red + event.target + reset + " by " + red + event.source.nick + reset + ": " + green + event.arguments[idx + 1])
-    
-        # Track channel keys.
-        if mode[1] == "k":  # Channel key changed.
-            if mode[0] == "+":  # Key set.
-                self.db.run("UPDATE channels SET key='" + mode[2] + "' WHERE name='" + event.target + "' AND network='" + self.network + "'")
-            else:   # Key removed.
-                self.db.run("UPDATE channels SET key=NULL WHERE name='" + event.target + "' AND network='" + self.network + "'")
-        
-        # Unban if bot is banned from home channel
-        try:
-            if mode[1] == "b" and fnmatch.fnmatch(self.protectees[connection.get_nickname()]['ident'], mode[2]) and mode[0] == "+" and event.target == self.homechannel:
-                connection.privmsg("ChanServ", "UNBAN")
-                do_everything_to.unban(connection, event.target, connection.get_nickname(), mode[2])
-                connection.mode(event.target, "-b " + mode[2])
-        except:
-            pass
-        
-        # Stop if not present in home channel.
-        if not self.homechannel in self.channels:
-            return
-        
-        # Stop if offender is bot or bot owner
-        if event.source.nick == connection.get_nickname() or self.channels[self.homechannel].is_owner(event.source.nick):
-            return
-        
-        for protectee in self.protectees:
-            
-            # Stop if offender is atleast halfop in the home channel and offended is not owner.
-            if userstatus.atleast_halfop(self, event.source.nick, self.homechannel) and not self.channels[self.homechannel].is_owner(protectee):
-                return
-            
-            try:    # Had "IndexError: list index out of range" on "if modes[1] == "b" and mode[0] == "+":  # Ban.".
-                if behaviour == "passive":    # Passive behaviour.
-                    return
-                elif behaviour == "defense_only":   # Defensive only behaviour.
-                    if mode[1] == "b" and mode[0] == "+":  # Ban.
-                        if fnmatch.fnmatch(self.protectees[protectee]['ident'], mode[2]):  # Protectee.
-                            do_everything_to.unban(connection, event.target, protectee, mode[2])
-                    elif mode[1] == "e" and mode[0] == "-":  # Removed exception.
-                        if fnmatch.fnmatch(self.protectees[protectee]['ident'], event.arguments[idx + 1]):  # Protectee.
-                            connection.mode(event.target, "+e " + mode[2])
-                    elif mode[0] == "-" and mode[1] in ["q", "a", "o", "h", "v"]:   # Channel right taken.
-                        ChanServ.give_mode(connection, event.target, mode[2], mode[1])
-                        connection.mode(event.target, mode[0] + mode[1] + " " + mode[2])
-                elif behaviour == "equal_retalliation":   # Equal retaliatory behaviour.
-                    if modes[1] == "b" and mode[0] == "+":  # Ban.
-                        if fnmatch.fnmatch(self.protectees[protectee]['ident'], mode[2]):  # Protectee.
-                            do_everything_to.unban(connection, event.target, protectee, mode[2])
-                            if protectee == connection.get_nickname():  # Bot banned.
-                                ChanServ.ban(connection, event.target, event.source.nick, "Aggression channel function = equal_retalliation.")
-                            else:
-                                ChanServ.ban(connection, event.target, event.source.nick, "Aggression channel function = equal_retalliation: " + protectee + " is an operator of " + connection.get_nickname() + ".")
-                            connection.mode(event.target, "+b " + event.source)
-                    elif mode[1] == "e" and mode[0] == "-":  # Removed exception.
-                        if fnmatch.fnmatch(self.protectees[protectee]['ident'], event.arguments[idx + 1]):  # Protectee.
-                            connection.mode(event.target, "+e " + event.arguments[idx + 1])
-                    elif mode[0] == "-" and mode[1] in ["q", "a", "o", "h", "v"]:   # Channel right taken.
-                        ChanServ.give_mode(connection, event.target, mode[2], mode[1])
-                        connection.mode(event.target, mode[0] + mode[1] + " " + mode[2])
-                        ChanServ.take_all_modes(connection, event.target, mode[2])
-                        connection.mode(event.target, "-vhoaq " + mode[2] + " " + mode[2]+ " " + mode[2]+ " " + mode[2]+ " " + mode[2])
-                elif behaviour == "battlebot":  # Battlebot behaviour.
-                    if mode[0] == "-" and mode[1] in ["q", "a", "o", "h", "v"]:   # Channel right taken.
-                        ChanServ.give_mode(connection, event.target, mode[2], mode[1])
-                        connection.mode(event.target, mode[0] + mode[1] + " " + mode[2])
-                        do_everything_to.unban(connection, event.target, protectee, mode[2])
-                        do_everything_to.ban(connection, event.target, event.source.nick, event.source, Aggressiveness.retalliation_reason(self, connection, protectee, behaviour))
-                        connection.mode(event.target, "+e " + mode[2])
-                        ChanServ.akick_add(connection, event.target, event.source.nick)
-                        do_everything_to.kick(connection, event.target, event.source.nick, Aggressiveness.retalliation_reason(self, connection, protectee, behaviour))
-                    if mode[1] == "b" and mode[0] == "+":  # Ban.
-                        if fnmatch.fnmatch(self.protectees[protectee]['ident'], mode[2]):  # Protectee.
-                            do_everything_to.unban(connection, event.target, protectee, mode[2])
-                            do_everything_to.ban(connection, event.target, event.source.nick, event.source, Aggressiveness.retalliation_reason(self, connection, protectee, behaviour))
-                            connection.mode(event.target, "+e " + mode[2])
-                            ChanServ.akick_add(connection, event.target, event.source.nick)
-                            do_everything_to.kick(connection, event.target, event.source.nick, Aggressiveness.retalliation_reason(self, connection, protectee, behaviour))
-                    elif mode[1] == "e" and mode[0] == "-":  # Removed exception.
-                        #for protectee in self.protectees:
-                        if fnmatch.fnmatch(self.protectees[protectee]['ident'], event.arguments[idx + 1]):  # Protectee.
-                            do_everything_to.ban(connection, event.target, event.source.nick, event.source, Aggressiveness.retalliation_reason(self, connection, protectee, behaviour))
-                            connection.mode(event.target, "+e " + event.arguments[idx + 1])
-                            ChanServ.akick_add(connection, event.target, event.source.nick)
-                            do_everything_to.kick(connection, event.target, event.source.nick, Aggressiveness.retalliation_reason(self, connection, protectee, behaviour))
-            except:
-                pass
+
+    # # Update protectees.
+    # if event.target == self.homechannel:    # Home channel
+    #     if any(mode in event.arguments[0][-1:] for mode in ("q", "a", "o", "h")):  # Atleast halfop.
+    #         connection.who(event.arguments[1])  # Get whorepy to update protectees.
+    #
+    # # Update last act.
+    # Lastact.update(self, event.source.nick, "mode", lastact=event.target)
+    #
+    # # React.
+    # modes = parse_channel_modes(" ".join(event.arguments))
+    # behaviour = self.db.one("SELECT aggressiveness FROM channels WHERE name='" + event.target + "' AND network='" + self.network + "'")
+    # for idx, mode in enumerate(modes):
+    #
+    #     # Report.
+    #     if not event.target == self.homechannel:    # Not in home channel.
+    #         for protectee in self.protectees:
+    #             if mode[1] == "b" and fnmatch.fnmatch(self.protectees[protectee]['ident'], mode[2]) and mode[0] == "+":   # Protectee banned.
+    #                 connection.privmsg(self.homechannel, font.red + protectee + font.reset + " banned from " + font.red + event.target + font.reset + " by " + font.red + event.source.nick + font.reset + ": " + font.green + mode[2])
+    #
+    #             if mode[1] == "e" and fnmatch.fnmatch(self.protectees[protectee]['ident'], event.arguments[idx + 1]) and mode[0] == "-":   # Protectee's exception removed.
+    #                 connection.privmsg(self.homechannel, font.red + protectee + font.reset + " has had their exception removed from " + font.red + event.target + font.reset + " by " + font.red + event.source.nick + font.reset + ": " + font.green + event.arguments[idx + 1])
+    #
+    #     # Track channel keys.
+    #     if mode[1] == "k":  # Channel key changed.
+    #         if mode[0] == "+":  # Key set.
+    #             self.db.run("UPDATE channels SET key='" + mode[2] + "' WHERE name='" + event.target + "' AND network='" + self.network + "'")
+    #         else:   # Key removed.
+    #             self.db.run("UPDATE channels SET key=NULL WHERE name='" + event.target + "' AND network='" + self.network + "'")
+    #
+    #     # Unban if bot is banned from home channel
+    #     try:
+    #         if mode[1] == "b" and fnmatch.fnmatch(self.protectees[connection.get_nickname()]['ident'], mode[2]) and mode[0] == "+" and event.target == self.homechannel:
+    #             connection.privmsg("ChanServ", "UNBAN")
+    #             do_everything_to.unban(connection, event.target, connection.get_nickname(), mode[2])
+    #             connection.mode(event.target, "-b " + mode[2])
+    #     except:
+    #         pass
+    #
+    #     # Stop if not present in home channel.
+    #     if not self.homechannel in self.channels:
+    #         return
+    #
+    #     # Stop if offender is bot or bot owner
+    #     if event.source.nick == connection.get_nickname() or self.channels[self.homechannel].is_owner(event.source.nick):
+    #         return
+    #
+    #     for protectee in self.protectees:
+    #
+    #         # Stop if offender is atleast halfop in the home channel and offended is not owner.
+    #         if userstatus.atleast_halfop(self, event.source.nick, self.homechannel) and not self.channels[self.homechannel].is_owner(protectee):
+    #             return
+    #
+    #         try:    # Had "IndexError: list index out of range" on "if modes[1] == "b" and mode[0] == "+":  # Ban.".
+    #             if behaviour == "passive":    # Passive behaviour.
+    #                 return
+    #             elif behaviour == "defense_only":   # Defensive only behaviour.
+    #                 if mode[1] == "b" and mode[0] == "+":  # Ban.
+    #                     if fnmatch.fnmatch(self.protectees[protectee]['ident'], mode[2]):  # Protectee.
+    #                         do_everything_to.unban(connection, event.target, protectee, mode[2])
+    #                 elif mode[1] == "e" and mode[0] == "-":  # Removed exception.
+    #                     if fnmatch.fnmatch(self.protectees[protectee]['ident'], event.arguments[idx + 1]):  # Protectee.
+    #                         connection.mode(event.target, "+e " + mode[2])
+    #                 elif mode[0] == "-" and mode[1] in ["q", "a", "o", "h", "v"]:   # Channel right taken.
+    #                     ChanServ.give_mode(connection, event.target, mode[2], mode[1])
+    #                     connection.mode(event.target, mode[0] + mode[1] + " " + mode[2])
+    #             elif behaviour == "equal_retalliation":   # Equal retaliatory behaviour.
+    #                 if modes[1] == "b" and mode[0] == "+":  # Ban.
+    #                     if fnmatch.fnmatch(self.protectees[protectee]['ident'], mode[2]):  # Protectee.
+    #                         do_everything_to.unban(connection, event.target, protectee, mode[2])
+    #                         if protectee == connection.get_nickname():  # Bot banned.
+    #                             ChanServ.ban(connection, event.target, event.source.nick, "Aggression channel function = equal_retalliation.")
+    #                         else:
+    #                             ChanServ.ban(connection, event.target, event.source.nick, "Aggression channel function = equal_retalliation: " + protectee + " is an operator of " + connection.get_nickname() + ".")
+    #                         connection.mode(event.target, "+b " + event.source)
+    #                 elif mode[1] == "e" and mode[0] == "-":  # Removed exception.
+    #                     if fnmatch.fnmatch(self.protectees[protectee]['ident'], event.arguments[idx + 1]):  # Protectee.
+    #                         connection.mode(event.target, "+e " + event.arguments[idx + 1])
+    #                 elif mode[0] == "-" and mode[1] in ["q", "a", "o", "h", "v"]:   # Channel right taken.
+    #                     ChanServ.give_mode(connection, event.target, mode[2], mode[1])
+    #                     connection.mode(event.target, mode[0] + mode[1] + " " + mode[2])
+    #                     ChanServ.take_all_modes(connection, event.target, mode[2])
+    #                     connection.mode(event.target, "-vhoaq " + mode[2] + " " + mode[2]+ " " + mode[2]+ " " + mode[2]+ " " + mode[2])
+    #             elif behaviour == "battlebot":  # Battlebot behaviour.
+    #                 if mode[0] == "-" and mode[1] in ["q", "a", "o", "h", "v"]:   # Channel right taken.
+    #                     ChanServ.give_mode(connection, event.target, mode[2], mode[1])
+    #                     connection.mode(event.target, mode[0] + mode[1] + " " + mode[2])
+    #                     do_everything_to.unban(connection, event.target, protectee, mode[2])
+    #                     do_everything_to.ban(connection, event.target, event.source.nick, event.source, Aggressiveness.retalliation_reason(self, connection, protectee, behaviour))
+    #                     connection.mode(event.target, "+e " + mode[2])
+    #                     ChanServ.akick_add(connection, event.target, event.source.nick)
+    #                     do_everything_to.kick(connection, event.target, event.source.nick, Aggressiveness.retalliation_reason(self, connection, protectee, behaviour))
+    #                 if mode[1] == "b" and mode[0] == "+":  # Ban.
+    #                     if fnmatch.fnmatch(self.protectees[protectee]['ident'], mode[2]):  # Protectee.
+    #                         do_everything_to.unban(connection, event.target, protectee, mode[2])
+    #                         do_everything_to.ban(connection, event.target, event.source.nick, event.source, Aggressiveness.retalliation_reason(self, connection, protectee, behaviour))
+    #                         connection.mode(event.target, "+e " + mode[2])
+    #                         ChanServ.akick_add(connection, event.target, event.source.nick)
+    #                         do_everything_to.kick(connection, event.target, event.source.nick, Aggressiveness.retalliation_reason(self, connection, protectee, behaviour))
+    #                 elif mode[1] == "e" and mode[0] == "-":  # Removed exception.
+    #                     #for protectee in self.protectees:
+    #                     if fnmatch.fnmatch(self.protectees[protectee]['ident'], event.arguments[idx + 1]):  # Protectee.
+    #                         do_everything_to.ban(connection, event.target, event.source.nick, event.source, Aggressiveness.retalliation_reason(self, connection, protectee, behaviour))
+    #                         connection.mode(event.target, "+e " + event.arguments[idx + 1])
+    #                         ChanServ.akick_add(connection, event.target, event.source.nick)
+    #                         do_everything_to.kick(connection, event.target, event.source.nick, Aggressiveness.retalliation_reason(self, connection, protectee, behaviour))
+    #         except:
+    #             pass

+ 11 - 12
rotbot/events/on_nick.py

@@ -1,20 +1,19 @@
-from common import log
+from common import log, queries
 from common.networkservices import NickServ
-from events.common import Lastact
 
 def process_event(self, connection, event):
     log.info(event)
-    
+
+    # Get and update resources.
+    old_user = queries.create_or_get_and_update_last_event(self, 'user', 'nc', user_name=event.source.nick, event_subject=event.target)
+    new_user = queries.create_or_get_and_update_last_event(self, 'user', 'nc', user_name=event.target, event_subject=event.source.nick)
+
     # Keep preferred nick.
     if event.source.nick == connection.nickname:    # Preffered nick being changed.
         log.info("Assuming original nick.")
         NickServ.recover_nick(connection, self.password)
-    
-    # Update protectees.
-    if event.source.nick in self.protectees:    # Protectee chaning nick.
-        del self.protectees[event.source.nick]  # Remove old nick from list.
-        connection.who(event.target)    # Get whorepy to add new nick to protectees.
-    
-    # Update last act.
-    Lastact.update(self, event.source.nick, "nick", lastact=event.target)
-    
+
+    # # Update protectees.
+    # if event.source.nick in self.protectees:    # Protectee chaning nick.
+    #     del self.protectees[event.source.nick]  # Remove old nick from list.
+    #     connection.who(event.target)    # Get whorepy to add new nick to protectees.

+ 11 - 6
rotbot/events/on_pubmsg.py

@@ -1,13 +1,18 @@
-import datetime
-from events.common import Replyto,  Lastact, MessageStatistics
+import commands.public, commands.admin, commands.games, commands.statistics
+from common import log, queries
+
 
 def process_event(self, connection, event):
+    # Let's not log all public messages to the console.
 
-    # # Update last act.
-    # Lastact.update(self, event.source.nick, "msg", channel=event.target, lastact=event.arguments[0])
+    # Get and update resources.
+    channel = queries.create_or_get_and_update_last_event(self, 'channel', 'cj', channel_name=event.target, user_name=event.source.nick)
+    user = queries.create_or_get_and_update_last_event(self, 'user', 'cj', channel_name=event.target, user_name=event.source.nick)
 
-    # Save statistic to database.
-    MessageStatistics.update(self, event, 'message')
+    commands.public.do_command(self, connection, event, user, channel)
+    commands.admin.do_command(self, connection, event, user, channel)
+    commands.statistics.do_command(self, connection, event, user, channel)
+    commands.games.do_command(self, connection, event, user, channel)
 
     # # Stop if channelfunction chat if off.
     # if not self.db.one("SELECT chat FROM channels WHERE name='" + event.target + "' AND network='" + self.network + "'"):

+ 3 - 3
website/dancecalendar/views.py

@@ -1,6 +1,6 @@
 from django.shortcuts import render
 from django.contrib.auth.decorators import login_required, permission_required
-
+from django.urls import reverse
 from .forms import EventForm
 
 def keywords(additional_keywords=None):
@@ -22,7 +22,7 @@ def index(request):
 def events(request):
     context = {
         'parent_title': 'Dance calendar',
-        'parent_url': 'dancecalendar:index',
+        'parent_url': reverse('dancecalendar:index'),
         'parent_icon': 'calander alternate outline',
         'title': 'Events',
         'icon': 'sitemap',
@@ -40,7 +40,7 @@ def add_event(request):
         form = EventForm()
     context = {
         'parent_title': 'Dance calendar',
-        'parent_url': 'dancecalendar:index',
+        'parent_url': reverse('dancecalendar:index'),
         'parent_icon': 'calander alternate outline',
         'title': 'Add event',
         'icon': 'sitemap',

+ 3 - 3
website/knowledgebase/views.py

@@ -10,7 +10,7 @@ def index(request):
         article = article_decoration(article)
     context = {
         'parent_title': 'Knowledgebase',
-        'parent_url': 'knowledgebase:index',
+        'parent_url': reverse('knowledgebase:index'),
         'parent_icon': 'newspaper outline',
         'title': 'Index',
         'icon': 'list',
@@ -26,7 +26,7 @@ def article(request, article_slug):
     article = article_decoration(article)
     context = {
         'parent_title': 'Knowledgebase',
-        'parent_url': 'knowledgebase:index',
+        'parent_url': reverse('knowledgebase:index'),
         'parent_icon': 'newspaper outline',
         'title': article.title,
         'icon': 'file alternate outline',
@@ -42,7 +42,7 @@ def edit_article(request, article_slug):
     article = article_decoration(article)
     context = {
         'parent_title': 'Knowledgebase',
-        'parent_url': 'knowledgebase:index',
+        'parent_url': reverse('knowledgebase:index'),
         'parent_icon': 'newspaper outline',
         'title': 'Edit ' + article.title,
         'icon': 'file alternate outline',

+ 16 - 1
website/rotbot/forms.py

@@ -2,7 +2,7 @@ from django import forms
 from django.forms import ModelForm, CharField, SlugField
 from django.core.validators import validate_unicode_slug
 
-from .models import Network, Host
+from .models import Network, Host, Channel
 
 class NetworkForm(ModelForm):
     class Meta:
@@ -43,3 +43,18 @@ class HostForm(ModelForm):
             'address': forms.TextInput(attrs={'autocomplete': 'on'}),
             'ssl': forms.CheckboxInput(attrs={'_style': 'toggle',}),
         }
+
+class ChannelForm(ModelForm):
+    class Meta:
+        model=Channel
+        fields=['autojoin', 'statistic_commands', 'games']
+        labels={
+            'autojoin': '<i class="power icon"></i>Auto join',
+            'statistic_commands': '<i class="chart pie icon"></i>Statistic commands',
+            'games': '<i class="gamepad icon"></i>Games',
+        }
+        widgets={
+            'autojoin': forms.CheckboxInput(attrs={'_style': 'inverted toggle'}),
+            'statistic_commands': forms.CheckboxInput(attrs={'_style': 'inverted toggle'}),
+            'games': forms.CheckboxInput(attrs={'_style': 'inverted toggle'}),
+        }

+ 54 - 18
website/rotbot/migrations/0001_initial.py

@@ -1,4 +1,4 @@
-# Generated by Django 2.2.6 on 2019-11-14 03:43
+# Generated by Django 2.2.6 on 2019-11-15 00:15
 
 import django.core.validators
 from django.db import migrations, models
@@ -14,15 +14,6 @@ class Migration(migrations.Migration):
     ]
 
     operations = [
-        migrations.CreateModel(
-            name='Channel',
-            fields=[
-                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('name', models.CharField(max_length=64)),
-                ('autojoin', models.BooleanField(default=False, null=True)),
-                ('key', models.CharField(max_length=32, null=True)),
-            ],
-        ),
         migrations.CreateModel(
             name='Network',
             fields=[
@@ -37,8 +28,6 @@ class Migration(migrations.Migration):
                 ('command_character', models.CharField(default='!', max_length=1, validators=[django.core.validators.MaxLengthValidator(1)])),
                 ('help_character', models.CharField(default='@', max_length=1, validators=[django.core.validators.MaxLengthValidator(1)])),
                 ('enabled', models.BooleanField(default=True)),
-                ('network_services', models.CharField(choices=[('m', 'Modern network services, like Rizon.'), ('x', 'X bot, like UnderNet.'), ('n', 'None, bare server.')], default='m', max_length=1)),
-                ('mute', models.BooleanField(default=False)),
             ],
             options={
                 'ordering': ['name'],
@@ -48,21 +37,54 @@ class Migration(migrations.Migration):
             name='User',
             fields=[
                 ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('slug', models.SlugField(unique=True, validators=[django.core.validators.RegexValidator(re.compile('^[-\\w]+\\Z'), "Enter a valid 'slug' consisting of Unicode letters, numbers, underscores, or hyphens.", 'invalid')])),
                 ('name', models.CharField(max_length=31)),
                 ('aliasses', models.ManyToManyField(related_name='_user_aliasses_+', to='rotbot.User')),
                 ('network', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='rotbot.Network')),
             ],
+            options={
+                'unique_together': {('network', 'name')},
+            },
+        ),
+        migrations.CreateModel(
+            name='Channel',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('slug', models.SlugField(unique=True, validators=[django.core.validators.RegexValidator(re.compile('^[-\\w]+\\Z'), "Enter a valid 'slug' consisting of Unicode letters, numbers, underscores, or hyphens.", 'invalid')])),
+                ('name', models.CharField(max_length=64)),
+                ('autojoin', models.BooleanField(default=False)),
+                ('key', models.CharField(max_length=32, null=True)),
+                ('network', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='channel', related_query_name='channels', to='rotbot.Network')),
+            ],
+            options={
+                'unique_together': {('network', 'name')},
+            },
+        ),
+        migrations.CreateModel(
+            name='Notice',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('amount', models.PositiveIntegerField(default=0)),
+                ('channel', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='rotbot.Channel')),
+                ('network', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='rotbot.Network')),
+                ('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='rotbot.User')),
+            ],
+            options={
+                'unique_together': {('network', 'channel', 'user')},
+            },
         ),
         migrations.CreateModel(
             name='Message',
             fields=[
                 ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                 ('amount', models.PositiveIntegerField(default=0)),
-                ('type', models.CharField(choices=[('m', 'Message'), ('a', 'Action'), ('n', 'Notice')], max_length=1)),
                 ('channel', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='rotbot.Channel')),
                 ('network', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='rotbot.Network')),
                 ('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='rotbot.User')),
             ],
+            options={
+                'unique_together': {('network', 'channel', 'user')},
+            },
         ),
         migrations.CreateModel(
             name='Kick',
@@ -74,6 +96,9 @@ class Migration(migrations.Migration):
                 ('kicker', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='kicker', to='rotbot.User')),
                 ('network', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='rotbot.Network')),
             ],
+            options={
+                'unique_together': {('network', 'channel', 'kicker', 'kicked')},
+            },
         ),
         migrations.CreateModel(
             name='Join',
@@ -84,11 +109,9 @@ class Migration(migrations.Migration):
                 ('network', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='rotbot.Network')),
                 ('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='rotbot.User')),
             ],
-        ),
-        migrations.AddField(
-            model_name='channel',
-            name='network',
-            field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='channel', related_query_name='channels', to='rotbot.Network'),
+            options={
+                'unique_together': {('network', 'channel', 'user')},
+            },
         ),
         migrations.CreateModel(
             name='Host',
@@ -106,4 +129,17 @@ class Migration(migrations.Migration):
                 'order_with_respect_to': 'network',
             },
         ),
+        migrations.CreateModel(
+            name='Action',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('amount', models.PositiveIntegerField(default=0)),
+                ('channel', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='rotbot.Channel')),
+                ('network', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='rotbot.Network')),
+                ('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='rotbot.User')),
+            ],
+            options={
+                'unique_together': {('network', 'channel', 'user')},
+            },
+        ),
     ]

+ 0 - 21
website/rotbot/migrations/0002_auto_20191114_1739.py

@@ -1,21 +0,0 @@
-# Generated by Django 2.2.6 on 2019-11-14 16:39
-
-from django.db import migrations
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('rotbot', '0001_initial'),
-    ]
-
-    operations = [
-        migrations.RemoveField(
-            model_name='network',
-            name='mute',
-        ),
-        migrations.RemoveField(
-            model_name='network',
-            name='network_services',
-        ),
-    ]

+ 34 - 0
website/rotbot/migrations/0002_auto_20191115_0606.py

@@ -0,0 +1,34 @@
+# Generated by Django 2.2.6 on 2019-11-15 05:06
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('rotbot', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='channel',
+            name='games',
+            field=models.BooleanField(default=False),
+        ),
+        migrations.AlterField(
+            model_name='channel',
+            name='key',
+            field=models.CharField(default='', max_length=32),
+            preserve_default=False,
+        ),
+        migrations.CreateModel(
+            name='TempChannelKey',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('key', models.CharField(max_length=40, unique=True)),
+                ('channel', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rotbot.Channel')),
+                ('network', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rotbot.Network')),
+            ],
+        ),
+    ]

+ 0 - 30
website/rotbot/migrations/0003_auto_20191114_1920.py

@@ -1,30 +0,0 @@
-# Generated by Django 2.2.6 on 2019-11-14 18:20
-
-from django.db import migrations, models
-import django.db.models.deletion
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('rotbot', '0002_auto_20191114_1739'),
-    ]
-
-    operations = [
-        migrations.CreateModel(
-            name='MessageTypes',
-            fields=[
-                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('type', models.CharField(choices=[('m', 'Message'), ('a', 'Action'), ('n', 'Notice')], max_length=1)),
-            ],
-        ),
-        migrations.RemoveField(
-            model_name='message',
-            name='type',
-        ),
-        migrations.AlterField(
-            model_name='message',
-            name='user',
-            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rotbot.User'),
-        ),
-    ]

+ 20 - 0
website/rotbot/migrations/0003_tempchannelkey_created.py

@@ -0,0 +1,20 @@
+# Generated by Django 2.2.6 on 2019-11-15 06:51
+
+from django.db import migrations, models
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('rotbot', '0002_auto_20191115_0606'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='tempchannelkey',
+            name='created',
+            field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
+            preserve_default=False,
+        ),
+    ]

+ 0 - 42
website/rotbot/migrations/0004_auto_20191114_2005.py

@@ -1,42 +0,0 @@
-# Generated by Django 2.2.6 on 2019-11-14 19:05
-
-from django.db import migrations, models
-import django.db.models.deletion
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('rotbot', '0003_auto_20191114_1920'),
-    ]
-
-    operations = [
-        migrations.CreateModel(
-            name='Action',
-            fields=[
-                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('amount', models.PositiveIntegerField(default=0)),
-                ('channel', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='rotbot.Channel')),
-                ('network', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='rotbot.Network')),
-                ('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='rotbot.User')),
-            ],
-        ),
-        migrations.CreateModel(
-            name='Notice',
-            fields=[
-                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('amount', models.PositiveIntegerField(default=0)),
-                ('channel', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='rotbot.Channel')),
-                ('network', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='rotbot.Network')),
-                ('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='rotbot.User')),
-            ],
-        ),
-        migrations.DeleteModel(
-            name='MessageTypes',
-        ),
-        migrations.AlterField(
-            model_name='message',
-            name='user',
-            field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='rotbot.User'),
-        ),
-    ]

+ 56 - 0
website/rotbot/migrations/0004_auto_20191116_0255.py

@@ -0,0 +1,56 @@
+# Generated by Django 2.2.6 on 2019-11-16 01:55
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('rotbot', '0003_tempchannelkey_created'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='channel',
+            name='last_event_type',
+            field=models.CharField(choices=[('cm', 'channel message'), ('ca', 'channel action'), ('cn', 'channel notice'), ('ct', 'channel topic'), ('ck', 'channel password'), ('ci', 'channel invite'), ('cj', 'channel join'), ('cp', 'channel part'), ('ck', 'channel kick'), ('kd', 'channel kicked'), ('mc', 'mode change')], default='cm', max_length=2),
+            preserve_default=False,
+        ),
+        migrations.AddField(
+            model_name='channel',
+            name='statistic_commands',
+            field=models.BooleanField(default=False),
+        ),
+        migrations.AddField(
+            model_name='user',
+            name='last_event_channel',
+            field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='rotbot.Channel'),
+        ),
+        migrations.AddField(
+            model_name='user',
+            name='last_event_content',
+            field=models.CharField(max_length=307, null=True),
+        ),
+        migrations.AddField(
+            model_name='user',
+            name='last_event_datetime',
+            field=models.DateTimeField(auto_now=True),
+        ),
+        migrations.AddField(
+            model_name='user',
+            name='last_event_subject',
+            field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='subject', to='rotbot.User'),
+        ),
+        migrations.AddField(
+            model_name='user',
+            name='last_event_type',
+            field=models.CharField(choices=[('pm', 'private message'), ('pa', 'private action'), ('pn', 'private notice'), ('cm', 'channel message'), ('ca', 'channel action'), ('cn', 'channel notice'), ('ct', 'channel topic'), ('ck', 'channel password'), ('ci', 'channel invite'), ('cj', 'channel join'), ('cp', 'channel part'), ('ck', 'channel kick'), ('kd', 'channel kicked'), ('mc', 'mode change'), ('nc', 'nick change'), ('sq', 'quit')], default='cm', max_length=2),
+            preserve_default=False,
+        ),
+        migrations.AddField(
+            model_name='user',
+            name='last_event_user',
+            field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='rotbot.User'),
+        ),
+    ]

+ 49 - 0
website/rotbot/migrations/0005_auto_20191116_0315.py

@@ -0,0 +1,49 @@
+# Generated by Django 2.2.6 on 2019-11-16 02:15
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('rotbot', '0004_auto_20191116_0255'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='channel',
+            name='last_event_channel',
+            field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='rotbot.Channel'),
+        ),
+        migrations.AddField(
+            model_name='channel',
+            name='last_event_content',
+            field=models.CharField(max_length=307, null=True),
+        ),
+        migrations.AddField(
+            model_name='channel',
+            name='last_event_datetime',
+            field=models.DateTimeField(auto_now=True),
+        ),
+        migrations.AddField(
+            model_name='channel',
+            name='last_event_subject',
+            field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='channel_subject', to='rotbot.User'),
+        ),
+        migrations.AddField(
+            model_name='channel',
+            name='last_event_user',
+            field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='rotbot.User'),
+        ),
+        migrations.AlterField(
+            model_name='channel',
+            name='last_event_type',
+            field=models.CharField(choices=[('pm', 'private message'), ('pa', 'private action'), ('pn', 'private notice'), ('cm', 'channel message'), ('ca', 'channel action'), ('cn', 'channel notice'), ('ct', 'channel topic'), ('ck', 'channel password'), ('ci', 'channel invite'), ('cj', 'channel join'), ('cp', 'channel part'), ('ck', 'channel kick'), ('kd', 'channel kicked'), ('mc', 'mode change'), ('nc', 'nick change'), ('sq', 'quit')], max_length=2),
+        ),
+        migrations.AlterField(
+            model_name='user',
+            name='last_event_subject',
+            field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='user_subject', to='rotbot.User'),
+        ),
+    ]

+ 166 - 3
website/rotbot/models.py

@@ -1,7 +1,6 @@
 from django.db import models
 from django.core.validators import validate_unicode_slug, MaxLengthValidator, MaxValueValidator, URLValidator
 
-# Create your models here.
 class Network(models.Model):
     name = models.CharField(
         max_length=40,
@@ -112,6 +111,10 @@ class Host(models.Model):
         return '%s:%s' % (self.address, self.port)
 
 class Channel(models.Model):
+    slug = models.SlugField(
+        unique=True,
+        validators=[validate_unicode_slug],
+    )
     network = models.ForeignKey(
         'Network',
         on_delete=models.PROTECT,
@@ -123,18 +126,115 @@ class Channel(models.Model):
     )
     autojoin = models.BooleanField(
         default=False,
-        null=True,
-
+        #null=True,
+        #blank=False
     )
     key = models.CharField(
         max_length=32,
+    )
+    games = models.BooleanField(
+        default=False,
+    )
+    statistic_commands = models.BooleanField(
+        default=False,
+    )
+    EVENT_TYPE_CHOICES = [
+        #('pm', 'private message'),
+        #('pa', 'private action'),
+        #('pn', 'private notice'),
+        ('cm', 'channel message'),
+        ('ca', 'channel action'),
+        ('cn', 'channel notice'),
+        ('ct', 'channel topic'),
+        ('ck', 'channel password'),
+        ('ci', 'channel invite'),
+        ('cj', 'channel join'),
+        ('cp', 'channel part'),
+        ('ck', 'channel kick'),
+        ('kd', 'channel kicked'),
+        ( 'mc', 'mode change'),
+        #('nc', 'nick change'),
+        #('sq', 'quit'),
+    ]
+    last_event_type = models.CharField(
+        max_length=2,
+        choices=EVENT_TYPE_CHOICES,
+    )
+    EVENT_TYPE_CHOICES = [
+        ('pm', 'private message'),
+        ('pa', 'private action'),
+        ('pn', 'private notice'),
+        ('cm', 'channel message'),
+        ('ca', 'channel action'),
+        ('cn', 'channel notice'),
+        ('ct', 'channel topic'),
+        ('ck', 'channel password'),
+        ('ci', 'channel invite'),
+        ('cj', 'channel join'),
+        ('cp', 'channel part'),
+        ('ck', 'channel kick'),
+        ('kd', 'channel kicked'),
+        ('mc', 'mode change'),
+        ('nc', 'nick change'),
+        ('sq', 'quit'),
+    ]
+    last_event_type = models.CharField(
+        max_length=2,
+        choices=EVENT_TYPE_CHOICES,
+    )
+    last_event_datetime = models.DateTimeField(
+        auto_now=True,
+    )
+    last_event_channel = models.ForeignKey(
+        'Channel',
+        on_delete=models.CASCADE,
         null=True,
     )
+    last_event_user = models.ForeignKey(
+        'User',
+        on_delete=models.CASCADE,
+        null=True,
+    )
+    last_event_subject = models.ForeignKey(
+        'User',
+        on_delete=models.CASCADE,
+        null=True,
+        related_name='channel_subject',
+    )
+    last_event_content = models.CharField(
+        null=True,
+        max_length = 307,
+    )
+
+
+    class Meta:
+        unique_together = ['network', 'name']
 
     def __str__(self):
         return '%s/$s' % (self.network, self.name)
 
+class TempChannelKey(models.Model):
+    key = models.CharField(
+        max_length=40,
+        unique=True,
+    )
+    network = models.ForeignKey(
+        'Network',
+        on_delete=models.CASCADE,
+    )
+    channel = models.ForeignKey(
+        'Channel',
+        on_delete=models.CASCADE,
+    )
+    created = models.DateTimeField(
+        auto_now_add=True,
+    )
+
 class User(models.Model):
+    slug = models.SlugField(
+        unique=True,
+        validators=[validate_unicode_slug],
+    )
     network = models.ForeignKey(
         'Network',
         on_delete=models.PROTECT,
@@ -143,6 +243,54 @@ class User(models.Model):
         max_length=31
     )
     aliasses = models.ManyToManyField('self')
+    EVENT_TYPE_CHOICES = [
+        ('pm', 'private message'),
+        ('pa', 'private action'),
+        ('pn', 'private notice'),
+        ('cm', 'channel message'),
+        ('ca', 'channel action'),
+        ('cn', 'channel notice'),
+        ('ct', 'channel topic'),
+        ('ck', 'channel password'),
+        ('ci', 'channel invite'),
+        ('cj', 'channel join'),
+        ('cp', 'channel part'),
+        ('ck', 'channel kick'),
+        ('kd', 'channel kicked'),
+        ('mc', 'mode change'),
+        ('nc', 'nick change'),
+        ('sq', 'quit'),
+    ]
+    last_event_type = models.CharField(
+        max_length=2,
+        choices=EVENT_TYPE_CHOICES,
+    )
+    last_event_datetime = models.DateTimeField(
+        auto_now=True,
+    )
+    last_event_channel = models.ForeignKey(
+        'Channel',
+        on_delete=models.CASCADE,
+        null=True,
+    )
+    last_event_user = models.ForeignKey(
+        'User',
+        on_delete=models.CASCADE,
+        null=True,
+    )
+    last_event_subject = models.ForeignKey(
+        'User',
+        on_delete=models.CASCADE,
+        null=True,
+        related_name='user_subject',
+    )
+    last_event_content = models.CharField(
+        null=True,
+        max_length = 307,
+    )
+
+    class Meta:
+        unique_together = ['network', 'name']
 
 class Join(models.Model):
     network = models.ForeignKey(
@@ -161,6 +309,9 @@ class Join(models.Model):
         default=0,
     )
 
+    class Meta:
+        unique_together = ['network', 'channel', 'user']
+
 class Message(models.Model):
     network = models.ForeignKey(
         'Network',
@@ -178,6 +329,9 @@ class Message(models.Model):
         default=0,
     )
 
+    class Meta:
+        unique_together = ['network', 'channel', 'user']
+
 class Action(models.Model):
     network = models.ForeignKey(
         'Network',
@@ -195,6 +349,9 @@ class Action(models.Model):
         default=0,
     )
 
+    class Meta:
+        unique_together = ['network', 'channel', 'user']
+
 class Notice(models.Model):
     network = models.ForeignKey(
         'Network',
@@ -212,6 +369,9 @@ class Notice(models.Model):
         default=0,
     )
 
+    class Meta:
+        unique_together = ['network', 'channel', 'user']
+
 class Kick(models.Model):
     network = models.ForeignKey(
         'Network',
@@ -234,3 +394,6 @@ class Kick(models.Model):
     amount = models.PositiveIntegerField(
         default=0,
     )
+
+    class Meta:
+        unique_together = ['network', 'channel', 'kicker', 'kicked']

+ 61 - 0
website/rotbot/templates/rotbot/channel.html

@@ -0,0 +1,61 @@
+{% extends "base.html" %}
+{% load semanticui %}
+{% block content %}
+  <div class="ui five inverted grey statistics">
+    <div class="statistic">
+      <div class="value">
+        <i class="comment icon"></i> {{ total_messages }}
+      </div>
+      <div class="label">
+        messages
+      </div>
+    </div>
+    <div class="statistic">
+      <div class="value">
+        <i class="theater masks icon"></i> {{ total_actions }}
+      </div>
+      <div class="label">
+        Actions
+      </div>
+    </div>
+    <div class="statistic">
+      <div class="value">
+        <i class="bullhorn icon"></i> {{ total_notices }}
+      </div>
+      <div class="label">
+        Notces
+      </div>
+    </div>
+    <div class="statistic">
+      <div class="value">
+        <i class="user plus icon"></i> {{ total_joins }}
+      </div>
+      <div class="label">
+        Joins
+      </div>
+    </div>
+    <div class="statistic">
+      <div class="value">
+        <i class="user slash icon"></i> {{ total_kicks }}
+      </div>
+      <div class="label">
+        Kicks
+      </div>
+    </div>
+  </div>
+  {% if perms.rotbot.change_channel %}
+    <a href="{% url 'rotbot:channel_settings' channel.slug %}" class="ui inverted right floated button">Settings</a>
+  {% endif %}
+  {% if channel.autojoin and network.enabled or network.home_channel == channel.id and network.enambled %}
+    <i class="power icon"></i>
+  {% endif %}
+  {%if channel.key %}
+    <i class="privacy icon"></i>
+  {% endif %}
+  {% if channel.games %}
+    <i class="gamepad icon"></i>
+  {% endif %}
+  {% if channel.statistic_commands %}
+    <i class="chart pie icon"></i>
+  {% endif %}
+{% endblock content %}

+ 22 - 0
website/rotbot/templates/rotbot/channel_settings.html

@@ -0,0 +1,22 @@
+{% extends "base.html" %}
+{% load semanticui %}
+{% block content %}
+  {% if key_expired %}
+    Request expired, 10 minutes have passed.
+  {% else %}
+    <form class= "ui form" method="post" action="
+      {% if temp_key %}
+        {% url 'rotbot:ircauth_channel_settings' temp_key %}
+      {% else %}
+        {% url 'rotbot:channel_settings' channel_slug %}
+      {% endif %}
+    ">
+      {% csrf_token %}
+      {% render_form form %}
+      <button class="ui right floated inverted positive button" type="submit" value="Submit"><i class="save icon"></i>Save</button>
+      <a class="ui right floated inverted basic negative button" href="{% url 'rotbot:channel' channel_slug %}">
+        <i class="hand point left icon"></i>Back
+      </a>
+    </form>
+  {% endif %}
+{% endblock content %}

+ 67 - 8
website/rotbot/templates/rotbot/network.html

@@ -3,7 +3,15 @@
   <div class="ui three inverted grey statistics">
     <div class="statistic">
       <div class="value">
-        <i class="hashtag icon"></i> {{ channel_amount }}
+        <i class="server icon"></i> {{ total_hosts }}
+      </div>
+      <div class="label">
+        Hosts
+      </div>
+    </div>
+    <div class="statistic">
+      <div class="value">
+        <i class="hashtag icon"></i> {{ total_chanels }}
       </div>
       <div class="label">
         Channels
@@ -11,21 +19,57 @@
     </div>
     <div class="statistic">
       <div class="value">
-        <i class="users icon"></i> {{ user_amount }}
+        <i class="users icon"></i> {{ total_users }}
       </div>
       <div class="label">
         Users
       </div>
     </div>
+  </div>
+  <div class="ui five tiny inverted grey statistics">
     <div class="statistic">
       <div class="value">
-        <i class="comments outline icon"></i> {{ message_amount }}
+        <i class="comments icon"></i> {{ total_messages }}
       </div>
       <div class="label">
         Messages
       </div>
     </div>
+    <div class="statistic">
+      <div class="value">
+        <i class="theater masks icon"></i> {{ total_actions }}
+      </div>
+      <div class="label">
+        Actions
+      </div>
+    </div>
+    <div class="statistic">
+      <div class="value">
+        <i class="bullhorn icon"></i> {{ total_notices }}
+      </div>
+      <div class="label">
+        Notices
+      </div>
+    </div>
+    <div class="statistic">
+      <div class="value">
+        <i class="user plus icon"></i> {{ total_joins }}
+      </div>
+      <div class="label">
+        Joins
+      </div>
+    </div>
+    <div class="statistic">
+      <div class="value">
+        <i class="user slash icon"></i> {{ total_kicks }}
+      </div>
+      <div class="label">
+        Kicks
+      </div>
+    </div>
   </div>
+
+
   <div class="ui inverted card">
     <div class="content">
       <div class="header" title="Network">
@@ -38,13 +82,19 @@
         {% endif %}
       </div>
       <div class="ui list">
-        {% for host in network.host.all %}
+        {% for host in hosts %}
           <div class="item" title="Server">
             <i class="server icon"></i>
-            <div class="content">{{ host }}
-            {% if host.ssl %}
-              <i class="green lock icon" title="Encrypted connection"></i>
-            {% endif %}
+            <div class="content">
+              {{ host }}
+              {% if host.ssl %}
+                <i class="green lock icon" title="Encrypted connection"></i>
+              {% endif %}
+              <span class="right floated">
+                <i class="ethernet icon"></i>
+                <span class="ui inverted green text">{{ host.connection_succeeds }}</span>
+                <span class="ui inverted red text">{{ host.connection_fails }}</span>
+              </span>
             </div>
           </div>
         {% endfor %}
@@ -93,6 +143,15 @@
       </div>
     </div>
   </div>
+  {% if channels %}
+    <div class="ui inverted relaxed divided selection list">
+      {% for channel in channels %}
+        <div class="item" onclick="location.href='{% url 'rotbot:channel' channel.slug %}';">
+          <a class ="header" href="{% url 'rotbot:channel' channel.slug %}">{{ channel.name }}</a>
+        </div>
+      {% endfor %}
+    </div>
+  {% endif %}
 {% endblock content %}
 
 

+ 1 - 5
website/rotbot/templates/rotbot/network_form.html

@@ -1,8 +1,4 @@
 {% extends "base.html" %}
-{% block title %}
-
-
-{% endblock title %}
 {% load semanticui %}
 {% block content %}
 
@@ -158,7 +154,7 @@
       </div>
     {% endif %}
   </div>
-  
+
 
 <button class="ui right floated inverted positive button" type="submit" value="Submit"><i class="save icon"></i>Save</button>
 </form>

+ 46 - 4
website/rotbot/templates/rotbot/networks.html

@@ -3,7 +3,7 @@
   <div class="ui four inverted grey statistics">
     <div class="statistic">
       <div class="value">
-        <i class="sitemap icon"></i> {{ network_amount }}
+        <i class="sitemap icon"></i> {{ total_networks }}
       </div>
       <div class="label">
         Networks
@@ -11,7 +11,15 @@
     </div>
     <div class="statistic">
       <div class="value">
-        <i class="hashtag icon"></i> {{ channel_amount }}
+        <i class="server icon"></i> {{ total_hosts }}
+      </div>
+      <div class="label">
+        Hosts
+      </div>
+    </div>
+    <div class="statistic">
+      <div class="value">
+        <i class="hashtag icon"></i> {{ total_chanels }}
       </div>
       <div class="label">
         Channels
@@ -19,20 +27,54 @@
     </div>
     <div class="statistic">
       <div class="value">
-        <i class="users icon"></i> {{ user_amount }}
+        <i class="users icon"></i> {{ total_users }}
       </div>
       <div class="label">
         Users
       </div>
     </div>
+  </div>
+  <div class="ui five tiny inverted grey statistics">
     <div class="statistic">
       <div class="value">
-        <i class="comments outline icon"></i> {{ message_amount }}
+        <i class="comments  icon"></i> {{ total_messages }}
       </div>
       <div class="label">
         Messages
       </div>
     </div>
+    <div class="statistic">
+      <div class="value">
+        <i class="theater masks icon"></i> {{ total_actions }}
+      </div>
+      <div class="label">
+        Actions
+      </div>
+    </div>
+    <div class="statistic">
+      <div class="value">
+        <i class="bullhorn icon"></i> {{ total_notices }}
+      </div>
+      <div class="label">
+        Notices
+      </div>
+    </div>
+    <div class="statistic">
+      <div class="value">
+        <i class="user plus icon"></i> {{ total_joins }}
+      </div>
+      <div class="label">
+        Joins
+      </div>
+    </div>
+    <div class="statistic">
+      <div class="value">
+        <i class="user slash icon"></i> {{ total_kicks }}
+      </div>
+      <div class="label">
+        Kicks
+      </div>
+    </div>
   </div>
   {% if perms.rotbot.add_network %}
     <a href="{% url 'rotbot:add_network' %}" class="ui inverted right floated button">Add</a>

+ 55 - 0
website/rotbot/templates/rotbot/user.html

@@ -0,0 +1,55 @@
+{% extends "base.html" %}
+{% block content %}
+  <div class="ui three inverted grey statistics">
+    <div class="statistic">
+      <div class="value">
+        <i class="comment icon"></i> {{ total_messages }}
+      </div>
+      <div class="label">
+        messages
+      </div>
+    </div>
+    <div class="statistic">
+      <div class="value">
+        <i class="theater masks icon"></i> {{ total_actions }}
+      </div>
+      <div class="label">
+        Actions
+      </div>
+    </div>
+    <div class="statistic">
+      <div class="value">
+        <i class="bullhorn icon"></i> {{ total_notices }}
+      </div>
+      <div class="label">
+        Notces
+      </div>
+    </div>
+  </div>
+  <div class="ui three tiny inverted grey statistics">
+    <div class="statistic">
+      <div class="value">
+        <i class="user plus icon"></i> {{ total_joins }}
+      </div>
+      <div class="label">
+        Joins
+      </div>
+    </div>
+    <div class="statistic">
+      <div class="value">
+        <i class="jet fighter icon"></i> {{ total_kicks }}
+      </div>
+      <div class="label">
+        Kicks
+      </div>
+    </div>
+    <div class="statistic">
+      <div class="value">
+        <i class="user slash icon"></i> {{ total_kicked }}
+      </div>
+      <div class="label">
+        Kicked
+      </div>
+    </div>
+  </div>
+{% endblock content %}

+ 4 - 0
website/rotbot/urls.py

@@ -9,4 +9,8 @@ urlpatterns = [
     path('network/<str:network_slug>/', views.network, name='network'),
     path('network/<str:network_slug>/edit/', views.edit_network, name='edit_network'),
     path('network/<str:network_slug>/delete/', views.delete_network, name='delete_network'),
+    path('channel/<str:channel_slug>/', views.channel, name='channel'),
+    path('channel/<str:channel_slug>/settings/', views.channel_settings, name='channel_settings'),
+    path('channelsettings/<str:temp_key>', views.ircauth_channel_settings, name='ircauth_channel_settings'),
+    path('user/<str:user_slug>/', views.user, name='user'),
 ]

+ 180 - 29
website/rotbot/views.py

@@ -1,13 +1,13 @@
+import datetime
 from django.shortcuts import render, get_object_or_404
 from django.http import HttpResponseRedirect
 from django.urls import reverse
 #from django.views import generic
 from django.contrib.auth.decorators import login_required, permission_required
 from django.forms import modelformset_factory, inlineformset_factory
-
 from website.settings import APPLICATION_NAME
-from .models import Network, Host, Channel, User, Message, Action, Notice
-from .forms import NetworkForm, HostForm
+from .models import Network, Host, Channel, User, Message, Action, Notice, TempChannelKey, Join, Kick
+from .forms import NetworkForm, HostForm, ChannelForm
 
 def default_keywords(additional_keywords=None):
     default_keywords = 'RotBot, robot, bot, irc, irc bot, irc robot, '
@@ -19,19 +19,14 @@ def default_keywords(additional_keywords=None):
 
 def networks(request):
     networks = Network.objects.all()
+    hosts = Host.objects.all()
     channels = Channel.objects.all()
     users = User.objects.all()
     messages = Message.objects.all()
     actions = Action.objects.all()
     notices = Notice.objects.all()
-
-    total_messages = 0
-    for message in messages:
-        total_messages += message.amount
-    for action in actions:
-        total_messages += action.amount
-    for notice in notices:
-        total_messages += notice.amount
+    joins = Join.objects.all()
+    kicks = Kick.objects.all()
 
     context = {
         'title': 'RotBot',
@@ -39,43 +34,52 @@ def networks(request):
         'description': 'Index of RotBot, the IRC robot.',
         'keywords': default_keywords('index'),
         'networks': networks,
-        'network_amount': networks.count(),
-        'channel_amount': channels.count(),
-        'user_amount': users.count(),
-        'message_amount': total_messages,
+        'total_networks': shorten_number(networks.count()),
+        'total_hosts': shorten_number(hosts.count()),
+        'total_chanels': shorten_number(channels.count()),
+        'total_users': shorten_number(users.count()),
+        'total_messages': shorten_number(total_messages(messages)),
+        'total_actions': shorten_number(total_messages(actions)),
+        'total_notices': shorten_number(total_messages(notices)),
+        'total_joins': shorten_number(joins.count()),
+        'total_kicks': shorten_number(kicks.count()),
     }
     return render(request, 'rotbot/networks.html', context)
 
 
 def network(request, network_slug):
     network = get_object_or_404(Network, slug=network_slug)
+    hosts = Host.objects.filter(network=network)
     channels = Channel.objects.filter(network=network)
     users = User.objects.filter(network=network)
     messages = Message.objects.filter(network=network)
     actions = Action.objects.filter(network=network)
     notices = Notice.objects.filter(network=network)
+    joins = Join.objects.all()
+    kicks = Kick.objects.all()
 
-    total_messages = 0
-    for message in messages:
-        print(message.amount)
-        total_messages += message.amount
-    for action in actions:
-        total_messages += action.amount
-    for notice in notices:
-        total_messages += notice.amount
+    for host in hosts:
+        host.connection_fails = host.connection_attempts - host.connection_succeeds
 
     context = {
         'parent_title': 'Networks',
-        'parent_url': 'rotbot:networks',
+        'parent_url': reverse('rotbot:networks'),
         'parent_icon': 'robot',
         'title': network.name,
         'icon': 'sitemap',
         'description': 'Details of ' + network.name,
         'keywords': default_keywords() + 'network.name, display, details',
         'network': network,
-        'channel_amount': channels.count(),
-        'user_amount': users.count(),
-        'message_amount': total_messages,
+        'hosts': hosts,
+        'channels': channels,
+        'total_hosts': shorten_number(hosts.count()),
+        'total_chanels': shorten_number(channels.count()),
+        'total_users': shorten_number(users.count()),
+        'total_messages': shorten_number(total_messages(messages)),
+        'total_actions': shorten_number(total_messages(actions)),
+        'total_notices': shorten_number(total_messages(notices)),
+        'total_joins': shorten_number(joins.count()),
+        'total_kicks': shorten_number(kicks.count()),
     }
     return render(request, 'rotbot/network.html', context)
 
@@ -98,7 +102,7 @@ def edit_network(request, network_slug):
         formset = HostFormSet(instance=network)
     context = {
         'parent_title': 'Networks',
-        'parent_url': 'rotbot:networks',
+        'parent_url': reverse('rotbot:networks'),
         'parent_icon': 'robot',
         'title': title,
         'icon': 'sitemap',
@@ -129,7 +133,7 @@ def add_network(request):
         formset = HostFormSet(queryset=Host.objects.none())
     context = {
         'parent_title': 'Networks',
-        'parent_url': 'rotbot:networks',
+        'parent_url': reverse('rotbot:networks'),
         'parent_icon': 'robot',
         'title': 'Add network',
         'icon': 'sitemap',
@@ -147,3 +151,150 @@ def delete_network(request, network_slug):
     network = get_object_or_404(Network, slug=network_slug)
     network.delete()
     return HttpResponseRedirect(reverse('rotbot:networks'))
+
+def channel(request, channel_slug):
+    channel = get_object_or_404(Channel, slug=channel_slug)
+    network = Network.objects.get(id=channel.network.id)
+    messages = Message.objects.filter(channel=channel)
+    actions = Action.objects.filter(channel=channel)
+    notices = Notice.objects.filter(channel=channel)
+    joins = Join.objects.filter(channel=channel)
+    kicks = Kick.objects.filter(channel=channel)
+
+    context = {
+        'parent_title': network.name,
+        'parent_url': reverse('rotbot:network', args=(network.slug,)),
+        'parent_icon': 'sitemap',
+        'title': channel.name,
+        'icon': 'hashtag',
+        'description': 'Details of channel %s on network %s' % (channel.name, network.name),
+        'keywords': default_keywords() + '%s, %s' % (network.name, channel.name),
+        'channel': channel,
+        'network': network,
+        'total_messages': shorten_number(total_messages(messages)),
+        'total_actions': shorten_number(total_messages(actions)),
+        'total_notices': shorten_number(total_messages(notices)),
+        'total_joins': shorten_number(joins.count()),
+        'total_kicks': shorten_number(kicks.count()),
+    }
+    return render(request, 'rotbot/channel.html', context)
+
+@login_required
+@permission_required('rotbot.change_channel', raise_exception=True)
+def channel_settings(request, channel_slug):
+    channel = get_object_or_404(Channel, slug=channel_slug)
+    network = Network.objects.get(id=channel.network_id)
+
+    if request.method == 'POST':
+        form = ChannelForm(request.POST, instance=channel)
+        if form.is_valid():
+            form.save()
+            return HttpResponseRedirect(reverse('rotbot:channel', args=(channel.slug,)))
+
+    else:
+        form = ChannelForm(instance=channel)
+
+    context = {
+        'parent_title': network.name,
+        'parent_url': reverse('rotbot:network', args=(network.slug,)),
+        'parent_icon': 'sitemap',
+        'title': channel.name,
+        'icon': 'hashtag',
+        'description': 'Change settings for channel %s on network %s' % (channel.name, network.name),
+        'keywords': default_keywords() + 'settings, %s, %s' % (network.name, channel.name),
+        'channel_slug': channel.slug,
+        'network_slug': network.slug,
+        'form': form,
+    }
+    return render(request, 'rotbot/channel_settings.html', context)
+
+def ircauth_channel_settings(request, temp_key):
+    key = get_object_or_404(TempChannelKey, key=temp_key)
+    channel = Channel.objects.get(id=key.channel_id)
+    network = Network.objects.get(id=key.network_id)
+    if key.created > datetime.datetime.now() - datetime.timedelta(minute=10):
+        key_expired = False
+    else:
+        key_expired = True
+
+    if request.method == 'POST':
+        if recent_key:
+            form = ChannelForm(request.POST, instance=channel)
+            if form.is_valid():
+                form.save()
+                key.delete()
+                return HttpResponseRedirect(reverse('rotbot:channel', args=(channel.slug,)))
+
+    else:
+        form = ChannelForm(instance=channel)
+    context = {
+        'parent_title': network.name,
+        'parent_url': reverse('rotbot:network', args=(network.slug,)),
+        'parent_icon': 'sitemap',
+        'title': channel.name,
+        'icon': 'hashtag',
+        'description': 'Change settings for channel %s on network %s' % (channel.name, network.name),
+        'keywords': default_keywords() + 'settings, %s, %s' % (network.name, channel.name),
+        'channel_slug': channel.slug,
+        'network_slug': network.slug,
+        'form': form,
+        'temp_key': temp_key,
+        'key_expired': key_expired,
+    }
+    return render(request, 'rotbot/channel_settings.html', context)
+
+
+def user(request, user_slug):
+    user = get_object_or_404(User, slug=user_slug)
+    network = Network.objects.get(id=user.network.id)
+    messages = Message.objects.filter(user=user)
+    actions = Action.objects.filter(user=user)
+    notices = Notice.objects.filter(user=user)
+    joins = Join.objects.filter(user=user)
+    kicks = Kick.objects.filter(kicker=user)
+    kicked = Kick.objects.filter(kicked=user)
+
+    context = {
+        'parent_title': network.name,
+        'parent_url': reverse('rotbot:network', args=(network.slug,)),
+        'parent_icon': 'sitemap',
+        'title': user.name,
+        'icon': 'hashtag',
+        'description': 'Details of user %s on network %s' % (user.name, network.name),
+        'keywords': default_keywords() + '%s, %s' % (network.name, user.name),
+        'user': user,
+        'network': network,
+        'total_messages': shorten_number(total_messages(messages)),
+        'total_actions': shorten_number(total_messages(actions)),
+        'total_notices': shorten_number(total_messages(notices)),
+        'total_joins': shorten_number(joins.count()),
+        'total_kicks': shorten_number(kicked.count()),
+        'total_kicked': shorten_number(kicked.count()),
+    }
+    return render(request, 'rotbot/user.html', context)
+
+# Helpers
+def total_messages(messages):
+    total_messages = 0
+    for message in messages:
+        total_messages += message.amount
+    return total_messages
+
+def shorten_number (number):
+    if len(str(number)) > 20:
+        number = str(int(number /1000000000000000000)) +"E"
+    elif len(str(number)) > 17:
+        number = str(int(number /1000000000000000)) +"P"
+    elif len(str(number)) > 14:
+        number = str(int(number /1000000000000)) +"T"
+    elif len(str(number)) > 11:
+        number = str(int(number /1000000000)) +"G"
+    elif len(str(number)) > 8:
+        number = str(int(number /1000000)) +"M"
+    elif len(str(number)) > 5:
+        number = str(int(number /1000)) + "K"
+    elif len(str(number)) > 4:
+        number = str(int(number /100)) + "h"
+    else:
+        number = int(number)
+    return str(number)

+ 1 - 1
website/templates/base.html

@@ -26,7 +26,7 @@
       <a class="item" href="{% url 'index' %}"><i class="globe icon"></i>{{ settings.APPLICATION_NAME }}</a>
       {% if title %}
         {% if parent_title %}
-          <a class="item" href="{% url parent_url %}"><i class="{{ parent_icon }} icon"></i>{{ parent_title }}</a>
+          <a class="item" href="{{ parent_url }}"><i class="{{ parent_icon }} icon"></i>{{ parent_title }}</a>
         {% endif %}
         <a class="active item" href="{{ request.path }}">
           <i class="{{ icon }} icon"></i>