瀏覽代碼

minor improvements

tBKwtWS 6 年之前
父節點
當前提交
aab5a15041

+ 11 - 6
rotbot/commands/chat.py

@@ -30,18 +30,18 @@ def do_command(self, connection, event, user, channel):
 
     elif one == 'vocabulary' or one == 'vocab':
         curse = queries.random_curse(self)
-        categories = ['curseword', 'adjective', 'nickreply']
+        categories = ['curseWord', 'adjective', 'nickReplyAction', 'nickReplyMessage']
         if len(command.split()) == 1:    # Command without arguments
             if cmdtype == 'help':
                 connection.privmsg(replyto, 'Add, display or ban a vocabulary item. Option and item are optional arguments.')
                 connection.privmsg(replyto, '%sUsage:%s %s%s %scatagory option item' % (font.grey, font.blue, self.network.command_character, one, font.italic))
-                connection.privmsg(replyto, 'List categories: %s%s%slist categories%s List options: %s%s%slist options' % (font.blue, self.network.command_character, one, font.reset, font.blue, self.network.command_character, one))
+                connection.privmsg(replyto, 'List categories: %s%s%s list categories%s List options: %s%s%s list options%s List definitions: %s%s%s list definitions' % (font.blue, self.network.command_character, one, font.reset, font.blue, self.network.command_character, one, font.reset, font.blue, self.network.command_character, one))
             else:   # Actual command without arguments.
                 connection.privmsg(replyto, 'Insuficcient arguments. For help type: %s%s%s' % (font.blue, self.network.help_character, one))
         elif len(command.split()) == 2:   # Command with one argument.
             if command.split()[1] == 'list':
-                connection.privmsg(replyto, 'List categories: %s%s%s list categories%s List options: %s%s%slist options' % (font.blue, self.network.command_character, one, font.reset, font.blue, self.network.command_character, one))
-            elif command.split()[1] in categories:
+                connection.privmsg(replyto, 'List categories: %s%s%s list categories%s List options: %s%s%slist options%s List definitions: %s%s%s list definitions' % (font.blue, self.network.command_character, one, font.reset, font.blue, self.network.command_character, one, font.reset, font.blue, self.network.command_character, one))
+            elif command.split()[1].lower() in categories:
                 if cmdtype == 'help':
                     connection.privmsg(replyto, 'Show a random %s' % command.split()[1])
                 else:
@@ -60,9 +60,14 @@ def do_command(self, connection, event, user, channel):
                         connection.privmsg(replyto, 'Lists available options.')
                     else:
                         connection.privmsg(replyto, 'add, info, ban')
+                elif command.split()[2] == 'definitions':
+                    if cmdtype == 'help':
+                        connection.privmsg(replyto, 'Lists definitions of categories.')
+                    else:
+                        connection.privmsg(replyto, 'CurseWord: A not to harsh swear word, let\'s keep it funny, not vulgar. Adjective: Something you would put in front of a curse word. NickReplyMessage: A response to when my name is mentioned. NickReplyAction: An action (Also know as emote or /me) in responce to when my name is mentioned.')
                 else:
                     connection.privmsg(replyto, 'Unkown list, options are %scategories%s and %soptions%s.' % (font.blue, font.reset,font.blue, font.reset))
-            elif command.split()[1] in catagories:
+            elif command.split()[1].lower() in categories:
                 if command.split()[2] in ['add', 'info', 'ban']:
                     if cmdtype == 'help':
                         connection.privmsg(replyto, 'Without specifiying add, display or ban, this displays a random %s' % command,split()[1])
@@ -73,7 +78,7 @@ def do_command(self, connection, event, user, channel):
             else:
                 connection.privmsg(replyto, 'Invalid catagory. To list valid categories use: %s%s%s list categories' % (font.blue, self.network.command_character, one))
         elif len(command.split()) >= 4:  # Command with three or more arguments.
-            if command.split()[1] in catagories:
+            if command.split()[1].lower() in categories:
                 if command.split()[2] == 'add':
                     if cmdtype == 'help':
                         connection.privmsg(replyto, 'Adds %s to %s' % (command.split()[3:], command.split()[1]))

+ 19 - 6
rotbot/common/queries.py

@@ -124,11 +124,6 @@ def is_owner(self, hostmask):
     return self.db.one('SELECT id FROM rotbot_owner WHERE hostmask=%(hostmask)s', hostmask=hostmask)
 
 
-# Network
-def get_network_services(self):
-    return self.db.one('SELECT network_services FROM rotbot_network WHERE id=%(network_id)s', network_id=self.network.id)
-
-
 # Channel
 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)
@@ -288,7 +283,7 @@ def random_adjective(self):
     return adjective
 
 def random_curseword(self):
-    curseword = self.db.one('SELECT content FROM rotbot_nickresponse WHERE banned=False ORDER BY RANDOM() LIMIT 1')
+    curseword = self.db.one('SELECT content FROM rotbot_curseword WHERE banned=False ORDER BY RANDOM() LIMIT 1')
 
     # Try to hint the user of my capabilities.
     if not curseword:
@@ -296,6 +291,24 @@ def random_curseword(self):
 
     return curseword
 
+def random_nick_reply_message(self):
+    nick_reply_message = self.db.one('SELECT content FROM rotbot_nickreplymessage WHERE banned=False ORDER BY RANDOM() LIMIT 1')
+
+    # Try to hint the user of my capabilities.
+    if not nick_reply_message:
+        log.notice('No nick reply message in database, please add some to let me speak.')
+
+    return nick_reply_message
+
+def random_nick_reply_action(self):
+    nick_reply_action = self.db.one('SELECT content FROM rotbot_nickreplyaction WHERE banned=False ORDER BY RANDOM() LIMIT 1')
+
+    # Try to hint the user of my capabilities.
+    if not nick_reply_action:
+        log.notice('No nick reply message in database, please add some to let me speak.')
+
+    return nick_reply_action
+
 # Phasing out.
 # def get_curse(self, word):
 #     return self.db.one('SELECT * FROM rotbot_curseword WHERE LOWER(content)=LOWER(%(content)s)')

+ 59 - 53
rotbot/events/common.py

@@ -29,6 +29,65 @@ class Inform():
         log.info('Informing home channel: %s' % message)
         connection.privmsg(self.network.home_channel, message)
 
+class Replyto():
+    def name(self, connection, replyto):
+
+        # Reply with a random message or action.
+        if random.randint(0, 1) == 0:
+            message = queries.random_nick_reply_message(self)
+            if message:
+                connection.privmsg(replyto, message)
+        else:
+            action = queries.random_nick_reply_action(self)
+            if action:
+                connection.action(replyto, action)
+
+        # messages = [
+        #     "Hello " + event.source.nick + ".",
+        #     "How are you today " + event.source.nick +  "?",
+        #     "Piss off " + event.source.nick + "!",
+        #     event.source.nick + ", what are you botherring me for?",
+        #     "Go bother someone else...",
+        #     "Is life treating you fair?",
+        #     "What's up?",
+        #     "Why are you talking to me?",
+        #     "I'm not talking to you!",
+        #     "What have you been up to?",
+        #     "How is life?",
+        #     "What do you want from me?",
+        #     event.source.nick + ", why are you bothering me?",
+        #     event.source.nick + ", when will you stop talking about me?",
+        #     event.source.nick + " I hate you!",
+        #     "Get bent!",
+        #     "Go and cut yourself.",
+        #     "Do you think i care about you?",
+        #     "Stop nickalerting me " + event.source.nick + ", you wanker!",
+        # ]
+        # actions = [
+        #     "hides!",
+        #     "dies.",
+        #     "runs away.",
+        #     "is ignoring that.",
+        #     "is not feeling like caring.",
+        #     "is away",
+        #     "will be ignoring that.",
+        #     "is faggaliciouz!! <333",
+        #     "likes you! <3",
+        #     "looks the other way...",
+        #     "does a little dance with " + event.source.nick + ".",
+        #     "makes a little love.",
+        #     "get's down tonight.",
+        #     "thinks SAM Broadcaster sucks raw cocks in hell!",
+        #     "is secretly in love with " + event.source.nick + ".",
+        #     "tosses " + event.source.nick + "'s salad.",
+        #     "tortures " + event.source.nick + " horribly!",
+        #     "is smelling like tuna when looking at " + event.source.nick + ".",
+        #     "sniffing armpits.. Eew! Smells like " + event.source.nick + ".",
+        #     "rapes " + event.source.nick + ".",
+        #     "pets " + event.source.nick + ", and sais: Why what a nice little human you are, and such plentifull organs!"
+        # ]
+
+
 # class Protectees():
 #     def update(self, nick, user, host):
 #         if nick in self.protectees: # On record.
@@ -40,59 +99,6 @@ class Inform():
 #             if userstatus.atleast_halfop(self, user, self.network.home_channel) or nick == self.connection.get_nickname():   # Update. Is atleast halfop or bot itself.
 #                 self.protectees[nick] = {'ident': nick + "!" + user + "@" + host}
 
-# class Replyto():
-#     def name(connection, event):
-#         messages = [
-#             "Hello " + event.source.nick + ".",
-#             "How are you today " + event.source.nick +  "?",
-#             "Piss off " + event.source.nick + "!",
-#             event.source.nick + ", what are you botherring me for?",
-#             "Go bother someone else...",
-#             "Is life treating you fair?",
-#             "What's up?",
-#             "Why are you talking to me?",
-#             "I'm not talking to you!",
-#             "What have you been up to?",
-#             "How is life?",
-#             "What do you want from me?",
-#             event.source.nick + ", why are you bothering me?",
-#             event.source.nick + ", when will you stop talking about me?",
-#             event.source.nick + " I hate you!",
-#             "Get bent!",
-#             "Go and cut yourself.",
-#             "Do you think i care about you?",
-#             "Stop nickalerting me " + event.source.nick + ", you wanker!",
-#         ]
-#         actions = [
-#             "hides!",
-#             "dies.",
-#             "runs away.",
-#             "is ignoring that.",
-#             "is not feeling like caring.",
-#             "is away",
-#             "will be ignoring that.",
-#             "is faggaliciouz!! <333",
-#             "likes you! <3",
-#             "looks the other way...",
-#             "does a little dance with " + event.source.nick + ".",
-#             "makes a little love.",
-#             "get's down tonight.",
-#             "thinks SAM Broadcaster sucks raw cocks in hell!",
-#             "is secretly in love with " + event.source.nick + ".",
-#             "tosses " + event.source.nick + "'s salad.",
-#             "tortures " + event.source.nick + " horribly!",
-#             "is smelling like tuna when looking at " + event.source.nick + ".",
-#             "sniffing armpits.. Eew! Smells like " + event.source.nick + ".",
-#             "rapes " + event.source.nick + ".",
-#             "pets " + event.source.nick + ", and sais: Why what a nice little human you are, and such plentifull organs!"
-#         ]
-#
-#         # Reply with a random message or action.
-#         if random.randint(0, 1) == 0:
-#             connection.privmsg(event.target, random.choice(messages))
-#         else:
-#             connection.action(event.target, random.choice(actions))
-
 # class Aggressiveness():
 #     def retalliation_reason(self, connection, protectee, behaviour):
 #         if protectee == connection.get_nickname():  # Bot itself.

+ 3 - 3
rotbot/events/on_action.py

@@ -56,6 +56,6 @@ def process_event(self, connection, event):
             elif joinin == True:
                 connection.action(event.target, 'checks his hand and joins in slapping %s%s%s profusely.' % (font.red, event.arguments[0].split(" ")[1], font.reset))
 
-    # # Respond to own name.
-    # elif connection.get_nickname().lower() in event.arguments[0].lower() and event.source.nick is not connection.get_nickname(): # Bot's name was mentioned, not by the bot itself.
-    #     Replyto.name(connection, event)
+    # Respond to own name.
+    elif connection.get_nickname().lower() in event.arguments[0].lower() and event.source.nick is not connection.get_nickname(): # Bot's name was mentioned, not by the bot itself.
+        Replyto.name(self, connection, replyto)

+ 6 - 1
rotbot/events/on_join.py

@@ -16,6 +16,11 @@ def process_event(self, connection, event):
                 self.db.run('UPDATE rotbot_channels SET key=%(key)s WHERE id=%(id)s', key=self.channelkeys[event.target], id=channel.id)    # Save new key to DB.
         return   # Stop if the bot joined the channel.
 
+    # Op via x bot
+    if self.network.services == 'x':
+        connection.privmsg(self.xbot, 'op %s %s' % event.target, connection.get_nickname())
+
+
     # Greeting conditions.
     timeout = 10    # Amount of minutes to let pass before greeting again.
     joins = queries.get_user_channel_joins(self, user.id, channel.id)
@@ -32,7 +37,7 @@ def process_event(self, connection, event):
     if chan_lastgreet < cet.localize(datetime.datetime.now()) - datetime.timedelta(minutes=timeout) and user_lastgreet < cet.localize(datetime.datetime.now()) - datetime.timedelta(minutes=timeout):    # User or channel have not had greet message in the last timeout minutes, there is a curse available
         if joins % 200 == 0:    # Has joined 200, or a multiple of 200 times. (Also 0 but the join has already been saved.)
             amount_greet = True
-        elif userstatus.atleast_voiced(self, event.source.nick, self.network.home_channel):  # User has atleast voice in home channel.
+        elif userstatus.atleast_voiced(self, event.source.nick, self.network.home_channel) or queries.is_owner(self, event.source):  # User has atleast voice in home channel or usermask is in database as owner.
             curse = queries.random_curse(self)
             if curse:
                 greet = True

+ 4 - 0
rotbot/events/on_privmsg.py

@@ -18,3 +18,7 @@ def process_event(self, connection, event):
     commands.statistics.do_command(self, connection, event, user, channel)
     commands.chat.do_command(self, connection, event, user, channel)
     commands.admin.do_command(self, connection, event, user, channel)
+
+    # Respond to own name.
+    if connection.get_nickname().lower() in event.arguments[0].lower() and event.source.nick is not connection.get_nickname(): # Bot's name was mentioned, not by the bot itself.
+        Replyto.name(self, connection, event.source)

+ 15 - 6
rotbot/events/on_privnotice.py

@@ -28,7 +28,7 @@ def process_event(self, connection, event):
         if event.arguments[0].startswith("This nickname is registered"):
             connection.privmsg('NickServ', 'identify %s %s' % (self.network.nickname, self.network.password)) # Identify with NickServ. Also doing it on_welcome, but if there is a netsplit or so and there is no welcome, but a need to login, this is needed.
             return
-        elif event.arguments[0].endswith(' is not a registered nickname.') or event.arguments[0].startswith('Nick ') and event.arguments[0].endswith(' isn\'t registered.') or event.arguments[0] == 'Your nick isn\'t registered.': # Username from database is not registered.
+        elif event.arguments[0].endswith(' is not a registered nickname.') or event.arguments[0].startswith('Nick ') and event.arguments[0].endswith(' isn\'t registered.') or event.arguments[0].startswith('Your nick isn\'t registered.'): # Username from database is not registered.
             log.info('Registerring with NickServ.')
             Inform.operators(self, connection, 'Regisring %s%s%s with %sNickServ%s.' % (font.red, self.network.nickname, font.reset, font.red, font.reset))
             connection.privmsg('NickServ', 'register %s spamtBK@xs4all.nl' % (self.network.password)) # Register with NickServ.
@@ -53,7 +53,7 @@ def process_event(self, connection, event):
             return
         elif event.arguments[0].startswith("please choose a different nick."):
             return
-        elif event.arguments[0] == 'Password accepted - you are now recognized.':
+        elif event.arguments[0].startswith('Password accepted - you are now recognized.'):
             return
     elif event.source.nick == "ChanServ":
         if event.arguments[0].startswith("Key for channel ") and len(event.arguments[0]) > 5:   # Received channel key.
@@ -62,18 +62,27 @@ def process_event(self, connection, event):
             connection.join(event.arguments[0].split(' ')[3], event.arguments[0].split(' ')[5][:-1])
             Inform.owners(self, connection, "Received " + red + event.arguments[0].split(" ")[3] + reset + " key: " + event.arguments.split(" ")[5][:-1])
             returnZZ
-        elif event.arguments[0] == "Password authentication required for that command.":  # Not authenticated with NisckServ.
+        elif event.arguments[0].startswith("Password authentication required for that command."):  # Not authenticated with NisckServ.
             connection.privmsg('NickServ', 'identify %s %s' % (self.network.nickname, self.network.password))
             Inform.notice_owners(self, connection, "Not authenticated with NickServ.")
             return
-        elif event.arguments[0].startswith("You have been unbanned from ") or event.arguments[0].endswith(" autokick list is empty.") or event.arguments[0].startswith("You are already in ") or event.arguments[0] == "Syntax: UNBAN channel [nick]" or event.arguments[0] == "/msg ChanServ HELP UNBAN for more information":
+        elif event.arguments[0].startswith("You have been unbanned from ") or event.arguments[0].endswith(" autokick list is empty.") or event.arguments[0].startswith("You are already in ") or event.arguments[0].startswith("Syntax: UNBAN channel [nick]") or event.arguments[0].startswith("/msg ChanServ HELP UNBAN for more information"):
             return
         # if event.arguments[0].startswith("Channel ") and event.arguments[0].endswith(" has no key."):
         #     return
+    elif event.arguments[0].startswith('*** Looking up your hostname...') or event.arguments[0].startswith('*** Found your hostname') or event.arguments[0].startswith('*** Connected securely via ') or event.arguments[0].startswith('*** Your host is masked (') or event.arguments[0].startswith('You are connected to ') or event.arguments[0].startswith('Highest connection count: ') or event.arguments[0].startswith('*** If you are having problems connecting due to ping timeouts, please type /quote PONG') or event.arguments[0].startswith('*** You are connected using SSL cipher "') :
+        return  # Do not forward these messages.
+
+    # Respond to own name.
+    elif connection.get_nickname().lower() in event.arguments[0].lower() and event.source.nick is not connection.get_nickname(): # Bot's name was mentioned, not by the bot itself.
+        Replyto.name(self, connection, replyto)
+
+    # Forward messages to owners.
     if event.source.nick != "Global":
         Inform.notice_owners(self, connection, 'Notice from %s%s%s: %s%s' % (font.red, event.source.nick, font.grey, font.reset, event.arguments[0]))
-    if event.arguments[0] == '*** Looking up your hostname...' or event.arguments[0] == '*** Found your hostname' or event.arguments[0].startswith('*** Connected securely via ') or event.arguments[0] == '*** Your host is masked (' or event.arguments[0] == 'You are connected to ' or event.arguments[0] == 'Highest connection count: ':
-        return  # Do not forward these messages.
+
+
+
 
 async def register_later(self, connection, seconds):
     await asyncio.sleep(seconds)

+ 3 - 4
rotbot/events/on_pubmsg.py

@@ -22,10 +22,9 @@ def process_event(self, connection, event):
     if not queries.get_channel_setting_chat(self, channel.id):
         return
 
-    # if connection.get_nickname().lower() in event.arguments[0].lower() and event.source.nick is not connection.get_nickname(): # Bot's name was mentioned
-    #     if event.arguments[0].startswith(self.cmdchar):
-    #         return  # Stop if it's a command.
-    #     Replyto.name(connection, event)
+    # Respond to own name.
+    if connection.get_nickname().lower() in event.arguments[0].lower() and event.source.nick is not connection.get_nickname(): # Bot's name was mentioned, not by the bot itself.
+        Replyto.name(self, connection, replyto)
 
     # Character lame.
     elif event.arguments[0] == len(event.arguments[0]) * event.arguments[0][0]:   # Trigger exclusively same character.

+ 2 - 3
rotbot/events/on_welcome.py

@@ -5,11 +5,10 @@ def process_event(self, connection, event):
     self.db.run("UPDATE rotbot_host SET connection_succeeds = connection_succeeds + 1 WHERE id=%s", [self.network.id])
 
     # Identify with network services.
-    network_services = queries.get_network_services(self)
-    if network_services == 'm': # Modern network services (NickServ, ChanServ & MemoServ)
+    if self.network.services == 'm': # Modern network services (NickServ, ChanServ & MemoServ)
         if self.network.password:   # Password saved.
             connection.privmsg("NickServ", "identify " + self.network.password) # Identify with NickServ.
-    if network_services == 'x': # X bot.
+    if self.network.services == 'x': # X bot.
         self.xbot = 'x@xhannels.%s' % '.'.join(event.source.split('.')[-2:])
         if self.network.password:   # Password saved.
             print(self.xbot + ' LOGIN %s %s' % (self.network.nickname, self.network.password))

+ 52 - 0
website/rotbot/migrations/0009_auto_20191123_0724.py

@@ -0,0 +1,52 @@
+# Generated by Django 2.2.6 on 2019-11-23 06:24
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+        ('rotbot', '0008_auto_20191123_0357'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='NickReplyAction',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('content', models.CharField(max_length=200, unique=True)),
+                ('created', models.DateField(auto_now_add=True)),
+                ('banned', models.BooleanField(default=False)),
+                ('ban_created', models.DateField(blank=True, null=True)),
+                ('banned_by_irc_user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='nickreplyaction_banned_by_irc_user', to='rotbot.User')),
+                ('banned_by_web_user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='nickreplyaction_banned_by_web_user', to=settings.AUTH_USER_MODEL)),
+                ('irc_user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='rotbot.User')),
+                ('web_user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
+            ],
+        ),
+        migrations.CreateModel(
+            name='NickReplyMessage',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('content', models.CharField(max_length=200, unique=True)),
+                ('created', models.DateField(auto_now_add=True)),
+                ('banned', models.BooleanField(default=False)),
+                ('ban_created', models.DateField(blank=True, null=True)),
+                ('banned_by_irc_user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='nickreplymessage_banned_by_irc_user', to='rotbot.User')),
+                ('banned_by_web_user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='nickreplymessage_banned_by_web_user', to=settings.AUTH_USER_MODEL)),
+                ('irc_user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='rotbot.User')),
+                ('web_user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
+            ],
+        ),
+        migrations.RenameField(
+            model_name='network',
+            old_name='network_services',
+            new_name='services',
+        ),
+        migrations.DeleteModel(
+            name='NickReply',
+        ),
+    ]

+ 46 - 4
website/rotbot/models.py

@@ -63,7 +63,7 @@ class Network(models.Model):
         ('x', 'X bot'),
         ('n', 'None.'),
     ]
-    network_services = models.CharField(
+    services = models.CharField(
         max_length=1,
         default='m',
         choices=SERVICE_CHOICES,
@@ -529,7 +529,7 @@ class Adjective(models.Model):
     def __str__(self):
         return self.word
 
-class NickReply(models.Model):
+class NickReplyMessage(models.Model):
     content = models.CharField(
         max_length=200,
         unique=True,
@@ -561,12 +561,54 @@ class NickReply(models.Model):
         on_delete=models.PROTECT,
         null=True,
         blank=True,
-        related_name='nickreply_banned_by_irc_user',
+        related_name='nickreplymessage_banned_by_irc_user',
     )
     banned_by_web_user = models.ForeignKey(
         WebUser,
         on_delete=models.PROTECT,
         null=True,
         blank=True,
-        related_name='nickreply_banned_by_web_user',
+        related_name='nickreplymessage_banned_by_web_user',
+    )
+
+class NickReplyAction(models.Model):
+    content = models.CharField(
+        max_length=200,
+        unique=True,
+    )
+    created = models.DateField(
+        auto_now_add=True,
+    )
+    irc_user = models.ForeignKey(
+        'User',
+        on_delete=models.PROTECT,
+        null=True,
+        blank=True,
+    )
+    web_user = models.ForeignKey(
+        WebUser,
+        on_delete=models.PROTECT,
+        null=True,
+        blank=True,
+    )
+    banned = models.BooleanField(
+        default=False,
+    )
+    ban_created = models.DateField(
+        null=True,
+        blank=True,
+    )
+    banned_by_irc_user = models.ForeignKey(
+        'User',
+        on_delete=models.PROTECT,
+        null=True,
+        blank=True,
+        related_name='nickreplyaction_banned_by_irc_user',
+    )
+    banned_by_web_user = models.ForeignKey(
+        WebUser,
+        on_delete=models.PROTECT,
+        null=True,
+        blank=True,
+        related_name='nickreplyaction_banned_by_web_user',
     )