move IRC server settings to database

this is the first step in trying to get the bot to support multiple
servers with different channels, countdown triggers, and so on

this also ends up affecting some configuration around:
* dispatch
* markov
* admin privmsg form
This commit is contained in:
2021-04-25 08:59:01 -05:00
parent 44d8b7db00
commit 6136127c5f
12 changed files with 158 additions and 73 deletions

View File

@@ -3,23 +3,15 @@
import logging
import xmlrpc.client
from django.conf import settings
from django.contrib import admin
from django.shortcuts import render
from ircbot.forms import PrivmsgForm
from ircbot.models import Alias, BotUser, IrcChannel, IrcPlugin
from ircbot.models import Alias, BotUser, IrcChannel, IrcPlugin, IrcServer
log = logging.getLogger('ircbot.admin')
admin.site.register(Alias)
admin.site.register(BotUser)
admin.site.register(IrcChannel)
admin.site.register(IrcPlugin)
def send_privmsg(request):
"""Send a privmsg over XML-RPC to the IRC bot."""
if request.method == 'POST':
@@ -28,7 +20,8 @@ def send_privmsg(request):
target = form.cleaned_data['target']
message = form.cleaned_data['message']
bot_url = 'http://{0:s}:{1:d}/'.format(settings.IRCBOT_XMLRPC_HOST, settings.IRCBOT_XMLRPC_PORT)
bot_url = 'http://{0:s}:{1:d}/'.format(form.cleaned_data['xmlrpc_host'],
form.cleaned_data['xmlrpc_port'])
bot = xmlrpc.client.ServerProxy(bot_url, allow_none=True)
bot.reply(None, message, False, target)
form = PrivmsgForm()
@@ -37,4 +30,11 @@ def send_privmsg(request):
return render(request, 'privmsg.html', {'form': form})
admin.site.register(Alias)
admin.site.register(BotUser)
admin.site.register(IrcChannel)
admin.site.register(IrcPlugin)
admin.site.register(IrcServer)
admin.site.register_view('ircbot/privmsg/', "Ircbot - privmsg", view=send_privmsg, urlname='ircbot_privmsg')

View File

@@ -22,7 +22,7 @@ from irc.dict import IRCDict
import irc.modes
import ircbot.lib as ircbotlib
from ircbot.models import Alias, IrcChannel, IrcPlugin
from ircbot.models import Alias, IrcChannel, IrcPlugin, IrcServer
log = logging.getLogger('ircbot.bot')
@@ -54,6 +54,8 @@ class LenientServerConnection(irc.client.ServerConnection):
buffer_class = irc.buffer.LenientDecodingLineBuffer
server_config = None
def _prep_message(self, string):
"""Override SimpleIRCClient._prep_message to add some logging."""
log.debug("preparing message %s", string)
@@ -163,7 +165,8 @@ class DrReactor(irc.client.Reactor):
event.original_msg = what
# check if we were addressed or not
all_nicks = '|'.join(settings.ADDITIONAL_NICK_MATCHES + [connection.get_nickname()])
all_nicks = '|'.join(connection.server_config.additional_addressed_nicks.split('\n') +
[connection.get_nickname()])
addressed_pattern = r'^(({nicks})[:,]|@({nicks}))\s+(?P<addressed_msg>.*)'.format(nicks=all_nicks)
match = re.match(addressed_pattern, what, re.IGNORECASE)
if match:
@@ -351,15 +354,16 @@ class IRCBot(irc.client.SimpleIRCClient):
reactor_class = DrReactor
splitter = "..."
def __init__(self, reconnection_interval=60):
def __init__(self, server_name, reconnection_interval=60):
"""Initialize bot."""
super(IRCBot, self).__init__()
self.channels = IRCDict()
self.plugins = []
# set up the server list
self.server_list = settings.IRCBOT_SERVER_LIST
self.server_config = IrcServer.objects.get(name=server_name)
# the reactor made the connection, save the server reference in it since we pass that around
self.connection.server_config = self.server_config
# set reconnection interval
if not reconnection_interval or reconnection_interval < 0:
@@ -367,8 +371,8 @@ class IRCBot(irc.client.SimpleIRCClient):
self.reconnection_interval = reconnection_interval
# set basic stuff
self._nickname = settings.IRCBOT_NICKNAME
self._realname = settings.IRCBOT_REALNAME
self._nickname = self.server_config.nickname
self._realname = self.server_config.realname
# guess at nickmask. hopefully _on_welcome() will set this, but this should be
# a pretty good guess if not
@@ -395,7 +399,7 @@ class IRCBot(irc.client.SimpleIRCClient):
getattr(self, 'handle_reload'), -20)
# load XML-RPC server
self.xmlrpc = SimpleXMLRPCServer((settings.IRCBOT_XMLRPC_HOST, settings.IRCBOT_XMLRPC_PORT),
self.xmlrpc = SimpleXMLRPCServer((self.server_config.xmlrpc_host, self.server_config.xmlrpc_port),
requestHandler=IrcBotXMLRPCRequestHandler, allow_none=True)
self.xmlrpc.register_introspection_functions()
@@ -414,16 +418,15 @@ class IRCBot(irc.client.SimpleIRCClient):
self.jump_server()
def _connect(self):
server = self.server_list[0]
try:
# build the connection factory as determined by IPV6/SSL settings
if settings.IRCBOT_SSL:
connect_factory = Factory(wrapper=ssl.wrap_socket, ipv6=settings.IRCBOT_IPV6)
if self.server_config.use_ssl:
connect_factory = Factory(wrapper=ssl.wrap_socket, ipv6=self.server_config.use_ipv6)
else:
connect_factory = Factory(ipv6=settings.IRCBOT_IPV6)
connect_factory = Factory(ipv6=self.server_config.use_ipv6)
self.connect(server[0], server[1], self._nickname, server[2], ircname=self._realname,
connect_factory=connect_factory)
self.connect(self.server_config.hostname, self.server_config.port, self._nickname,
self.server_config.password, ircname=self._realname, connect_factory=connect_factory)
except irc.client.ServerConnectionError:
pass
@@ -528,13 +531,13 @@ class IRCBot(irc.client.SimpleIRCClient):
log.debug("welcome: %s", what)
# run automsg commands
for cmd in settings.IRCBOT_POST_CONNECT_COMMANDS:
for cmd in self.server_config.post_connect.split('\n'):
# TODO NOTE: if the bot is sending something that changes the vhost
# (like 'hostserv on') we don't pick it up
self.connection.privmsg(cmd.split(' ')[0], ' '.join(cmd.split(' ')[1:]))
# sleep before doing autojoins
time.sleep(settings.IRCBOT_SLEEP_BEFORE_AUTOJOIN_SECONDS)
time.sleep(self.server_config.delay_before_joins)
for chan in IrcChannel.objects.filter(autojoin=True):
log.info("autojoining %s", chan.name)
@@ -584,7 +587,6 @@ class IRCBot(irc.client.SimpleIRCClient):
if self.connection.is_connected():
self.connection.disconnect(msg)
self.server_list.append(self.server_list.pop(0))
self._connect()
def on_ctcp(self, c, e):

View File

@@ -1,10 +1,9 @@
"""Forms for doing ircbot stuff."""
import logging
from django.forms import Form, CharField, Textarea
from django.forms import CharField, Form, IntegerField, Textarea
log = logging.getLogger('markov.forms')
log = logging.getLogger('ircbot.forms')
class PrivmsgForm(Form):
@@ -12,3 +11,5 @@ class PrivmsgForm(Form):
target = CharField()
message = CharField(widget=Textarea)
xmlrpc_host = CharField()
xmlrpc_port = IntegerField()

View File

@@ -17,8 +17,13 @@ class Command(BaseCommand):
help = "Start the IRC bot"
def add_arguments(self, parser):
"""Add arguments to the bot startup."""
parser.add_argument('server_name')
def handle(self, *args, **options):
"""Start the IRC bot and spin forever."""
irc = IRCBot()
self.stdout.write(self.style.NOTICE(f"Starting up {options['server_name']} bot"))
irc = IRCBot(options['server_name'])
signal.signal(signal.SIGINT, irc.sigint_handler)
irc.start()

View File

@@ -0,0 +1,32 @@
# Generated by Django 3.1.2 on 2021-04-25 14:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ircbot', '0014_auto_20160116_1955'),
]
operations = [
migrations.CreateModel(
name='IrcServer',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=200, unique=True)),
('hostname', models.CharField(max_length=200)),
('port', models.PositiveSmallIntegerField(default=6667)),
('password', models.CharField(blank=True, default=None, max_length=200, null=True)),
('nickname', models.CharField(max_length=32)),
('realname', models.CharField(blank=True, default='', max_length=32)),
('additional_addressed_nicks', models.TextField(blank=True, default='', help_text='For e.g. BitlBee alternative nicks')),
('use_ssl', models.BooleanField(default=False)),
('use_ipv6', models.BooleanField(default=False)),
('post_connect', models.TextField(blank=True, default='')),
('delay_before_joins', models.PositiveSmallIntegerField(default=0)),
('xmlrpc_host', models.CharField(default='localhost', max_length=200)),
('xmlrpc_port', models.PositiveSmallIntegerField(default=13132)),
],
),
]

View File

@@ -0,0 +1,26 @@
# Generated by Django 3.1.2 on 2021-04-25 04:11
from django.db import migrations
def create_placeholder_server(apps, schema_editor):
"""Create the first server entry, to be configured by the admin."""
IrcServer = apps.get_model('ircbot', 'IrcServer')
IrcServer.objects.create(name='default', hostname='irc.example.org', port=6667)
def delete_placeholder_server(apps, schema_editor):
"""Remove the default server."""
IrcServer = apps.get_model('ircbot', 'IrcServer')
IrcServer.objects.filter(name='default').delete()
class Migration(migrations.Migration):
dependencies = [
('ircbot', '0015_ircserver'),
]
operations = [
migrations.RunPython(create_placeholder_server, delete_placeholder_server),
]

View File

@@ -62,6 +62,33 @@ class BotUser(models.Model):
return "{0:s} (Django user {1:s})".format(self.nickmask, self.user.username)
class IrcServer(models.Model):
"""Contain server information in an object, and help contextualize channels."""
name = models.CharField(max_length=200, unique=True)
hostname = models.CharField(max_length=200)
port = models.PositiveSmallIntegerField(default=6667)
password = models.CharField(max_length=200, default=None, null=True, blank=True)
nickname = models.CharField(max_length=32)
realname = models.CharField(max_length=32, default='', blank=True)
additional_addressed_nicks = models.TextField(default='', blank=True,
help_text="For e.g. BitlBee alternative nicks")
use_ssl = models.BooleanField(default=False)
use_ipv6 = models.BooleanField(default=False)
post_connect = models.TextField(default='', blank=True)
delay_before_joins = models.PositiveSmallIntegerField(default=0)
xmlrpc_host = models.CharField(max_length=200, default='localhost')
xmlrpc_port = models.PositiveSmallIntegerField(default=13132)
def __str__(self):
"""Provide string summary of the server."""
return f"{self.name} ({self.hostname}/{self.port})"
class IrcChannel(models.Model):
"""Track channel settings."""