###
# Copyright (c) 2021, B. Watson
# All rights reserved.
#
#
###
import os
import glob
import subprocess
import sys
import re
import sqlite3
from supybot import utils, plugins, ircutils, callbacks
from supybot.commands import *
import supybot.utils.minisix as minisix
try:
from supybot.i18n import PluginInternationalization
_ = PluginInternationalization('SBoTools')
except ImportError:
# Placeholder that allows to run the plugin on a bot
# without the i18n module
_ = lambda x: x
class SBoTools(callbacks.Plugin):
"""Provides SlackBuilds.org build searches"""
threaded = True
db = None
categories = None
def getMaxResults(self, msg):
maxresults = self.registryValue('maxresults')
if msg.channel is None:
# private message, increase limit
maxresults *= 5
return maxresults
def getMaxDeps(self, msg):
maxdeps = self.registryValue('maxdeps')
if msg.channel is None:
# private message, increase limit
maxdeps *= 10
return maxdeps
def InitDB(self):
filename = self.registryValue('dbpath')
# if updated database exists under as newfilename, it's
# complete and flushed, ready to use. the db-creation
# script that runs from cron uses a different name for
# db-creation, and renames to .new only when finished.
# no error-checking (if the rename fails, the bot logs an
# exception and someone has to fix it manually).
newfilename = filename + ".new"
if(os.path.exists(newfilename)):
if(self.db is not None):
self.db.close()
self.db = None
os.rename(newfilename, filename)
if self.db is None:
filename = self.registryValue('dbpath')
if(os.path.exists(filename)):
self.db = sqlite3.connect(filename)
return self.db
def getCategory(self, cat):
if self.categories is None:
self.categories = {}
db = self.InitDB();
cursor = db.cursor()
cursor.execute("select id, name from categories");
result = cursor.fetchall()
for (id, name) in result:
self.categories[name] = id
if cat in self.categories:
return self.categories[cat]
else:
return None
def sbosearch(self, irc, msg, args, tflag, terms):
""" [] | [-t] [ ...]>
Search the SlackBuilds.org build database. Case-insensitive
substring match. Without -t, does a search by name only. With
-t, searches names, descriptions, and tags. With multiple search
terms, results are ANDed together.
"""
category = None
if(not tflag):
if(len(terms) > 1):
irc.error("only one search term allowed unless using -t flag", Raise=True)
t = terms[0].split('/',1)
if(len(t) != 1):
category = self.getCategory(t[0])
if(category is None):
irc.error("invalid category '" + t[0] + "'", Raise=True)
terms[0] = t[1]
db = self.InitDB();
cursor = db.cursor()
tags = False;
if(tflag):
sql = "select distinct(b.id) from builds b, tags t "
sql += "where b.id = t.build_id"
else:
sql = "select distinct(b.id) from builds b where b.id=b.id"
if(category is not None):
sql += " and b.category=" + str(category) + " "
for (term) in terms:
tlike = "'%" + term.replace("'", "''") + "%'"
if(tflag):
sql += " and (b.name like " + tlike + " or b.descrip like " + tlike + " or t.tag like " + tlike + ")"
else:
sql += " and b.name like " + tlike
maxresults = self.getMaxResults(msg)
cursor.execute(sql)
result = cursor.fetchall()
count = len(result)
toomany = (count > maxresults)
if(toomany):
del result[maxresults:]
if(len(result) == 0):
irc.reply("no results found")
else:
lines = []
lines.append(ircutils.bold(str(count)) + " results:")
for (i) in result:
c2 = db.cursor()
c2.execute("select b.name, c.name from builds b, categories c where b.category=c.id and b.id=?", (i[0],))
xres = c2.fetchall();
if(len(result) == 1):
# if there's only one result, jump straight to its !sboinfo
(name, cat) = xres[0]
self.SBoInfo(irc, msg, cat + "/" + name)
return;
for (name, cat) in xres:
lines.append(ircutils.bold(cat + "/" + name))
if(toomany):
lines.append("...")
irc.replies(lines, joiner=' ')
sbosearch = thread(wrap(sbosearch, [optional(('literal', ['-t'])), many('somethingWithoutSpaces')]))
def SBoInfo(self, irc, msg, catbuild):
category = None
build = None
t = catbuild.split('/',1)
if(len(t) == 1):
build = catbuild
else:
category = self.getCategory(t[0])
if(category is None):
irc.error("invalid category '" + t[0] + "'", Raise=True)
build = t[1]
db = self.InitDB();
cursor = db.cursor()
sql = """select b.id, b.name, c.name, b.descrip, b.version, m.name, e.addr
from builds b, categories c, maintainers m, emails e where """
if(category is not None):
sql += "c.id=" + str(category) + " and "
sql += """b.category = c.id and b.maintainer = m.id and b.email = e.id
and b.name=?"""
cursor.execute(sql, (build,))
result = cursor.fetchall()
if(len(result) == 0):
irc.reply("no results found")
else:
lines = []
for (bid, bname, cname, bdescrip, bversion, maint, email) in result:
lines.append(ircutils.bold(cname + "/" + bname) + " v" + bversion + ": " + bdescrip +
", " + maint + " <" + email + ">.")
maxdeps = self.getMaxDeps(msg)
cursor.execute("select b.name from builds b, deps d where d.build_id=? and b.id=d.depends_on", (bid,))
depres = cursor.fetchall()
if(len(depres) == 0):
lines.append("No external deps.")
else:
depcount = len(depres)
toomany = (depcount > maxdeps)
if(toomany):
del depres[maxdeps:]
deps = ""
for (depname) in depres:
deps += " " + depname[0]
if(toomany):
deps += " ..."
if(depcount == 1):
plural = ""
else:
plural = "s"
lines.append(str(depcount) + " dep" + plural + ":" + deps)
irc.replies(lines, joiner=' ')
def sboinfo(self, irc, msg, args, catbuild):
""" []
Show detailed information about a SBo build. The argument must be
an exact match (this is not a search).
"""
self.SBoInfo(irc, msg, catbuild)
sboinfo = thread(wrap(sboinfo, ['somethingWithoutSpaces']))
def sbomaint(self, irc, msg, args, terms):
"""
Show list of SBo builds whose maintainers match the search term.
"""
term = str.join(' ', terms)
like = '%' + term + '%'
maxdeps = self.getMaxDeps(msg)
db = self.InitDB();
cursor = db.cursor()
cursor.execute("select c.name, b.name, m.name, e.addr from categories c, builds b, maintainers m, emails e where c.id=b.category and b.maintainer=m.id and b.email=e.id and (m.name like ? or e.addr like ?) order by m.name, e.addr, b.id", (like, like,))
result = cursor.fetchall()
if(len(result) == 0):
irc.reply("No matching maintainer names or email addresses")
return
lines = []
toomany = (len(result) > maxdeps)
if(toomany):
lines.append(str(len(result)) + " results, showing " + str(maxdeps) + ".")
del result[maxdeps:]
oldnamemail = ""
for (cat, build, maint, email) in result:
namemail = maint + " <" + email + ">"
if(namemail != oldnamemail):
if(oldnamemail != ""):
lines.append("|")
lines.append(namemail + ":")
oldnamemail = namemail
lines.append(ircutils.bold(cat + "/" + build))
if(toomany):
lines.append("...")
irc.replies(lines, joiner=" ")
sbomaint = thread(wrap(sbomaint, [many('somethingWithoutSpaces')]))
def getDependees(self, rflag, buildid, db):
retlist = []
cursor = db.cursor()
cursor.execute("select c.name, b.name, build_id from builds b, categories c, deps d where b.category=c.id and b.id=d.build_id and d.depends_on=?", (buildid,))
result = cursor.fetchall()
for (cat, name, bid) in result:
retlist.append(ircutils.bold(cat + "/" + name))
if(rflag):
sublist = self.getDependees(rflag, bid, db)
if(len(sublist) > 0):
retlist.append("[")
retlist.extend(sublist)
retlist.append("]")
return retlist
def sborevdep(self, irc, msg, args, rflag, catbuild):
""" [-r]
Show builds that depend on . With -r, show builds that
depend on each result, recursively.
"""
category = None
build = None
t = catbuild.split('/',1)
if(len(t) == 1):
build = catbuild
else:
category = self.getCategory(t[0])
if(category is None):
irc.error("invalid category '" + t[0] + "'", Raise=True)
build = t[1]
db = self.InitDB();
cursor = db.cursor()
sql = "select b.id from builds b"
if(category is None):
sql += " where "
else:
sql += ", categories c where c.id=" + str(category) + " and b.category=c.id and "
sql += "b.name=?"
cursor.execute(sql, (build,))
result = cursor.fetchall()
if(len(result) == 0):
irc.reply("no such build: " + catbuild)
return
lines = self.getDependees(rflag, result[0][0], db)
if(len(lines) == 0):
irc.reply("nothing depends on " + catbuild)
else:
irc.replies(lines, joiner=' ')
sborevdep = thread(wrap(sborevdep, [optional(('literal', ['-r'])), 'somethingWithoutSpaces']))
Class = SBoTools