144 lines
5.4 KiB
Python
144 lines
5.4 KiB
Python
###
|
||
# Copyright (c) 2019, Pedro de Oliveira
|
||
# 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.
|
||
###
|
||
|
||
from supybot import utils, plugins, ircmsgs, ircutils, callbacks
|
||
from supybot.commands import *
|
||
import supybot.dbi as dbi
|
||
import supybot.utils.minisix as minisix
|
||
import sqlite3
|
||
import os
|
||
from urllib.parse import urlparse
|
||
import arrow
|
||
try:
|
||
from supybot.i18n import PluginInternationalization
|
||
_ = PluginInternationalization('Old')
|
||
except ImportError:
|
||
# Placeholder that allows to run the plugin on a bot
|
||
# without the i18n module
|
||
_ = lambda x: x
|
||
|
||
class SqliteEtaDB(object):
|
||
def __init__(self, filename):
|
||
self.dbs = ircutils.IrcDict()
|
||
self.filename = filename
|
||
|
||
def close(self):
|
||
for db in self.dbs.values():
|
||
db.close()
|
||
|
||
def _getDb(self, channel):
|
||
filename = plugins.makeChannelFilename(self.filename, channel)
|
||
if filename in self.dbs:
|
||
return self.dbs[filename]
|
||
if os.path.exists(filename):
|
||
db = sqlite3.connect(filename, check_same_thread=False)
|
||
if minisix.PY2:
|
||
db.text_factory = str
|
||
self.dbs[filename] = db
|
||
return db
|
||
db = sqlite3.connect(filename, check_same_thread=False)
|
||
if minisix.PY2:
|
||
db.text_factory = str
|
||
self.dbs[filename] = db
|
||
cursor = db.cursor()
|
||
cursor.execute("""CREATE TABLE urltbl(
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
nick TEXT NOT NULL,
|
||
url TEXT UNIQUE NOT NULL,
|
||
dt DATETIME NOT NULL
|
||
)""")
|
||
db.commit()
|
||
return db
|
||
|
||
def _fix_url(self, url):
|
||
parsed_url = urlparse(url)
|
||
if parsed_url.path == "":
|
||
parsed_url = parsed_url._replace(path="/")
|
||
return parsed_url.geturl()
|
||
|
||
|
||
def add(self, channel, url, msg):
|
||
db = self._getDb(channel)
|
||
fixed_url = self._fix_url(url)
|
||
cursor = db.cursor()
|
||
cursor.execute("""INSERT INTO urltbl
|
||
(nick, url, dt)
|
||
VALUES (?, ?, ?)""",
|
||
(msg.nick.lower(), fixed_url, msg.receivedAt,))
|
||
db.commit()
|
||
|
||
def find(self, channel, url):
|
||
db = self._getDb(channel)
|
||
fixed_url = self._fix_url(url)
|
||
cursor = db.cursor()
|
||
cursor.execute("""SELECT nick, dt FROM urltbl
|
||
WHERE url=?""", (fixed_url,))
|
||
return cursor.fetchone()
|
||
|
||
URLDB = plugins.DB('Old',
|
||
{'sqlite3': SqliteEtaDB})
|
||
|
||
class Old(callbacks.Plugin):
|
||
"""This plugin records URLs mentioned in a channel, and
|
||
reports if it is old."""
|
||
def __init__(self, irc):
|
||
self.__parent = super(Old, self)
|
||
self.__parent.__init__(irc)
|
||
self.db = URLDB()
|
||
|
||
def doPrivmsg(self, irc, msg):
|
||
if ircmsgs.isCtcp(msg) and not ircmsgs.isAction(msg):
|
||
return
|
||
if msg.channel:
|
||
if ircmsgs.isAction(msg):
|
||
text = ircmsgs.unAction(msg)
|
||
else:
|
||
text = msg.args[1]
|
||
for url in utils.web.urlRe.findall(text):
|
||
exists = self.db.find(msg.channel, url)
|
||
if exists is None:
|
||
self.log.debug('Adding %u to db.', url)
|
||
self.db.add(msg.channel, url, msg)
|
||
else:
|
||
orig_nick = exists[0]
|
||
orig_dt = exists[1]
|
||
if orig_nick != msg.nick.lower():
|
||
no_hilight_nick = orig_nick[:1] + "" + orig_nick[1:]
|
||
past = arrow.get(orig_dt)
|
||
now = arrow.get(msg.receivedAt)
|
||
dt_str = past.humanize(now)
|
||
|
||
irc.reply("BAH! {}'s link is OLD! (shown {} by {})".format(
|
||
msg.nick.lower(), dt_str, no_hilight_nick
|
||
), prefixNick=False)
|
||
|
||
Class = Old
|
||
|
||
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:
|