mirror of
https://github.com/Mikaela/Limnoria.git
synced 2024-12-23 19:22:45 +01:00
a6d92a70e8
This should prevent the accumulation of old unclaimed notes in the database, which is possible due to notes left to misspelled nicks, to temporary nicks used by regulars, or to one-time visitor nicks.
241 lines
9.0 KiB
Python
241 lines
9.0 KiB
Python
###
|
|
# Copyright (c) 2004, Jeremiah Fincher
|
|
# Copyright (c) 2010, James Vega
|
|
# All rights reserved.
|
|
#
|
|
# Redistribution and use in source and binary forms, with or without
|
|
# modification, are permitted provided that the following conditions are met:
|
|
#
|
|
# * Redistributions of source code must retain the above copyright notice,
|
|
# this list of conditions, and the following disclaimer.
|
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
|
# this list of conditions, and the following disclaimer in the
|
|
# documentation and/or other materials provided with the distribution.
|
|
# * Neither the name of the author of this software nor the name of
|
|
# contributors to this software may be used to endorse or promote products
|
|
# derived from this software without specific prior written consent.
|
|
#
|
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
# POSSIBILITY OF SUCH DAMAGE.
|
|
###
|
|
|
|
import csv
|
|
import time
|
|
import datetime
|
|
|
|
import supybot.log as log
|
|
import supybot.conf as conf
|
|
import supybot.utils as utils
|
|
from supybot.commands import *
|
|
import supybot.ircmsgs as ircmsgs
|
|
import supybot.ircutils as ircutils
|
|
import supybot.callbacks as callbacks
|
|
from supybot.i18n import PluginInternationalization, internationalizeDocstring
|
|
_ = PluginInternationalization('Later')
|
|
|
|
class Later(callbacks.Plugin):
|
|
"""Used to do things later; currently, it only allows the sending of
|
|
nick-based notes. Do note (haha!) that these notes are *not* private
|
|
and don't even pretend to be; if you want such features, consider using the
|
|
Note plugin."""
|
|
def __init__(self, irc):
|
|
self.__parent = super(Later, self)
|
|
self.__parent.__init__(irc)
|
|
self._notes = ircutils.IrcDict()
|
|
self.wildcards = []
|
|
self.filename = conf.supybot.directories.data.dirize('Later.db')
|
|
self._openNotes()
|
|
|
|
def die(self):
|
|
self._flushNotes()
|
|
|
|
def _flushNotes(self):
|
|
fd = utils.file.AtomicFile(self.filename)
|
|
writer = csv.writer(fd)
|
|
for (nick, notes) in self._notes.iteritems():
|
|
for (time, whence, text) in notes:
|
|
writer.writerow([nick, time, whence, text])
|
|
fd.close()
|
|
|
|
def _openNotes(self):
|
|
try:
|
|
fd = file(self.filename)
|
|
except EnvironmentError, e:
|
|
self.log.warning('Couldn\'t open %s: %s', self.filename, e)
|
|
return
|
|
reader = csv.reader(fd)
|
|
for (nick, time, whence, text) in reader:
|
|
self._addNote(nick, whence, text, at=float(time), maximum=0)
|
|
fd.close()
|
|
|
|
def _timestamp(self, when):
|
|
#format = conf.supybot.reply.format.time()
|
|
diff = time.time() - when
|
|
try:
|
|
return _('%s ago') % utils.timeElapsed(diff, seconds=False)
|
|
except ValueError:
|
|
return _('just now')
|
|
|
|
def _addNote(self, nick, whence, text, at=None, maximum=None):
|
|
if at is None:
|
|
at = time.time()
|
|
if maximum is None:
|
|
maximum = self.registryValue('maximum')
|
|
try:
|
|
notes = self._notes[nick]
|
|
if maximum and len(notes) >= maximum:
|
|
raise ValueError
|
|
else:
|
|
notes.append((at, whence, text))
|
|
except KeyError:
|
|
self._notes[nick] = [(at, whence, text)]
|
|
if '?' in nick or '*' in nick and nick not in self.wildcards:
|
|
self.wildcards.append(nick)
|
|
self._flushNotes()
|
|
|
|
def _validateNick(self, irc, nick):
|
|
"""Validate nick according to the IRC RFC 2812 spec.
|
|
|
|
Reference: http://tools.ietf.org/rfcmarkup?doc=2812#section-2.3.1
|
|
|
|
Some irc clients' tab-completion feature appends 'address' characters
|
|
to nick, such as ':' or ','. We try correcting for that by trimming
|
|
a char off the end.
|
|
|
|
If nick incorrigibly invalid, return False, otherwise,
|
|
return (possibly trimmed) nick.
|
|
"""
|
|
if not irc.isNick(nick):
|
|
if not irc.isNick(nick[:-1]):
|
|
return False
|
|
else:
|
|
return nick[:-1]
|
|
return nick
|
|
|
|
def _deleteExpired(self):
|
|
expiry = self.registryValue('messageExpiry')
|
|
curtime = time.time()
|
|
nickremovals=[]
|
|
for (nick, notes) in self._notes.iteritems():
|
|
removals = []
|
|
for (notetime, whence, text) in notes:
|
|
td = datetime.timedelta(seconds=(curtime - notetime))
|
|
if td.days > expiry:
|
|
removals.append((notetime, whence, text))
|
|
for note in removals:
|
|
notes.remove(note)
|
|
if len(notes) == 0:
|
|
nickremovals.append(nick)
|
|
for nick in nickremovals:
|
|
del self._notes[nick]
|
|
self._flushNotes()
|
|
|
|
## Note: we call _deleteExpired from 'tell'. This means that it's possible
|
|
## for expired notes to remain in the database for longer than the maximum,
|
|
## if no tell's are called.
|
|
## However, the whole point of this is to avoid crud accumulation in the
|
|
## database, so it's fine that we only delete expired notes when we try
|
|
## adding new ones.
|
|
|
|
@internationalizeDocstring
|
|
def tell(self, irc, msg, args, nick, text):
|
|
"""<nick> <text>
|
|
|
|
Tells <nick> <text> the next time <nick> is in seen. <nick> can
|
|
contain wildcard characters, and the first matching nick will be
|
|
given the note.
|
|
"""
|
|
self._deleteExpired()
|
|
if ircutils.strEqual(nick, irc.nick):
|
|
irc.error(_('I can\'t send notes to myself.'))
|
|
return
|
|
validnick = self._validateNick(irc, nick)
|
|
if validnick is False:
|
|
irc.error('That is an invalid IRC nick. Please check your input.')
|
|
return
|
|
try:
|
|
self._addNote(validnick, msg.nick, text)
|
|
irc.replySuccess()
|
|
except ValueError:
|
|
irc.error(_('That person\'s message queue is already full.'))
|
|
tell = wrap(tell, ['something', 'text'])
|
|
|
|
@internationalizeDocstring
|
|
def notes(self, irc, msg, args, nick):
|
|
"""[<nick>]
|
|
|
|
If <nick> is given, replies with what notes are waiting on <nick>,
|
|
otherwise, replies with the nicks that have notes waiting for them.
|
|
"""
|
|
if nick:
|
|
if nick in self._notes:
|
|
notes = [self._formatNote(when, whence, note)
|
|
for (when, whence, note) in self._notes[nick]]
|
|
irc.reply(format('%L', notes))
|
|
else:
|
|
irc.error(_('I have no notes for that nick.'))
|
|
else:
|
|
nicks = self._notes.keys()
|
|
if nicks:
|
|
utils.sortBy(ircutils.toLower, nicks)
|
|
irc.reply(format(_('I currently have notes waiting for %L.'),
|
|
nicks))
|
|
else:
|
|
irc.error(_('I have no notes waiting to be delivered.'))
|
|
notes = wrap(notes, [additional('something')])
|
|
|
|
@internationalizeDocstring
|
|
def remove(self, irc, msg, args, nick):
|
|
"""<nick>
|
|
|
|
Removes the notes waiting on <nick>.
|
|
"""
|
|
try:
|
|
del self._notes[nick]
|
|
self._flushNotes()
|
|
irc.replySuccess()
|
|
except KeyError:
|
|
irc.error(_('There were no notes for %r') % nick)
|
|
remove = wrap(remove, [('checkCapability', 'admin'), 'something'])
|
|
|
|
def doPrivmsg(self, irc, msg):
|
|
if ircmsgs.isCtcp(msg) and not ircmsgs.isAction(msg):
|
|
return
|
|
notes = self._notes.pop(msg.nick, [])
|
|
# Let's try wildcards.
|
|
removals = []
|
|
for wildcard in self.wildcards:
|
|
if ircutils.hostmaskPatternEqual(wildcard, msg.nick):
|
|
removals.append(wildcard)
|
|
notes.extend(self._notes.pop(wildcard))
|
|
for removal in removals:
|
|
self.wildcards.remove(removal)
|
|
if notes:
|
|
irc = callbacks.SimpleProxy(irc, msg)
|
|
private = self.registryValue('private')
|
|
for (when, whence, note) in notes:
|
|
s = self._formatNote(when, whence, note)
|
|
irc.reply(s, private=private)
|
|
self._flushNotes()
|
|
|
|
def _formatNote(self, when, whence, note):
|
|
return _('Sent %s: <%s> %s') % (self._timestamp(when), whence, note)
|
|
|
|
def doJoin(self, irc, msg):
|
|
if self.registryValue('tellOnJoin'):
|
|
self.doPrivmsg(irc, msg)
|
|
Later = internationalizeDocstring(Later)
|
|
|
|
Class = Later
|
|
|
|
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:
|