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:
parent
44d8b7db00
commit
6136127c5f
23
dispatch/migrations/0006_xmlrpc_settings.py
Normal file
23
dispatch/migrations/0006_xmlrpc_settings.py
Normal file
@ -0,0 +1,23 @@
|
||||
# Generated by Django 3.1.2 on 2021-04-25 14:37
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('dispatch', '0005_auto_20160116_1955'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='dispatcher',
|
||||
name='bot_xmlrpc_host',
|
||||
field=models.CharField(default='localhost', max_length=200),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='dispatcher',
|
||||
name='bot_xmlrpc_port',
|
||||
field=models.PositiveSmallIntegerField(default=13132),
|
||||
),
|
||||
]
|
@ -1,10 +1,8 @@
|
||||
"""Track dispatcher configurations."""
|
||||
|
||||
import logging
|
||||
|
||||
from django.db import models
|
||||
|
||||
|
||||
log = logging.getLogger('dispatch.models')
|
||||
|
||||
|
||||
@ -13,6 +11,9 @@ class Dispatcher(models.Model):
|
||||
|
||||
key = models.CharField(max_length=16, unique=True)
|
||||
|
||||
bot_xmlrpc_host = models.CharField(max_length=200, default='localhost')
|
||||
bot_xmlrpc_port = models.PositiveSmallIntegerField(default=13132)
|
||||
|
||||
class Meta:
|
||||
"""Meta options."""
|
||||
|
||||
@ -21,7 +22,7 @@ class Dispatcher(models.Model):
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
"""String representation."""
|
||||
"""Provide string representation."""
|
||||
return "{0:s}".format(self.key)
|
||||
|
||||
|
||||
@ -42,5 +43,5 @@ class DispatcherAction(models.Model):
|
||||
include_key = models.BooleanField(default=False)
|
||||
|
||||
def __str__(self):
|
||||
"""String representation."""
|
||||
"""Provide string representation."""
|
||||
return "{0:s} -> {1:s} {2:s}".format(self.dispatcher.key, self.type, self.destination)
|
||||
|
@ -1,18 +1,15 @@
|
||||
"""Handle dispatcher API requests."""
|
||||
|
||||
import copy
|
||||
import logging
|
||||
import os
|
||||
import xmlrpc.client
|
||||
|
||||
from django.conf import settings
|
||||
from rest_framework import generics, status
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
|
||||
from dispatch.models import Dispatcher, DispatcherAction
|
||||
from dispatch.serializers import DispatchMessageSerializer, DispatcherSerializer, DispatcherActionSerializer
|
||||
|
||||
from dispatch.serializers import DispatcherActionSerializer, DispatcherSerializer, DispatchMessageSerializer
|
||||
|
||||
log = logging.getLogger('dispatch.views')
|
||||
|
||||
@ -77,7 +74,7 @@ class DispatchMessage(generics.GenericAPIView):
|
||||
if action.type == DispatcherAction.PRIVMSG_TYPE:
|
||||
# connect over XML-RPC and send
|
||||
try:
|
||||
bot_url = 'http://{0:s}:{1:d}/'.format(settings.IRCBOT_XMLRPC_HOST, settings.IRCBOT_XMLRPC_PORT)
|
||||
bot_url = 'http://{0:s}:{1:d}/'.format(dispatcher.bot_xmlrpc_host, dispatcher.bot_xmlrpc_port)
|
||||
bot = xmlrpc.client.ServerProxy(bot_url, allow_none=True)
|
||||
log.debug("sending '%s' to channel %s", text, action.destination)
|
||||
bot.reply(None, text, False, action.destination)
|
||||
|
@ -154,32 +154,6 @@ ACCOUNT_ACTIVATION_DAYS = 7
|
||||
REGISTRATION_AUTO_LOGIN = True
|
||||
|
||||
|
||||
# IRC bot stuff
|
||||
|
||||
# tuple of hostname, port number, and password (or None)
|
||||
IRCBOT_SERVER_LIST = [
|
||||
('localhost', 6667, None),
|
||||
]
|
||||
IRCBOT_NICKNAME = 'dr_botzo'
|
||||
IRCBOT_REALNAME = 'Dr. Botzo'
|
||||
IRCBOT_SSL = False
|
||||
IRCBOT_IPV6 = False
|
||||
|
||||
|
||||
# post-connect, pre-autojoin stuff
|
||||
IRCBOT_SLEEP_BEFORE_AUTOJOIN_SECONDS = 10
|
||||
IRCBOT_POST_CONNECT_COMMANDS = [ ]
|
||||
|
||||
|
||||
# XML-RPC settings
|
||||
IRCBOT_XMLRPC_HOST = 'localhost'
|
||||
IRCBOT_XMLRPC_PORT = 13132
|
||||
|
||||
|
||||
# nick hack for discord through bitlbee
|
||||
ADDITIONAL_NICK_MATCHES = []
|
||||
|
||||
|
||||
# IRC module stuff
|
||||
|
||||
# karma
|
||||
|
@ -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')
|
||||
|
@ -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):
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
32
ircbot/migrations/0015_ircserver.py
Normal file
32
ircbot/migrations/0015_ircserver.py
Normal 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)),
|
||||
],
|
||||
),
|
||||
]
|
26
ircbot/migrations/0016_placeholder_ircserver.py
Normal file
26
ircbot/migrations/0016_placeholder_ircserver.py
Normal 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),
|
||||
]
|
@ -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."""
|
||||
|
||||
|
@ -1,23 +1,22 @@
|
||||
"""IRC support for Markov chain learning and text generation."""
|
||||
import logging
|
||||
import re
|
||||
|
||||
import irc.client
|
||||
from django.conf import settings
|
||||
|
||||
import markov.lib as markovlib
|
||||
from ircbot.lib import Plugin, reply_destination_for_event
|
||||
from ircbot.models import IrcChannel
|
||||
import markov.lib as markovlib
|
||||
|
||||
log = logging.getLogger('markov.ircplugin')
|
||||
|
||||
|
||||
class Markov(Plugin):
|
||||
|
||||
"""Build Markov chains and reply with them."""
|
||||
|
||||
def start(self):
|
||||
"""Set up the handlers."""
|
||||
|
||||
self.connection.add_global_handler('pubmsg', self.handle_chatter, -20)
|
||||
self.connection.add_global_handler('privmsg', self.handle_chatter, -20)
|
||||
|
||||
@ -29,7 +28,6 @@ class Markov(Plugin):
|
||||
|
||||
def stop(self):
|
||||
"""Tear down handlers."""
|
||||
|
||||
self.connection.remove_global_handler('pubmsg', self.handle_chatter)
|
||||
self.connection.remove_global_handler('privmsg', self.handle_chatter)
|
||||
|
||||
@ -39,7 +37,6 @@ class Markov(Plugin):
|
||||
|
||||
def handle_reply(self, connection, event, match):
|
||||
"""Generate a reply to one line, without learning it."""
|
||||
|
||||
target = reply_destination_for_event(event)
|
||||
|
||||
min_size = 15
|
||||
@ -63,9 +60,9 @@ class Markov(Plugin):
|
||||
|
||||
def handle_chatter(self, connection, event):
|
||||
"""Learn from IRC chatter."""
|
||||
|
||||
what = event.arguments[0]
|
||||
all_nicks = '|'.join(settings.ADDITIONAL_NICK_MATCHES + [connection.get_nickname()])
|
||||
all_nicks = '|'.join(connection.server_config.additional_addressed_nicks.split('\n') +
|
||||
[connection.get_nickname()])
|
||||
trimmed_what = re.sub(r'^(({nicks})[:,]|@({nicks}))\s+'.format(nicks=all_nicks), '', what)
|
||||
nick = irc.client.NickMask(event.source).nick
|
||||
target = reply_destination_for_event(event)
|
||||
|
Loading…
Reference in New Issue
Block a user