added logging
This commit is contained in:
parent
601bf4de02
commit
ea4e7a6653
7
.gitignore
vendored
7
.gitignore
vendored
|
@ -2,9 +2,10 @@ __pycache__/
|
||||||
*/__pycache__/
|
*/__pycache__/
|
||||||
logs/
|
logs/
|
||||||
*.cfg
|
*.cfg
|
||||||
*.db
|
|
||||||
*.pid
|
|
||||||
*.dat
|
*.dat
|
||||||
*.txt
|
*.db
|
||||||
|
*.log
|
||||||
|
*.pid
|
||||||
*.swp
|
*.swp
|
||||||
|
*.txt
|
||||||
tourettes.py
|
tourettes.py
|
||||||
|
|
213
bot.py
213
bot.py
|
@ -8,6 +8,7 @@ import time
|
||||||
import functools
|
import functools
|
||||||
import threading
|
import threading
|
||||||
import traceback
|
import traceback
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
from twisted.internet import protocol
|
from twisted.internet import protocol
|
||||||
from twisted.words.protocols import irc
|
from twisted.words.protocols import irc
|
||||||
|
@ -32,12 +33,29 @@ class Fulvia(irc.IRCClient):
|
||||||
self.username = config.core.username
|
self.username = config.core.username
|
||||||
"""The bot's username ident used for logging into the server."""
|
"""The bot's username ident used for logging into the server."""
|
||||||
|
|
||||||
|
self.host = ""
|
||||||
|
"""The bot's host, virtual or otherwise. To be filled in later."""
|
||||||
|
|
||||||
self.prefix = config.core.prefix
|
self.prefix = config.core.prefix
|
||||||
"""The command prefix the bot watches for."""
|
"""The command prefix the bot watches for."""
|
||||||
|
|
||||||
self.static = os.path.join(config.homedir, "static")
|
self.static = os.path.join(config.homedir, "static")
|
||||||
|
os.makedirs(self.static, exist_ok=True)
|
||||||
"""The path to the bot's static file directory."""
|
"""The path to the bot's static file directory."""
|
||||||
|
|
||||||
|
self.log_path = os.path.join(config.homedir, "logs")
|
||||||
|
os.makedirs(self.static, exist_ok=True)
|
||||||
|
"""The path to the bot's log files."""
|
||||||
|
|
||||||
|
self._log_dump = tools.FulviaMemoryDefault(list)
|
||||||
|
"""
|
||||||
|
Internal thread-safe dictionary containing log dumps yet to be
|
||||||
|
written.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self._last_log_dump = 0
|
||||||
|
"""The last time logs were dumped."""
|
||||||
|
|
||||||
self.channels = tools.FulviaMemory()
|
self.channels = tools.FulviaMemory()
|
||||||
"""
|
"""
|
||||||
A dictionary of all channels the bot is currently in and the users
|
A dictionary of all channels the bot is currently in and the users
|
||||||
|
@ -195,6 +213,30 @@ class Fulvia(irc.IRCClient):
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
|
|
||||||
|
def log(self, channel, text):
|
||||||
|
"""
|
||||||
|
Logs text to file. Logging structure is
|
||||||
|
{self.log_path}/{self.server}/{channel}/{date}.log
|
||||||
|
|
||||||
|
Only permits log dumping once per second.
|
||||||
|
"""
|
||||||
|
# TODO: use time module instead of datetime
|
||||||
|
t = datetime.fromtimestamp(time.time())
|
||||||
|
timestamp = t.strftime(self.config.core.default_time_format)
|
||||||
|
self._log_dump[channel].append(timestamp + " " + text)
|
||||||
|
|
||||||
|
if time.time() - self._last_log_dump > 1:
|
||||||
|
for ch in self._log_dump.keys():
|
||||||
|
fname = t.strftime("%Y-%m-%d") + ".log"
|
||||||
|
path = os.path.join(self.log_path, self.hostname, ch)
|
||||||
|
os.makedirs(path, exist_ok=True)
|
||||||
|
with open(os.path.join(path, fname), "a+") as file:
|
||||||
|
file.write("\n".join(self._log_dump[ch]) + "\n")
|
||||||
|
|
||||||
|
del(self._log_dump)
|
||||||
|
self._log_dump = tools.FulviaMemoryDefault(list)
|
||||||
|
|
||||||
|
|
||||||
## Actions involving the bot directly.
|
## Actions involving the bot directly.
|
||||||
## These will get called automatically.
|
## These will get called automatically.
|
||||||
|
|
||||||
|
@ -203,7 +245,16 @@ class Fulvia(irc.IRCClient):
|
||||||
Called when the bot receives a PRIVMSG, which can come from channels
|
Called when the bot receives a PRIVMSG, which can come from channels
|
||||||
or users alike.
|
or users alike.
|
||||||
"""
|
"""
|
||||||
func_names = []
|
nick = user.partition("!")[0].partition("@")[0]
|
||||||
|
if channel.startswith("#"):
|
||||||
|
opSym = tools.getOpSym(self.channels[channel].privileges[nick])
|
||||||
|
else:
|
||||||
|
opSym = ""
|
||||||
|
channel = nick
|
||||||
|
line = "<" + opSym + nick + ">" + " " + message
|
||||||
|
self.log(channel, line)
|
||||||
|
|
||||||
|
func_names = []
|
||||||
if message.startswith(self.prefix) and message != self.prefix:
|
if message.startswith(self.prefix) and message != self.prefix:
|
||||||
command, _, _ = message.partition(" ")
|
command, _, _ = message.partition(" ")
|
||||||
command = command.replace(self.prefix, "", 1)
|
command = command.replace(self.prefix, "", 1)
|
||||||
|
@ -246,19 +297,44 @@ class Fulvia(irc.IRCClient):
|
||||||
|
|
||||||
def joined(self, channel):
|
def joined(self, channel):
|
||||||
"""Called when the bot joins a new channel."""
|
"""Called when the bot joins a new channel."""
|
||||||
|
line = "-!- " + self.nickname + " " + "[" + self.username + "@"
|
||||||
|
line += self.host + "] has joined " + channel
|
||||||
|
self.log(channel, line)
|
||||||
|
|
||||||
print(f"Joined {channel}")
|
print(f"Joined {channel}")
|
||||||
if channel not in self.channels:
|
if channel not in self.channels:
|
||||||
self.channels[channel] = tools.Channel(channel)
|
self.channels[channel] = tools.Channel(channel)
|
||||||
|
|
||||||
|
|
||||||
def left(self, channel):
|
def left(self, channel, reason=""):
|
||||||
"""Called when the leaves a channel."""
|
"""Called when the leaves a channel."""
|
||||||
|
line = "-!- " + self.nickname + " " + "[" + self.username + "@"
|
||||||
|
line += self.host + "] has left " + channel + " [" + reason + "]"
|
||||||
|
self.log(channel, line)
|
||||||
|
|
||||||
print(f"Parted {channel}")
|
print(f"Parted {channel}")
|
||||||
self.channels.pop(channel)
|
self.channels.pop(channel)
|
||||||
|
|
||||||
|
|
||||||
|
def noticed(self, user, channel, message):
|
||||||
|
"""Called when the bot receives a NOTICE from a user or channel."""
|
||||||
|
# TODO: something?
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def modeChanged(self, user, channel, add_mode, modes, args):
|
def modeChanged(self, user, channel, add_mode, modes, args):
|
||||||
"""Called when users or channel's modes are changed."""
|
"""Called when users or channel's modes are changed."""
|
||||||
|
line = "-!- mode/" + channel + " ["
|
||||||
|
if add_mode:
|
||||||
|
line += "+"
|
||||||
|
else:
|
||||||
|
line += "-"
|
||||||
|
line += modes
|
||||||
|
if any(args):
|
||||||
|
line += " " + " ".join(args)
|
||||||
|
line += "] by " + user.partition("!")[0].partition("@")[0]
|
||||||
|
self.log(channel, line)
|
||||||
|
|
||||||
if not channel.startswith("#"): # server level
|
if not channel.startswith("#"): # server level
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -284,12 +360,22 @@ class Fulvia(irc.IRCClient):
|
||||||
def signedOn(self):
|
def signedOn(self):
|
||||||
"""Called when the bot successfully connects to the server."""
|
"""Called when the bot successfully connects to the server."""
|
||||||
print(f"Signed on as {self.nickname}")
|
print(f"Signed on as {self.nickname}")
|
||||||
|
self.whois(self.nickname)
|
||||||
|
|
||||||
|
line = "*** Signed onto " + self.hostname + " as "
|
||||||
|
line += self.nickname + "!" + self.username + "@" + self.host
|
||||||
|
self.log(self.hostname, line)
|
||||||
|
|
||||||
for channel in self.config.core.channels.split(","):
|
for channel in self.config.core.channels.split(","):
|
||||||
self.join(channel)
|
self.join(channel)
|
||||||
|
|
||||||
|
|
||||||
def kickedFrom(self, channel, kicker, message):
|
def kickedFrom(self, channel, kicker, message):
|
||||||
"""Called when the bot is kicked from a channel."""
|
"""Called when the bot is kicked from a channel."""
|
||||||
|
line = "-!- " + self.nickname + " was kicked from " + channel
|
||||||
|
line += " by " + kicker + " [" + message + "]"
|
||||||
|
self.log(channel, line)
|
||||||
|
|
||||||
self.channels.pop(channel)
|
self.channels.pop(channel)
|
||||||
|
|
||||||
|
|
||||||
|
@ -298,43 +384,69 @@ class Fulvia(irc.IRCClient):
|
||||||
|
|
||||||
def userJoined(self, user, channel):
|
def userJoined(self, user, channel):
|
||||||
"""Called when the bot sees another user join a channel."""
|
"""Called when the bot sees another user join a channel."""
|
||||||
if user not in self.users:
|
nick, _, host = user.partition("!")
|
||||||
self.users[user] = tools.User(user)
|
|
||||||
self.channels[channel].add_user(self.users[user])
|
line = "-!- " + nick + " " + "[" + host + "] has joined " + channel
|
||||||
|
self.log(channel, line)
|
||||||
|
|
||||||
|
if nick not in self.users:
|
||||||
|
self.users[nick] = tools.User(nick)
|
||||||
|
self.channels[channel].add_user(self.users[nick])
|
||||||
|
|
||||||
|
|
||||||
def userLeft(self, user, channel):
|
def userLeft(self, user, channel, reason=""):
|
||||||
"""Called when the bot sees another user join a channel."""
|
"""Called when the bot sees another user leave a channel."""
|
||||||
self.channels[channel].remove_user(user)
|
nick, _, host = user.partition("!")
|
||||||
|
|
||||||
|
line = "-!- " + nick + " " + "[" + host + "] has left "
|
||||||
|
line += channel + " [" + reason + "]"
|
||||||
|
self.log(channel, line)
|
||||||
|
|
||||||
|
self.channels[channel].remove_user(nick)
|
||||||
|
|
||||||
|
|
||||||
def userQuit(self, user, quitMessage):
|
def userQuit(self, user, quitMessage):
|
||||||
"""Called when the bot sees another user disconnect from the network."""
|
"""Called when the bot sees another user disconnect from the network."""
|
||||||
channels = list(self.users[user].channels.keys())
|
nick, _, host = user.partition("!")
|
||||||
|
line = "-!- " + nick + " [" + host + "] has quit [" + quitMessage + "]"
|
||||||
|
|
||||||
|
channels = list(self.users[nick].channels.keys())
|
||||||
for channel in channels:
|
for channel in channels:
|
||||||
# self.users[user].channels[channel].remove_user
|
self.log(channel, line)
|
||||||
# channel.remove_user(user)
|
|
||||||
self.channels[channel].remove_user(user)
|
self.channels[channel].remove_user(nick)
|
||||||
self.users.pop(user)
|
self.users.pop(nick)
|
||||||
|
|
||||||
|
|
||||||
def userKicked(self, kickee, channel, kicker, message):
|
def userKicked(self, kickee, channel, kicker, message):
|
||||||
"""
|
"""
|
||||||
Called when the bot sees another user getting kicked from the channel.
|
Called when the bot sees another user getting kicked from the channel.
|
||||||
"""
|
"""
|
||||||
|
line = "-!- " + kickee + " was kicked from " + channel
|
||||||
|
line += " by " + kicker + " [" + message + "]"
|
||||||
|
self.log(channel, line)
|
||||||
|
|
||||||
self.channels[channel].remove_user(kickee)
|
self.channels[channel].remove_user(kickee)
|
||||||
|
|
||||||
|
|
||||||
def topicUpdated(self, user, channel, newTopic):
|
def topicUpdated(self, user, channel, newTopic):
|
||||||
"""Called when the bot sees a user update the channel's topic."""
|
"""Called when the bot sees a user update the channel's topic."""
|
||||||
|
line = "-!- " + user + " changed the topic of " + channel + " to: "
|
||||||
|
line += newTopic
|
||||||
|
self.log(channel, line)
|
||||||
|
|
||||||
self.channels[channel].topic = newTopic
|
self.channels[channel].topic = newTopic
|
||||||
|
|
||||||
|
|
||||||
def userRenamed(self, oldname, newname):
|
def userRenamed(self, oldname, newname):
|
||||||
"""Called wehn the bot sees a user change their nickname."""
|
"""Called wehn the bot sees a user change their nickname."""
|
||||||
|
line = "-!- " + oldname + " is now known as " + newname
|
||||||
|
|
||||||
user = self.users.pop(oldname)
|
user = self.users.pop(oldname)
|
||||||
self.users[newname] = user
|
self.users[newname] = user
|
||||||
for key, channel in user.channels.items():
|
for key, channel in user.channels.items():
|
||||||
|
self.log(key, line)
|
||||||
|
|
||||||
channel.rename_user(oldname, newname)
|
channel.rename_user(oldname, newname)
|
||||||
user.nick = newname
|
user.nick = newname
|
||||||
|
|
||||||
|
@ -354,8 +466,36 @@ class Fulvia(irc.IRCClient):
|
||||||
self.channels[channel].privileges[nick] = op_level
|
self.channels[channel].privileges[nick] = op_level
|
||||||
|
|
||||||
|
|
||||||
|
def whoisUser(self, nick, ident, host, realname):
|
||||||
|
"""
|
||||||
|
Called when the bot receives a WHOIS about a particular user.
|
||||||
|
"""
|
||||||
|
if nick == self.nickname:
|
||||||
|
self.username = ident
|
||||||
|
self.host = host
|
||||||
|
self.realname = realname
|
||||||
|
|
||||||
|
|
||||||
## User commands, from client->server
|
## User commands, from client->server
|
||||||
|
|
||||||
|
def msg(self, user, message, length=None):
|
||||||
|
"""
|
||||||
|
Sends a message 'message' to 'user' (can be a user or a channel).
|
||||||
|
If 'length' is specified, the message will be split into lengths
|
||||||
|
of that size. If 'length' is not specified, the bot will determine
|
||||||
|
an appropriate length automatically.
|
||||||
|
"""
|
||||||
|
if user.startswith("#"):
|
||||||
|
priv = self.channels[user].privileges[self.nickname]
|
||||||
|
opSym = tools.getOpSym(priv)
|
||||||
|
else:
|
||||||
|
opSym = ""
|
||||||
|
line = "<" + opSym + self.nickname + ">" + " " + message
|
||||||
|
self.log(user, line)
|
||||||
|
|
||||||
|
irc.IRCClient.msg(self, user, message, length=None)
|
||||||
|
|
||||||
|
|
||||||
def reply(self, text, dest, reply_to, notice=False):
|
def reply(self, text, dest, reply_to, notice=False):
|
||||||
"""
|
"""
|
||||||
Sends a message to 'dest' prefixed with 'reply_to' and a colon,
|
Sends a message to 'dest' prefixed with 'reply_to' and a colon,
|
||||||
|
@ -390,6 +530,53 @@ class Fulvia(irc.IRCClient):
|
||||||
self.namesReceived(channel, channel_type, nicklist)
|
self.namesReceived(channel, channel_type, nicklist)
|
||||||
|
|
||||||
|
|
||||||
|
def irc_JOIN(self, prefix, params):
|
||||||
|
"""
|
||||||
|
Called when a user joins a channel.
|
||||||
|
"""
|
||||||
|
nick = prefix.split('!')[0]
|
||||||
|
channel = params[-1]
|
||||||
|
if nick == self.nickname:
|
||||||
|
self.joined(channel)
|
||||||
|
else:
|
||||||
|
self.userJoined(prefix, channel)
|
||||||
|
|
||||||
|
|
||||||
|
def irc_PART(self, prefix, params):
|
||||||
|
"""
|
||||||
|
Called when a user leaves a channel.
|
||||||
|
"""
|
||||||
|
nick = prefix.split('!')[0]
|
||||||
|
channel = params[0]
|
||||||
|
if len(params) > 1:
|
||||||
|
reason = params[1]
|
||||||
|
else:
|
||||||
|
reason = ""
|
||||||
|
|
||||||
|
if nick == self.nickname:
|
||||||
|
self.left(channel, reason)
|
||||||
|
else:
|
||||||
|
self.userLeft(prefix, channel, reason)
|
||||||
|
|
||||||
|
|
||||||
|
def irc_QUIT(self, prefix, params):
|
||||||
|
"""
|
||||||
|
Called when a user has quit.
|
||||||
|
"""
|
||||||
|
nick = prefix.split('!')[0]
|
||||||
|
self.userQuit(prefix, params[0])
|
||||||
|
|
||||||
|
|
||||||
|
def irc_RPL_WHOISUSER(self, prefix, params):
|
||||||
|
"""
|
||||||
|
Called when we receive the server's response from our "WHOIS [user]"
|
||||||
|
query.
|
||||||
|
"""
|
||||||
|
_, nick, ident, host, _, realname = params
|
||||||
|
self.whoisUser(nick, ident, host, realname)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class FulviaWrapper():
|
class FulviaWrapper():
|
||||||
"""
|
"""
|
||||||
A wrapper class for Fulvia to provide default destinations for msg
|
A wrapper class for Fulvia to provide default destinations for msg
|
||||||
|
|
|
@ -4,6 +4,7 @@ Some helper functions and other tools.
|
||||||
"""
|
"""
|
||||||
import re
|
import re
|
||||||
import threading
|
import threading
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
op_level = {
|
op_level = {
|
||||||
"voice": 1,
|
"voice": 1,
|
||||||
|
@ -55,6 +56,36 @@ class FulviaMemory(dict):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
class FulviaMemoryDefault(defaultdict):
|
||||||
|
"""
|
||||||
|
A simple thread-safe dict implementation.
|
||||||
|
|
||||||
|
In order to prevent exceptions when iterating over the values and changing
|
||||||
|
them at the same time from different threads, we use a blocking lock on
|
||||||
|
``__setitem__`` and ``__contains__``.
|
||||||
|
"""
|
||||||
|
def __init__(self, *args):
|
||||||
|
defaultdict.__init__(self, *args)
|
||||||
|
self.lock = threading.Lock()
|
||||||
|
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
self.lock.acquire()
|
||||||
|
result = defaultdict.__setitem__(self, key, value)
|
||||||
|
self.lock.release()
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def __contains__(self, key):
|
||||||
|
"""
|
||||||
|
Check if a key is in the dict. Locks it for writes when doing so.
|
||||||
|
"""
|
||||||
|
self.lock.acquire()
|
||||||
|
result = defaultdict.__contains__(self, key)
|
||||||
|
self.lock.release()
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
class User(object):
|
class User(object):
|
||||||
"""A representation of a user Fulvia is aware of."""
|
"""A representation of a user Fulvia is aware of."""
|
||||||
def __init__(self, nick):
|
def __init__(self, nick):
|
||||||
|
@ -168,3 +199,19 @@ def configureHostMask(mask):
|
||||||
if m is not None:
|
if m is not None:
|
||||||
return '%s!%s@*' % (m.group(1), m.group(2))
|
return '%s!%s@*' % (m.group(1), m.group(2))
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
|
||||||
|
def getOpSym(level):
|
||||||
|
"""Returns the appropriate op_level symbol given a level value."""
|
||||||
|
if level >= op_level["owner"]:
|
||||||
|
return "~"
|
||||||
|
elif level >= op_level["admin"]:
|
||||||
|
return "&"
|
||||||
|
elif level >= op_level["op"]:
|
||||||
|
return "@"
|
||||||
|
elif level >= op_level["halfop"]:
|
||||||
|
return "%"
|
||||||
|
elif level >= op_level["voice"]:
|
||||||
|
return "+"
|
||||||
|
else:
|
||||||
|
return " "
|
||||||
|
|
Loading…
Reference in New Issue
Block a user