Refactor to use Twisted IRC instead.
This commit is contained in:
parent
4636ed4a7a
commit
6e9a61b3bf
3 changed files with 304 additions and 2296 deletions
462
ircbot.py
462
ircbot.py
|
|
@ -1,462 +0,0 @@
|
||||||
# Copyright (C) 1999--2002 Joel Rosdahl
|
|
||||||
#
|
|
||||||
# This library is free software; you can redistribute it and/or
|
|
||||||
# modify it under the terms of the GNU Lesser General Public
|
|
||||||
# License as published by the Free Software Foundation; either
|
|
||||||
# version 2.1 of the License, or (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This library is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
# Lesser General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Lesser General Public
|
|
||||||
# License along with this library; if not, write to the Free Software
|
|
||||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
||||||
#
|
|
||||||
# Joel Rosdahl <joel@rosdahl.net>
|
|
||||||
#
|
|
||||||
# $Id: ircbot.py,v 1.21 2005/12/23 18:44:43 keltus Exp $
|
|
||||||
|
|
||||||
"""ircbot -- Simple IRC bot library.
|
|
||||||
|
|
||||||
This module contains a single-server IRC bot class that can be used to
|
|
||||||
write simpler bots.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
from UserDict import UserDict
|
|
||||||
|
|
||||||
from irclib import SimpleIRCClient
|
|
||||||
from irclib import nm_to_n, irc_lower, all_events
|
|
||||||
from irclib import parse_channel_modes, is_channel
|
|
||||||
from irclib import ServerConnectionError
|
|
||||||
import threading
|
|
||||||
import time
|
|
||||||
|
|
||||||
class OutputManager(threading.Thread):
|
|
||||||
def __init__(self, connection, delay=.5):
|
|
||||||
threading.Thread.__init__(self)
|
|
||||||
self.setDaemon(1)
|
|
||||||
self.connection = connection
|
|
||||||
self.delay = delay
|
|
||||||
self.event = threading.Event()
|
|
||||||
self.queue = []
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
while 1:
|
|
||||||
self.event.wait()
|
|
||||||
while self.queue:
|
|
||||||
msg,target = self.queue.pop(0)
|
|
||||||
self.connection.privmsg(target, msg)
|
|
||||||
time.sleep(self.delay)
|
|
||||||
self.event.clear()
|
|
||||||
|
|
||||||
def send(self, msg, target):
|
|
||||||
self.queue.append((msg.strip(),target))
|
|
||||||
self.event.set()
|
|
||||||
|
|
||||||
class SingleServerIRCBot(SimpleIRCClient):
|
|
||||||
"""A single-server IRC bot class.
|
|
||||||
|
|
||||||
The bot tries to reconnect if it is disconnected.
|
|
||||||
|
|
||||||
The bot keeps track of the channels it has joined, the other
|
|
||||||
clients that are present in the channels and which of those that
|
|
||||||
have operator or voice modes. The "database" is kept in the
|
|
||||||
self.channels attribute, which is an IRCDict of Channels.
|
|
||||||
"""
|
|
||||||
def __init__(self, server_list, nickname, realname, reconnection_interval=60):
|
|
||||||
"""Constructor for SingleServerIRCBot objects.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
|
|
||||||
server_list -- A list of tuples (server, port) that
|
|
||||||
defines which servers the bot should try to
|
|
||||||
connect to.
|
|
||||||
|
|
||||||
nickname -- The bot's nickname.
|
|
||||||
|
|
||||||
realname -- The bot's realname.
|
|
||||||
|
|
||||||
reconnection_interval -- How long the bot should wait
|
|
||||||
before trying to reconnect.
|
|
||||||
|
|
||||||
dcc_connections -- A list of initiated/accepted DCC
|
|
||||||
connections.
|
|
||||||
"""
|
|
||||||
|
|
||||||
SimpleIRCClient.__init__(self)
|
|
||||||
self.channels = IRCDict()
|
|
||||||
self.server_list = server_list
|
|
||||||
if not reconnection_interval or reconnection_interval < 0:
|
|
||||||
reconnection_interval = 2**31
|
|
||||||
self.reconnection_interval = reconnection_interval
|
|
||||||
|
|
||||||
self._nickname = nickname
|
|
||||||
self._realname = realname
|
|
||||||
for i in ["disconnect", "join", "kick", "mode",
|
|
||||||
"namreply", "nick", "part", "quit"]:
|
|
||||||
self.connection.add_global_handler(i,
|
|
||||||
getattr(self, "_on_" + i),
|
|
||||||
-10)
|
|
||||||
def _connected_checker(self):
|
|
||||||
"""[Internal]"""
|
|
||||||
if not self.connection.is_connected():
|
|
||||||
self.connection.execute_delayed(self.reconnection_interval,
|
|
||||||
self._connected_checker)
|
|
||||||
self.jump_server()
|
|
||||||
|
|
||||||
def _connect(self):
|
|
||||||
"""[Internal]"""
|
|
||||||
password = None
|
|
||||||
if len(self.server_list[0]) > 2:
|
|
||||||
password = self.server_list[0][2]
|
|
||||||
try:
|
|
||||||
self.connect(self.server_list[0][0],
|
|
||||||
self.server_list[0][1],
|
|
||||||
self._nickname,
|
|
||||||
password,
|
|
||||||
ircname=self._realname)
|
|
||||||
except ServerConnectionError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def _on_disconnect(self, c, e):
|
|
||||||
"""[Internal]"""
|
|
||||||
self.channels = IRCDict()
|
|
||||||
self.connection.execute_delayed(self.reconnection_interval,
|
|
||||||
self._connected_checker)
|
|
||||||
|
|
||||||
def _on_join(self, c, e):
|
|
||||||
"""[Internal]"""
|
|
||||||
ch = e.target()
|
|
||||||
nick = nm_to_n(e.source())
|
|
||||||
if nick == c.get_nickname():
|
|
||||||
self.channels[ch] = Channel()
|
|
||||||
self.channels[ch].add_user(nick)
|
|
||||||
|
|
||||||
def _on_kick(self, c, e):
|
|
||||||
"""[Internal]"""
|
|
||||||
nick = e.arguments()[0]
|
|
||||||
channel = e.target()
|
|
||||||
|
|
||||||
if nick == c.get_nickname():
|
|
||||||
del self.channels[channel]
|
|
||||||
else:
|
|
||||||
self.channels[channel].remove_user(nick)
|
|
||||||
|
|
||||||
def _on_mode(self, c, e):
|
|
||||||
"""[Internal]"""
|
|
||||||
modes = parse_channel_modes(" ".join(e.arguments()))
|
|
||||||
t = e.target()
|
|
||||||
if is_channel(t):
|
|
||||||
ch = self.channels[t]
|
|
||||||
for mode in modes:
|
|
||||||
if mode[0] == "+":
|
|
||||||
f = ch.set_mode
|
|
||||||
else:
|
|
||||||
f = ch.clear_mode
|
|
||||||
f(mode[1], mode[2])
|
|
||||||
else:
|
|
||||||
# Mode on self... XXX
|
|
||||||
pass
|
|
||||||
|
|
||||||
def _on_namreply(self, c, e):
|
|
||||||
"""[Internal]"""
|
|
||||||
|
|
||||||
# e.arguments()[0] == "@" for secret channels,
|
|
||||||
# "*" for private channels,
|
|
||||||
# "=" for others (public channels)
|
|
||||||
# e.arguments()[1] == channel
|
|
||||||
# e.arguments()[2] == nick list
|
|
||||||
|
|
||||||
ch = e.arguments()[1]
|
|
||||||
for nick in e.arguments()[2].split():
|
|
||||||
if nick[0] == "@":
|
|
||||||
nick = nick[1:]
|
|
||||||
self.channels[ch].set_mode("o", nick)
|
|
||||||
elif nick[0] == "+":
|
|
||||||
nick = nick[1:]
|
|
||||||
self.channels[ch].set_mode("v", nick)
|
|
||||||
self.channels[ch].add_user(nick)
|
|
||||||
|
|
||||||
def _on_nick(self, c, e):
|
|
||||||
"""[Internal]"""
|
|
||||||
before = nm_to_n(e.source())
|
|
||||||
after = e.target()
|
|
||||||
for ch in self.channels.values():
|
|
||||||
if ch.has_user(before):
|
|
||||||
ch.change_nick(before, after)
|
|
||||||
|
|
||||||
def _on_part(self, c, e):
|
|
||||||
"""[Internal]"""
|
|
||||||
nick = nm_to_n(e.source())
|
|
||||||
channel = e.target()
|
|
||||||
|
|
||||||
if nick == c.get_nickname():
|
|
||||||
del self.channels[channel]
|
|
||||||
else:
|
|
||||||
self.channels[channel].remove_user(nick)
|
|
||||||
|
|
||||||
def _on_quit(self, c, e):
|
|
||||||
"""[Internal]"""
|
|
||||||
nick = nm_to_n(e.source())
|
|
||||||
for ch in self.channels.values():
|
|
||||||
if ch.has_user(nick):
|
|
||||||
ch.remove_user(nick)
|
|
||||||
|
|
||||||
def die(self, msg="Bye, cruel world!"):
|
|
||||||
"""Let the bot die.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
|
|
||||||
msg -- Quit message.
|
|
||||||
"""
|
|
||||||
|
|
||||||
self.connection.disconnect(msg)
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
def disconnect(self, msg="I'll be back!"):
|
|
||||||
"""Disconnect the bot.
|
|
||||||
|
|
||||||
The bot will try to reconnect after a while.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
|
|
||||||
msg -- Quit message.
|
|
||||||
"""
|
|
||||||
self.connection.disconnect(msg)
|
|
||||||
|
|
||||||
def get_version(self):
|
|
||||||
"""Returns the bot version.
|
|
||||||
|
|
||||||
Used when answering a CTCP VERSION request.
|
|
||||||
"""
|
|
||||||
return "ircbot.py by Joel Rosdahl <joel@rosdahl.net>"
|
|
||||||
|
|
||||||
def jump_server(self, msg="Changing servers"):
|
|
||||||
"""Connect to a new server, possibly disconnecting from the current.
|
|
||||||
|
|
||||||
The bot will skip to next server in the server_list each time
|
|
||||||
jump_server is called.
|
|
||||||
"""
|
|
||||||
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):
|
|
||||||
"""Default handler for ctcp events.
|
|
||||||
|
|
||||||
Replies to VERSION and PING requests and relays DCC requests
|
|
||||||
to the on_dccchat method.
|
|
||||||
"""
|
|
||||||
if e.arguments()[0] == "VERSION":
|
|
||||||
c.ctcp_reply(nm_to_n(e.source()),
|
|
||||||
"VERSION " + self.get_version())
|
|
||||||
elif e.arguments()[0] == "PING":
|
|
||||||
if len(e.arguments()) > 1:
|
|
||||||
c.ctcp_reply(nm_to_n(e.source()),
|
|
||||||
"PING " + e.arguments()[1])
|
|
||||||
elif e.arguments()[0] == "DCC" and e.arguments()[1].split(" ", 1)[0] == "CHAT":
|
|
||||||
self.on_dccchat(c, e)
|
|
||||||
|
|
||||||
def on_dccchat(self, c, e):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
"""Start the bot."""
|
|
||||||
self._connect()
|
|
||||||
SimpleIRCClient.start(self)
|
|
||||||
|
|
||||||
|
|
||||||
class IRCDict:
|
|
||||||
"""A dictionary suitable for storing IRC-related things.
|
|
||||||
|
|
||||||
Dictionary keys a and b are considered equal if and only if
|
|
||||||
irc_lower(a) == irc_lower(b)
|
|
||||||
|
|
||||||
Otherwise, it should behave exactly as a normal dictionary.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, dict=None):
|
|
||||||
self.data = {}
|
|
||||||
self.canon_keys = {} # Canonical keys
|
|
||||||
if dict is not None:
|
|
||||||
self.update(dict)
|
|
||||||
def __repr__(self):
|
|
||||||
return repr(self.data)
|
|
||||||
def __cmp__(self, dict):
|
|
||||||
if isinstance(dict, IRCDict):
|
|
||||||
return cmp(self.data, dict.data)
|
|
||||||
else:
|
|
||||||
return cmp(self.data, dict)
|
|
||||||
def __len__(self):
|
|
||||||
return len(self.data)
|
|
||||||
def __getitem__(self, key):
|
|
||||||
return self.data[self.canon_keys[irc_lower(key)]]
|
|
||||||
def __setitem__(self, key, item):
|
|
||||||
if key in self:
|
|
||||||
del self[key]
|
|
||||||
self.data[key] = item
|
|
||||||
self.canon_keys[irc_lower(key)] = key
|
|
||||||
def __delitem__(self, key):
|
|
||||||
ck = irc_lower(key)
|
|
||||||
del self.data[self.canon_keys[ck]]
|
|
||||||
del self.canon_keys[ck]
|
|
||||||
def __iter__(self):
|
|
||||||
return iter(self.data)
|
|
||||||
def __contains__(self, key):
|
|
||||||
return self.has_key(key)
|
|
||||||
def clear(self):
|
|
||||||
self.data.clear()
|
|
||||||
self.canon_keys.clear()
|
|
||||||
def copy(self):
|
|
||||||
if self.__class__ is UserDict:
|
|
||||||
return UserDict(self.data)
|
|
||||||
import copy
|
|
||||||
return copy.copy(self)
|
|
||||||
def keys(self):
|
|
||||||
return self.data.keys()
|
|
||||||
def items(self):
|
|
||||||
return self.data.items()
|
|
||||||
def values(self):
|
|
||||||
return self.data.values()
|
|
||||||
def has_key(self, key):
|
|
||||||
return irc_lower(key) in self.canon_keys
|
|
||||||
def update(self, dict):
|
|
||||||
for k, v in dict.items():
|
|
||||||
self.data[k] = v
|
|
||||||
def get(self, key, failobj=None):
|
|
||||||
return self.data.get(key, failobj)
|
|
||||||
|
|
||||||
|
|
||||||
class Channel:
|
|
||||||
"""A class for keeping information about an IRC channel.
|
|
||||||
|
|
||||||
This class can be improved a lot.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.userdict = IRCDict()
|
|
||||||
self.operdict = IRCDict()
|
|
||||||
self.voiceddict = IRCDict()
|
|
||||||
self.modes = {}
|
|
||||||
|
|
||||||
def users(self):
|
|
||||||
"""Returns an unsorted list of the channel's users."""
|
|
||||||
return self.userdict.keys()
|
|
||||||
|
|
||||||
def opers(self):
|
|
||||||
"""Returns an unsorted list of the channel's operators."""
|
|
||||||
return self.operdict.keys()
|
|
||||||
|
|
||||||
def voiced(self):
|
|
||||||
"""Returns an unsorted list of the persons that have voice
|
|
||||||
mode set in the channel."""
|
|
||||||
return self.voiceddict.keys()
|
|
||||||
|
|
||||||
def has_user(self, nick):
|
|
||||||
"""Check whether the channel has a user."""
|
|
||||||
return nick in self.userdict
|
|
||||||
|
|
||||||
def is_oper(self, nick):
|
|
||||||
"""Check whether a user has operator status in the channel."""
|
|
||||||
return nick in self.operdict
|
|
||||||
|
|
||||||
def is_voiced(self, nick):
|
|
||||||
"""Check whether a user has voice mode set in the channel."""
|
|
||||||
return nick in self.voiceddict
|
|
||||||
|
|
||||||
def add_user(self, nick):
|
|
||||||
self.userdict[nick] = 1
|
|
||||||
|
|
||||||
def remove_user(self, nick):
|
|
||||||
for d in self.userdict, self.operdict, self.voiceddict:
|
|
||||||
if nick in d:
|
|
||||||
del d[nick]
|
|
||||||
|
|
||||||
def change_nick(self, before, after):
|
|
||||||
self.userdict[after] = 1
|
|
||||||
del self.userdict[before]
|
|
||||||
if before in self.operdict:
|
|
||||||
self.operdict[after] = 1
|
|
||||||
del self.operdict[before]
|
|
||||||
if before in self.voiceddict:
|
|
||||||
self.voiceddict[after] = 1
|
|
||||||
del self.voiceddict[before]
|
|
||||||
|
|
||||||
def set_mode(self, mode, value=None):
|
|
||||||
"""Set mode on the channel.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
|
|
||||||
mode -- The mode (a single-character string).
|
|
||||||
|
|
||||||
value -- Value
|
|
||||||
"""
|
|
||||||
if mode == "o":
|
|
||||||
self.operdict[value] = 1
|
|
||||||
elif mode == "v":
|
|
||||||
self.voiceddict[value] = 1
|
|
||||||
else:
|
|
||||||
self.modes[mode] = value
|
|
||||||
|
|
||||||
def clear_mode(self, mode, value=None):
|
|
||||||
"""Clear mode on the channel.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
|
|
||||||
mode -- The mode (a single-character string).
|
|
||||||
|
|
||||||
value -- Value
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
if mode == "o":
|
|
||||||
del self.operdict[value]
|
|
||||||
elif mode == "v":
|
|
||||||
del self.voiceddict[value]
|
|
||||||
else:
|
|
||||||
del self.modes[mode]
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def has_mode(self, mode):
|
|
||||||
return mode in self.modes
|
|
||||||
|
|
||||||
def is_moderated(self):
|
|
||||||
return self.has_mode("m")
|
|
||||||
|
|
||||||
def is_secret(self):
|
|
||||||
return self.has_mode("s")
|
|
||||||
|
|
||||||
def is_protected(self):
|
|
||||||
return self.has_mode("p")
|
|
||||||
|
|
||||||
def has_topic_lock(self):
|
|
||||||
return self.has_mode("t")
|
|
||||||
|
|
||||||
def is_invite_only(self):
|
|
||||||
return self.has_mode("i")
|
|
||||||
|
|
||||||
def has_allow_external_messages(self):
|
|
||||||
return self.has_mode("n")
|
|
||||||
|
|
||||||
def has_limit(self):
|
|
||||||
return self.has_mode("l")
|
|
||||||
|
|
||||||
def limit(self):
|
|
||||||
if self.has_limit():
|
|
||||||
return self.modes[l]
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def has_key(self):
|
|
||||||
return self.has_mode("k")
|
|
||||||
|
|
||||||
def key(self):
|
|
||||||
if self.has_key():
|
|
||||||
return self.modes["k"]
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
266
lolbot.py
266
lolbot.py
|
|
@ -2,8 +2,7 @@
|
||||||
#
|
#
|
||||||
# LolBot
|
# LolBot
|
||||||
#
|
#
|
||||||
# Code originally based on example bot and irc-bot class from
|
# New version based on Twisted IRC.
|
||||||
# Joel Rosdahl <joel@rosdahl.net>, author of included python-irclib.
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Useful bot for folks stuck behind censor walls at work
|
Useful bot for folks stuck behind censor walls at work
|
||||||
|
|
@ -11,20 +10,22 @@ Logs a channel and collects URLs for later.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import sys, string, random, time
|
import sys
|
||||||
from ircbot import SingleServerIRCBot, OutputManager
|
|
||||||
from irclib import nm_to_n, nm_to_h, irc_lower
|
|
||||||
import os
|
import os
|
||||||
|
import string
|
||||||
|
import random
|
||||||
|
import time
|
||||||
|
import getopt
|
||||||
|
from twisted.words.protocols import irc
|
||||||
|
from twisted.internet import protocol
|
||||||
|
from twisted.internet import reactor
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from mechanize import Browser
|
from mechanize import Browser
|
||||||
|
|
||||||
import getopt
|
|
||||||
from sqlalchemy import MetaData, Table, Column, String, Text, Integer, DateTime, engine_from_config
|
from sqlalchemy import MetaData, Table, Column, String, Text, Integer, DateTime, engine_from_config
|
||||||
from sqlalchemy.orm import sessionmaker
|
from sqlalchemy.orm import sessionmaker
|
||||||
from sqlalchemy.ext.declarative import declarative_base
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
except ImportError:
|
except ImportError:
|
||||||
print "Some modules could not be loaded: Lolbot relies on Mechanize and SQLAlchemy.\n"
|
print "Some modules missing: Lolbot relies on Twisted IRC, Mechanize and SQLAlchemy.\n"
|
||||||
sys.exit
|
sys.exit
|
||||||
|
|
||||||
# Exclamations - wrong input
|
# Exclamations - wrong input
|
||||||
|
|
@ -61,6 +62,9 @@ class Log(SqlBase):
|
||||||
text = Column(Text)
|
text = Column(Text)
|
||||||
|
|
||||||
def __init__(self, nickname, text, timestamp=None):
|
def __init__(self, nickname, text, timestamp=None):
|
||||||
|
"""
|
||||||
|
Creates an event log for the IRC logger.
|
||||||
|
"""
|
||||||
if timestamp is None:
|
if timestamp is None:
|
||||||
timestamp = datetime.now()
|
timestamp = datetime.now()
|
||||||
self.timestamp = timestamp
|
self.timestamp = timestamp
|
||||||
|
|
@ -107,15 +111,12 @@ class Url(SqlBase):
|
||||||
else:
|
else:
|
||||||
return "%s: %s - %s" % (self.nickname, self.url, self.title)
|
return "%s: %s - %s" % (self.nickname, self.url, self.title)
|
||||||
|
|
||||||
class LolBot(SingleServerIRCBot):
|
class LolBot(irc.IRCClient):
|
||||||
"""
|
"""
|
||||||
The Lolbot itself.
|
The Lolbot itself.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, config_path=''):
|
def created(self, when):
|
||||||
self.get_config(config_path)
|
|
||||||
SingleServerIRCBot.__init__(self, [(self.server, self.port)], self.nickname, self.nickname)
|
|
||||||
|
|
||||||
# connect to the database
|
# connect to the database
|
||||||
self.dbengine = engine_from_config(self.config, prefix="db.")
|
self.dbengine = engine_from_config(self.config, prefix="db.")
|
||||||
SqlBase.metadata.bind = self.dbengine
|
SqlBase.metadata.bind = self.dbengine
|
||||||
|
|
@ -124,74 +125,6 @@ class LolBot(SingleServerIRCBot):
|
||||||
|
|
||||||
self.helptext = "Keeps a list of URLs. Commands: list [n|x-y] - prints the last 10 URLs (or n URLs, or x through y); clear - clears the list; lol - say something funny; <url> - adds the URL to the list; help - this message."
|
self.helptext = "Keeps a list of URLs. Commands: list [n|x-y] - prints the last 10 URLs (or n URLs, or x through y); clear - clears the list; lol - say something funny; <url> - adds the URL to the list; help - this message."
|
||||||
|
|
||||||
self.queue = OutputManager(self.connection)
|
|
||||||
self.queue.start()
|
|
||||||
self.start()
|
|
||||||
|
|
||||||
def get_config(self, config_path):
|
|
||||||
"""
|
|
||||||
This method loads configuration options from a lolbot.conf file. The file
|
|
||||||
should look like this:
|
|
||||||
|
|
||||||
irc.server = irc.freenode.net
|
|
||||||
irc.port = 6667
|
|
||||||
irc.channel = #lolbottest
|
|
||||||
irc.nickname = lolbot
|
|
||||||
db.url = sqlite:///lolbot.db
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
if not config_path:
|
|
||||||
config_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "lolbot.conf")
|
|
||||||
if not os.path.exists(config_path):
|
|
||||||
print "Error: configuration file not found. By default lolbot will look for a lolbot.conf file in the same directory as the lolbot script, but you can override this by specifying a path on the command line with the --config option."
|
|
||||||
usage()
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# open the configuration file and grab all param=value declarations.
|
|
||||||
config = {}
|
|
||||||
with open(config_path) as f:
|
|
||||||
for line in f:
|
|
||||||
# skip comments
|
|
||||||
if line.strip().startswith("#") or line.strip() == "":
|
|
||||||
continue
|
|
||||||
|
|
||||||
# collect up param = value
|
|
||||||
try:
|
|
||||||
(param, value) = line.strip().split("=", 1)
|
|
||||||
if param.strip() != "":
|
|
||||||
config[param.strip()] = value.strip()
|
|
||||||
except ValueError:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# validate IRC host
|
|
||||||
if "irc.server" not in config.keys():
|
|
||||||
print "Error: the IRC server was not specified. Use --help for more information."
|
|
||||||
sys.exit(1)
|
|
||||||
self.server = config["irc.server"]
|
|
||||||
|
|
||||||
# validate IRC port
|
|
||||||
if "irc.port" not in config.keys():
|
|
||||||
config["irc.port"] = "6667"
|
|
||||||
try:
|
|
||||||
self.port = int(config["irc.port"])
|
|
||||||
except ValueError:
|
|
||||||
print "Error: the IRC port must be an integer. If not specified, lolbot will use the default IRC port value 6667. Use --help for more information."
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# validate IRC channel
|
|
||||||
if "irc.channel" not in config.keys() or not config["irc.channel"].startswith("#"):
|
|
||||||
print "Error: the IRC channel is not specified or incorrect. It must begin with a # - e.g. #mychatchannel. Use --help for more information."
|
|
||||||
sys.exit(1)
|
|
||||||
self.channel = config["irc.channel"]
|
|
||||||
|
|
||||||
# validate bot nickname
|
|
||||||
if "irc.nickname" not in config.keys():
|
|
||||||
config["irc.nickname"] = "lolbot"
|
|
||||||
self.nickname = config["irc.nickname"]
|
|
||||||
|
|
||||||
self.config = config
|
|
||||||
|
|
||||||
def now(self):
|
def now(self):
|
||||||
return datetime.today().strftime("%Y-%m-%d %H:%M:%S")
|
return datetime.today().strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
|
||||||
|
|
@ -221,34 +154,42 @@ class LolBot(SingleServerIRCBot):
|
||||||
except Exception, ex:
|
except Exception, ex:
|
||||||
print "Exception caught logging event: %s" % ex
|
print "Exception caught logging event: %s" % ex
|
||||||
|
|
||||||
def on_nicknameinuse(self, connection, event):
|
def _get_nickname(self):
|
||||||
self.nickname = connection.get_nickname() + "_"
|
return self.factory.nickname
|
||||||
connection.nick(self.nickname)
|
nickname = property(_get_nickname)
|
||||||
|
|
||||||
def on_welcome(self, connection, event):
|
def _get_channel(self):
|
||||||
connection.join(self.channel)
|
return self.factory.channel
|
||||||
|
channel = property(_get_channel)
|
||||||
|
|
||||||
def on_privmsg(self, connection, event):
|
def _get_config(self):
|
||||||
"Deal with a /msg private message."
|
return self.factory.config
|
||||||
from_nick = nm_to_n(event.source())
|
config = property(_get_config)
|
||||||
self.do_command(event, event.arguments()[0], from_nick)
|
|
||||||
|
|
||||||
def on_pubmsg(self, connection, event):
|
def signedOn(self):
|
||||||
"Deal with a public message in a channel."
|
self.join(self.channel)
|
||||||
|
print "Signed on as %s." % (self.nickname,)
|
||||||
|
|
||||||
|
def joined(self, channel):
|
||||||
|
print "Joined %s." % (channel,)
|
||||||
|
|
||||||
|
def privmsg(self, user, channel, msg):
|
||||||
|
if channel != self.channel:
|
||||||
|
# Private /msg from a user
|
||||||
|
self.do_command(msg, user)
|
||||||
|
else:
|
||||||
# log it
|
# log it
|
||||||
from_nick = nm_to_n(event.source())
|
self.log_event(user, msg)
|
||||||
self.log_event(from_nick, event.arguments()[0])
|
|
||||||
|
|
||||||
args = string.split(event.arguments()[0], ":", 1)
|
args = string.split(msg, ":", 1)
|
||||||
if len(args) > 1 and irc_lower(args[0]) == irc_lower(self.nickname):
|
if len(args) > 1 and args[0] == self.nickname:
|
||||||
self.do_command(event, string.strip(args[1]), from_nick)
|
self.do_command(string.strip(args[1]))
|
||||||
else:
|
else:
|
||||||
# parse it for links, add URLs to the list
|
# parse it for links, add URLs to the list
|
||||||
words = event.arguments()[0].split(" ")
|
words = msg.split(" ")
|
||||||
for w in words:
|
for w in words:
|
||||||
if w.startswith('http://') or w.startswith('https://'):
|
if w.startswith('http://') or w.startswith('https://'):
|
||||||
title = self.save_url(from_nick, w)
|
title = self.save_url(user, w)
|
||||||
if title == False:
|
if title == False:
|
||||||
self.say_public("Sorry, I'm useless at UTF-8.")
|
self.say_public("Sorry, I'm useless at UTF-8.")
|
||||||
else:
|
else:
|
||||||
|
|
@ -256,12 +197,12 @@ class LolBot(SingleServerIRCBot):
|
||||||
|
|
||||||
def say_public(self, text):
|
def say_public(self, text):
|
||||||
"Print TEXT into public channel, for all to see."
|
"Print TEXT into public channel, for all to see."
|
||||||
self.queue.send(text, self.channel)
|
self.msg(self.channel, text)
|
||||||
self.log_event(self.nickname, text)
|
self.log_event(self.nickname, text)
|
||||||
|
|
||||||
def say_private(self, nick, text):
|
def say_private(self, nick, text):
|
||||||
"Send private message of TEXT to NICK."
|
"Send private message of TEXT to NICK."
|
||||||
self.queue.send(text, nick)
|
self.msg(nick, text)
|
||||||
|
|
||||||
def reply(self, text, to_private=None):
|
def reply(self, text, to_private=None):
|
||||||
"Send TEXT to either public channel or TO_PRIVATE nick (if defined)."
|
"Send TEXT to either public channel or TO_PRIVATE nick (if defined)."
|
||||||
|
|
@ -278,16 +219,13 @@ class LolBot(SingleServerIRCBot):
|
||||||
"Return a random exclamation string."
|
"Return a random exclamation string."
|
||||||
return random.choice(exclamations)
|
return random.choice(exclamations)
|
||||||
|
|
||||||
def do_command(self, event, cmd, from_private):
|
def do_command(self, cmd, target=None):
|
||||||
"""
|
"""
|
||||||
This is the function called whenever someone sends a public or
|
This is the function called whenever someone sends a public or
|
||||||
private message addressed to the bot. (e.g. "bot: blah").
|
private message addressed to the bot. (e.g. "bot: blah").
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if event.eventtype() == "pubmsg":
|
target = target.strip()
|
||||||
target = None
|
|
||||||
else:
|
|
||||||
target = from_private.strip()
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if cmd == 'help':
|
if cmd == 'help':
|
||||||
|
|
@ -349,6 +287,100 @@ class LolBot(SingleServerIRCBot):
|
||||||
self.reply("Sorry, I didn't understand: %s" % cmd, target)
|
self.reply("Sorry, I didn't understand: %s" % cmd, target)
|
||||||
self.reply(self.helptext, target)
|
self.reply(self.helptext, target)
|
||||||
|
|
||||||
|
class LolBotFactory(protocol.ClientFactory):
|
||||||
|
protocol = LolBot
|
||||||
|
|
||||||
|
def __init__(self, config_path):
|
||||||
|
self.config = get_config(config_path)
|
||||||
|
self.server = self.config['irc.server']
|
||||||
|
self.port = self.config['irc.port']
|
||||||
|
self.nickname = self.config['irc.nickname']
|
||||||
|
self.channel = self.config['irc.channel']
|
||||||
|
|
||||||
|
def clientConnectionLost(self, connector, reason):
|
||||||
|
print "Lost connection (%s), reconnecting." % (reason,)
|
||||||
|
connector.connect()
|
||||||
|
|
||||||
|
def clientConnectionFailed(self, connector, reason):
|
||||||
|
print "Could not connect: %s" % (reason,)
|
||||||
|
|
||||||
|
def get_options():
|
||||||
|
try:
|
||||||
|
(options, args) = getopt.getopt(sys.argv[1:], "hc:", ["help", "config=", ])
|
||||||
|
except getopt.GetoptError, err:
|
||||||
|
print str(err)
|
||||||
|
usage()
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
|
config_path = ""
|
||||||
|
for option,value in options:
|
||||||
|
if option in ("-h", "--help"):
|
||||||
|
usage()
|
||||||
|
sys.exit(2)
|
||||||
|
if option in ("-c", "--config"):
|
||||||
|
config_path = value
|
||||||
|
return { 'config_path': config_path }
|
||||||
|
|
||||||
|
def get_config(config_path):
|
||||||
|
"""
|
||||||
|
This method loads configuration options from a lolbot.conf file. The file
|
||||||
|
should look like this:
|
||||||
|
|
||||||
|
irc.server = irc.freenode.net
|
||||||
|
irc.port = 6667
|
||||||
|
irc.channel = #lolbottest
|
||||||
|
irc.nickname = lolbot
|
||||||
|
db.url = sqlite:///lolbot.db
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not config_path:
|
||||||
|
config_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "lolbot.conf")
|
||||||
|
if not os.path.exists(config_path):
|
||||||
|
print "Error: configuration file not found. By default lolbot will look for a lolbot.conf file in the same directory as the lolbot script, but you can override this by specifying a path on the command line with the --config option."
|
||||||
|
usage()
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# open the configuration file and grab all param=value declarations.
|
||||||
|
config = {}
|
||||||
|
with open(config_path) as f:
|
||||||
|
for line in f:
|
||||||
|
# skip comments
|
||||||
|
if line.strip().startswith("#") or line.strip() == "":
|
||||||
|
continue
|
||||||
|
|
||||||
|
# collect up param = value
|
||||||
|
try:
|
||||||
|
(param, value) = line.strip().split("=", 1)
|
||||||
|
if param.strip() != "":
|
||||||
|
config[param.strip()] = value.strip()
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# validate IRC host
|
||||||
|
if "irc.server" not in config.keys():
|
||||||
|
print "Error: the IRC server was not specified. Use --help for more information."
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# validate IRC port
|
||||||
|
if "irc.port" not in config.keys():
|
||||||
|
config["irc.port"] = "6667"
|
||||||
|
try:
|
||||||
|
config["irc.port"] = int(config["irc.port"])
|
||||||
|
except ValueError:
|
||||||
|
print "Error: the IRC port must be an integer. If not specified, lolbot will use the default IRC port value 6667. Use --help for more information."
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# validate IRC channel
|
||||||
|
if "irc.channel" not in config.keys() or not config["irc.channel"].startswith("#"):
|
||||||
|
print "Error: the IRC channel is not specified or incorrect. It must begin with a # - e.g. #mychatchannel. Use --help for more information."
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# validate bot nickname
|
||||||
|
if "irc.nickname" not in config.keys():
|
||||||
|
config["irc.nickname"] = "lolbot"
|
||||||
|
|
||||||
|
return config
|
||||||
|
|
||||||
def usage():
|
def usage():
|
||||||
print """Run a lolbot.
|
print """Run a lolbot.
|
||||||
|
|
@ -382,23 +414,11 @@ Configuration:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
args = get_options()
|
||||||
|
config = get_config(args['config_path'])
|
||||||
try:
|
try:
|
||||||
(options, args) = getopt.getopt(sys.argv[1:], "hc:", ["help", "config=", ])
|
reactor.connectTCP(config['irc.server'], config['irc.port'], LolBotFactory(args['config_path']))
|
||||||
except getopt.GetoptError, err:
|
reactor.run()
|
||||||
print str(err)
|
|
||||||
usage()
|
|
||||||
sys.exit(2)
|
|
||||||
|
|
||||||
config_path = ""
|
|
||||||
for option,value in options:
|
|
||||||
if option in ("-h", "--help"):
|
|
||||||
usage()
|
|
||||||
sys.exit(2)
|
|
||||||
if option in ("-c", "--config"):
|
|
||||||
config_path = value
|
|
||||||
|
|
||||||
try:
|
|
||||||
LolBot(config_path).start()
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print "Shutting down."
|
print "Shutting down."
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue