Encyclopedia: General overhaul.
* Improve input handling. * Enable multi-message output. * Improve request logging. * Add real delete function. * Improve factoid web page. * Extend factoid info output. * Count calls of aliases towards their own popularity. * Show info on deleted factoids too. * Improve factoid search algorithm. * Sort factoid search by popularity. * Improve command name structure. * Various other and minor improvements. * Update default configuration.
This commit is contained in:
@ -1,12 +1,12 @@
|
|||||||
Factoid plugin
|
Factoid plugin
|
||||||
Note: This plugin used to have package lookup, this was mooved to the
|
Note: This plugin used to have package lookup, this was moved to the
|
||||||
PackageInfo plugin.
|
PackageInfo plugin.
|
||||||
|
|
||||||
Pick a name for your database. A lowercase-only name without spaces is probably
|
Pick a name for your database. A lowercase-only name without spaces is probably
|
||||||
best, this example wil use myfactoids as name. Then create a directory to store
|
best, this example will use myfactoids as name. Then create a directory to store
|
||||||
your databases in (somewere in $botdir/data would be best).
|
your databases in (somewhere in $botdir/data would be best).
|
||||||
If you choose to enable this plugin during supybot-wizard the database will be
|
If you choose to enable this plugin during supybot-wizard, the database will be
|
||||||
created for you. If noy, you can create the database manually.
|
created for you. If not, you can create the database manually.
|
||||||
In the new directory create an SQLite 3 database with the following command:
|
In the new directory create an SQLite 3 database with the following command:
|
||||||
|
|
||||||
sqlite3 myfactoids.db
|
sqlite3 myfactoids.db
|
||||||
@ -15,30 +15,31 @@ Then copy/paste in the below 2 tables:
|
|||||||
|
|
||||||
CREATE TABLE facts (
|
CREATE TABLE facts (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
author TEXT NOT NULL,
|
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
added TEXT NOT NULL,
|
|
||||||
value TEXT NOT NULL,
|
value TEXT NOT NULL,
|
||||||
|
author TEXT NOT NULL,
|
||||||
|
added TEXT NOT NULL,
|
||||||
popularity INTEGER NOT NULL DEFAULT 0
|
popularity INTEGER NOT NULL DEFAULT 0
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE log (
|
CREATE TABLE log (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
author TEXT NOT NULL,
|
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
added TEXT NOT NULL,
|
oldvalue TEXT NOT NULL,
|
||||||
oldvalue TEXT NOT NULL
|
author TEXT NOT NULL,
|
||||||
|
added TEXT NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
If you want to create more databases, repeat these last two steps.
|
If you want to create more databases, repeat these last two steps.
|
||||||
|
|
||||||
When the databases exist, you need to configure the bots to actually use them.
|
When the databases exist, you need to configure the bots to actually use them.
|
||||||
To do that, set the global value supybot.plugins.encyclopedia.datadir to the new
|
To do that, set the global value supybot.plugins.encyclopedia.datadir to the new
|
||||||
dirand the channel value supybot.plugins.encyclopedia.database to the name of
|
directory and the channel value supybot.plugins.encyclopedia.database to the name
|
||||||
the database (without the .db suffix).
|
of the database (without the .db suffix).
|
||||||
|
|
||||||
Documentation on adding/editing factoids can be found on
|
Documentation on adding/editing factoids can be found on
|
||||||
https://ubottu.com/devel/wiki/Plugins#Encyclopedia
|
https://ubottu.com/devel/wiki/Plugins#Encyclopedia
|
||||||
|
|
||||||
To give people edit access, let them register with your bot and use the command:
|
To give people edit access, let them register with your bot and use the command:
|
||||||
@addeditor nickname_here
|
@addeditor nickname_here
|
||||||
(replace @ with your prefix char). Similarly you can use removeeditor :).
|
(replace @ with your prefix char). Similarly you can use removeeditor :).
|
||||||
|
@ -24,7 +24,7 @@ import supybot
|
|||||||
import supybot.world as world
|
import supybot.world as world
|
||||||
from imp import reload
|
from imp import reload
|
||||||
|
|
||||||
__version__ = "2.5"
|
__version__ = "2.6"
|
||||||
__author__ = supybot.Author("Krytarik Raido", "krytarik", "krytarik@tuxgarage.com")
|
__author__ = supybot.Author("Krytarik Raido", "krytarik", "krytarik@tuxgarage.com")
|
||||||
__contributors__ = {
|
__contributors__ = {
|
||||||
supybot.Author("Dennis Kaarsemaker", "Seveas", "dennis@kaarsemaker.net"): ['Original Author'],
|
supybot.Author("Dennis Kaarsemaker", "Seveas", "dennis@kaarsemaker.net"): ['Original Author'],
|
||||||
|
@ -20,10 +20,7 @@ import supybot.registry as registry
|
|||||||
|
|
||||||
def configure(advanced):
|
def configure(advanced):
|
||||||
from supybot.questions import yn, something, output
|
from supybot.questions import yn, something, output
|
||||||
from supybot.utils.str import format
|
import os, re, sqlite3
|
||||||
import os
|
|
||||||
import sqlite3
|
|
||||||
import re
|
|
||||||
|
|
||||||
def anything(prompt, default=None):
|
def anything(prompt, default=None):
|
||||||
"""Because supybot is pure fail"""
|
"""Because supybot is pure fail"""
|
||||||
@ -40,8 +37,8 @@ def configure(advanced):
|
|||||||
ignores = set([])
|
ignores = set([])
|
||||||
output("This plugin can be configured to always ignore certain factoid requests, this is useful when you want another plugin to handle them")
|
output("This plugin can be configured to always ignore certain factoid requests, this is useful when you want another plugin to handle them")
|
||||||
output("For instance, the PackageInfo plugin responds to !info and !find, so those should be ignored in Encyclopedia to allow this to work")
|
output("For instance, the PackageInfo plugin responds to !info and !find, so those should be ignored in Encyclopedia to allow this to work")
|
||||||
ignores_i = anything("Which factoid requets should the bot always ignore?", default=', '.join(Encyclopedia.ignores._default))
|
ignores_i = anything("Which factoid requests should the bot always ignore?", default=', '.join(Encyclopedia.ignores._default))
|
||||||
for name in re.split(r',?\s', ignores_i):
|
for name in re.split(r'[,\s]+', ignores_i):
|
||||||
ignores.add(name.lower())
|
ignores.add(name.lower())
|
||||||
|
|
||||||
curStable = something("What is short name of the current stable release?", default=Encyclopedia.curStable._default)
|
curStable = something("What is short name of the current stable release?", default=Encyclopedia.curStable._default)
|
||||||
@ -70,16 +67,16 @@ def configure(advanced):
|
|||||||
curLTSLong = Encyclopedia.curLTSLong._default
|
curLTSLong = Encyclopedia.curLTSLong._default
|
||||||
curLTSNum = Encyclopedia.curLTSNum._default
|
curLTSNum = Encyclopedia.curLTSNum._default
|
||||||
|
|
||||||
relaychannel = anything("What channel/nick should the bot forward alter messages to?", default=Encyclopedia.relaychannel._default)
|
relaychannel = anything("What channel/nick should the bot forward edit messages to?", default=Encyclopedia.relaychannel._default)
|
||||||
output("What message should the bot reply with when a factoid can not be found?")
|
output("What message should the bot reply with when a factoid can not be found?")
|
||||||
notfoundmsg = something("If you include a '%s' in the message, it will be replaced with the requested factoid", default=Encyclopedia.notfoundmsg._default)
|
notfoundmsg = something("If you include a '%s' in the message, it will be replaced with the requested factoid", default=Encyclopedia.notfoundmsg._default)
|
||||||
|
alert = set([])
|
||||||
output("When certain factoids are called an alert can be forwarded to a channel/nick")
|
output("When certain factoids are called an alert can be forwarded to a channel/nick")
|
||||||
output("Which factoids should the bot forward alert calls for?")
|
output("Which factoids should the bot forward alert calls for?")
|
||||||
alert = set([])
|
|
||||||
alert_i = anything("Separate types by spaces or commas:", default=', '.join(Encyclopedia.alert._default))
|
alert_i = anything("Separate types by spaces or commas:", default=', '.join(Encyclopedia.alert._default))
|
||||||
for name in re.split(r',?\s+', alert_i):
|
for name in re.split(r'[,\s]+', alert_i):
|
||||||
alert.add(name.lower())
|
alert.add(name.lower())
|
||||||
remotedb = anything("Location of a remote database to sync with (used with @sync)", default=Encyclopedia.remotedb._default)
|
remotedb = anything("Location of a remote database to sync with (used with @sync):", default=Encyclopedia.remotedb._default)
|
||||||
privateNotFound = yn("Should the bot reply in private when a factoid is not found, as opposed to in the channel?", default=Encyclopedia.privateNotFound._default)
|
privateNotFound = yn("Should the bot reply in private when a factoid is not found, as opposed to in the channel?", default=Encyclopedia.privateNotFound._default)
|
||||||
|
|
||||||
Encyclopedia.enabled.setValue(enabled)
|
Encyclopedia.enabled.setValue(enabled)
|
||||||
@ -124,19 +121,19 @@ def configure(advanced):
|
|||||||
try:
|
try:
|
||||||
cur.execute("""CREATE TABLE facts (
|
cur.execute("""CREATE TABLE facts (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
author TEXT NOT NULL,
|
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
added TEXT NOT NULL,
|
|
||||||
value TEXT NOT NULL,
|
value TEXT NOT NULL,
|
||||||
|
author TEXT NOT NULL,
|
||||||
|
added TEXT NOT NULL,
|
||||||
popularity INTEGER NOT NULL DEFAULT 0
|
popularity INTEGER NOT NULL DEFAULT 0
|
||||||
)""")
|
)""")
|
||||||
|
|
||||||
cur.execute("""CREATE TABLE log (
|
cur.execute("""CREATE TABLE log (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
author TEXT NOT NULL,
|
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
added TEXT NOT NULL,
|
oldvalue TEXT NOT NULL,
|
||||||
oldvalue TEXT NOT NULL
|
author TEXT NOT NULL,
|
||||||
|
added TEXT NOT NULL
|
||||||
)""")
|
)""")
|
||||||
|
|
||||||
except:
|
except:
|
||||||
@ -150,7 +147,7 @@ def configure(advanced):
|
|||||||
Encyclopedia = conf.registerPlugin('Encyclopedia')
|
Encyclopedia = conf.registerPlugin('Encyclopedia')
|
||||||
|
|
||||||
conf.registerChannelValue(Encyclopedia, 'enabled',
|
conf.registerChannelValue(Encyclopedia, 'enabled',
|
||||||
registry.Boolean(True, "Enable Encyclopedia"))
|
registry.Boolean(True, 'Enable Encyclopedia'))
|
||||||
|
|
||||||
conf.registerChannelValue(Encyclopedia, 'database',
|
conf.registerChannelValue(Encyclopedia, 'database',
|
||||||
registry.String('ubuntu', 'Name of database to use'))
|
registry.String('ubuntu', 'Name of database to use'))
|
||||||
@ -159,11 +156,11 @@ conf.registerChannelValue(Encyclopedia, 'relaychannel',
|
|||||||
registry.String('#ubuntu-ops', 'Relay channel for unauthorized edits'))
|
registry.String('#ubuntu-ops', 'Relay channel for unauthorized edits'))
|
||||||
|
|
||||||
conf.registerGlobalValue(Encyclopedia, 'editchannel',
|
conf.registerGlobalValue(Encyclopedia, 'editchannel',
|
||||||
registry.SpaceSeparatedListOfStrings(['#ubuntu-ops'],
|
registry.SpaceSeparatedListOfStrings(['#ubuntu-ops'],
|
||||||
'Channels where edits of restricted editors are allowed.'))
|
'Channels where edits of restricted editors are allowed'))
|
||||||
|
|
||||||
conf.registerGlobalValue(Encyclopedia, 'notfoundmsg',
|
conf.registerGlobalValue(Encyclopedia, 'notfoundmsg',
|
||||||
registry.String('Factoid %s not found', 'Reply when factoid isn\'t found'))
|
registry.String('Factoid %s not found', 'Reply when factoid is not found'))
|
||||||
|
|
||||||
conf.registerChannelValue(Encyclopedia,'prefixchar',
|
conf.registerChannelValue(Encyclopedia,'prefixchar',
|
||||||
registry.String('!','Prefix character for factoid display/editing'))
|
registry.String('!','Prefix character for factoid display/editing'))
|
||||||
@ -172,38 +169,38 @@ conf.registerGlobalValue(Encyclopedia, 'datadir',
|
|||||||
conf.Directory(conf.supybot.directories.data(), 'Path to dir containing factoid databases', private=True))
|
conf.Directory(conf.supybot.directories.data(), 'Path to dir containing factoid databases', private=True))
|
||||||
|
|
||||||
conf.registerChannelValue(Encyclopedia, 'alert',
|
conf.registerChannelValue(Encyclopedia, 'alert',
|
||||||
registry.SpaceSeparatedListOfStrings(['ops', 'op', 'kops', 'calltheops'], 'factoid name(s) used for alerts', private=True))
|
registry.SpaceSeparatedListOfStrings(['ops', 'op', 'kops', 'calltheops'], 'Factoid names used for alerts', private=True))
|
||||||
|
|
||||||
conf.registerChannelValue(Encyclopedia, 'remotedb',
|
conf.registerChannelValue(Encyclopedia, 'remotedb',
|
||||||
registry.String('http://ubottu.com/ubuntu.db', 'Remote location of the master database', private=True))
|
registry.String('https://ubottu.com/ubuntu.db', 'Remote location of the master database', private=True))
|
||||||
|
|
||||||
conf.registerChannelValue(Encyclopedia, 'ignores',
|
conf.registerChannelValue(Encyclopedia, 'ignores',
|
||||||
registry.SpaceSeparatedListOfStrings(['find', 'info'], 'factoid name(s) to ignore', private=True))
|
registry.SpaceSeparatedListOfStrings(['info', 'depends', 'find'], 'Factoid names to ignore', private=True))
|
||||||
|
|
||||||
conf.registerChannelValue(Encyclopedia, 'privateNotFound',
|
conf.registerChannelValue(Encyclopedia, 'privateNotFound',
|
||||||
registry.Boolean(False, "If set to True, send notfoundmsg in private rather than in the channel"))
|
registry.Boolean(False, "If set to True, send notfoundmsg in private rather than in the channel"))
|
||||||
|
|
||||||
conf.registerChannelValue(Encyclopedia, 'forcedFactoid',
|
conf.registerChannelValue(Encyclopedia, 'forcedFactoid',
|
||||||
registry.Boolean(False, "If True, factoids in kick's reason will be sent to the user in private"))
|
registry.Boolean(False, "If set to True, factoids in kick reason will be sent to the user in private"))
|
||||||
|
|
||||||
|
|
||||||
conf.registerGlobalValue(Encyclopedia, 'curStable',
|
conf.registerGlobalValue(Encyclopedia, 'curStable',
|
||||||
registry.String('Natty', "Current stable release"))
|
registry.String('Artful', "Current stable release"))
|
||||||
conf.registerGlobalValue(Encyclopedia, 'curStableLong',
|
conf.registerGlobalValue(Encyclopedia, 'curStableLong',
|
||||||
registry.String('Natty Narwhal', "Current stable release"))
|
registry.String('Artful Aardvark', "Current stable release"))
|
||||||
conf.registerGlobalValue(Encyclopedia, 'curStableNum',
|
conf.registerGlobalValue(Encyclopedia, 'curStableNum',
|
||||||
registry.String('11.04', "Current stable release"))
|
registry.String('17.10', "Current stable release"))
|
||||||
|
|
||||||
conf.registerGlobalValue(Encyclopedia, 'curDevel',
|
conf.registerGlobalValue(Encyclopedia, 'curDevel',
|
||||||
registry.String('Oneiric', "Current development release"))
|
registry.String('Bionic', "Current development release"))
|
||||||
conf.registerGlobalValue(Encyclopedia, 'curDevelLong',
|
conf.registerGlobalValue(Encyclopedia, 'curDevelLong',
|
||||||
registry.String('Oneiric Ocelot', "Current development release"))
|
registry.String('Bionic Beaver', "Current development release"))
|
||||||
conf.registerGlobalValue(Encyclopedia, 'curDevelNum',
|
conf.registerGlobalValue(Encyclopedia, 'curDevelNum',
|
||||||
registry.String('11.10', "Current development release"))
|
registry.String('18.04', "Current development release"))
|
||||||
|
|
||||||
conf.registerGlobalValue(Encyclopedia, 'curLTS',
|
conf.registerGlobalValue(Encyclopedia, 'curLTS',
|
||||||
registry.String('Lucid', "Current LTS release"))
|
registry.String('Xenial', "Current LTS release"))
|
||||||
conf.registerGlobalValue(Encyclopedia, 'curLTSLong',
|
conf.registerGlobalValue(Encyclopedia, 'curLTSLong',
|
||||||
registry.String('Lucid Lynx', "Current LTS release"))
|
registry.String('Xenial Xerus', "Current LTS release"))
|
||||||
conf.registerGlobalValue(Encyclopedia, 'curLTSNum',
|
conf.registerGlobalValue(Encyclopedia, 'curLTSNum',
|
||||||
registry.String('10.04', "Current LTS release"))
|
registry.String('16.04', "Current LTS release"))
|
||||||
|
@ -15,183 +15,130 @@
|
|||||||
#
|
#
|
||||||
###
|
###
|
||||||
|
|
||||||
import sys
|
import sys, sqlite3
|
||||||
# This needs to be set to the location of the commoncgi.py file
|
# This needs to be set to the location of the commoncgi.py file
|
||||||
sys.path.append('/var/www/bot')
|
sys.path.append('/var/www/bot')
|
||||||
from commoncgi import *
|
from commoncgi import *
|
||||||
import sqlite3
|
|
||||||
|
|
||||||
### Variables
|
### Variables
|
||||||
NUM_PER_PAGE=50.0
|
NUM_PER_PAGE = 50
|
||||||
# Directory containing the factoid database
|
# Directory containing the factoid database
|
||||||
datadir = '/home/bot/'
|
datadir = '/home/bot/'
|
||||||
# Database filename (without the .db extention)
|
# Database filename (without the .db extention)
|
||||||
default_database = 'ubuntu'
|
database = 'ubuntu'
|
||||||
|
|
||||||
### Nothing below this line should be edited unless you know what you're doing ###
|
### Nothing below this line should be edited unless you know what you're doing ###
|
||||||
|
|
||||||
databases = [x for x in os.listdir(datadir)]
|
databases = [x for x in os.listdir(datadir)]
|
||||||
|
|
||||||
# Initialize
|
# Initialize
|
||||||
database = default_database
|
order_url = 'popularity|DESC'
|
||||||
order_by = 'popularity DESC'
|
order_by = order_url.replace('|',' ')
|
||||||
page = 0
|
page = 1
|
||||||
search = ''
|
search = ''
|
||||||
factoids = []
|
factoids = []
|
||||||
total = 0
|
total = 0
|
||||||
|
|
||||||
class Factoid:
|
class Factoid:
|
||||||
def __init__(self, name, value, author, added, popularity):
|
def __init__(self, name, value, author, added, popularity):
|
||||||
self.name, self.value, self._author, self._added, self.popularity = (name, value, author, added, popularity)
|
self.name, self.value, self.author, self.added, self.popularity = name, value, author, added, popularity
|
||||||
|
|
||||||
@property
|
|
||||||
def author(self):
|
|
||||||
if '!' in self._author:
|
|
||||||
return self._author[:self._author.find('!')]
|
|
||||||
return self._author
|
|
||||||
|
|
||||||
@property
|
|
||||||
def added(self):
|
|
||||||
if '.' in self._added:
|
|
||||||
return self._added[:self._added.find('.')]
|
|
||||||
return self._added
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
yield self.name
|
|
||||||
yield self.value
|
|
||||||
yield self.author
|
|
||||||
yield self.added
|
|
||||||
yield self.popularity
|
|
||||||
|
|
||||||
class Log:
|
class Log:
|
||||||
def __init__(self, author, added):
|
def __init__(self, author, added):
|
||||||
self._author, self._added = (author, added)
|
self.author, self.added = author, added
|
||||||
|
|
||||||
@property
|
|
||||||
def author(self):
|
|
||||||
if '!' in self._author:
|
|
||||||
return self._author[:self._author.find('!')]
|
|
||||||
return self._author
|
|
||||||
|
|
||||||
@property
|
|
||||||
def added(self):
|
|
||||||
if '.' in self._added:
|
|
||||||
return self._added[:self._added.find('.')]
|
|
||||||
return self._added
|
|
||||||
|
|
||||||
# Read POST
|
# Read POST
|
||||||
if 'db' in form:
|
if 'db' in form and form['db'].value in databases:
|
||||||
database = form['db'].value
|
database = form['db'].value
|
||||||
if database not in databases:
|
|
||||||
database = default_database
|
|
||||||
con = sqlite3.connect(os.path.join(datadir, '%s.db' % database))
|
con = sqlite3.connect(os.path.join(datadir, '%s.db' % database))
|
||||||
cur = con.cursor()
|
cur = con.cursor()
|
||||||
|
|
||||||
try: page = int(form['page'].value)
|
try:
|
||||||
except: pass
|
page = int(form['page'].value)
|
||||||
|
except:
|
||||||
if 'order' in form:
|
pass
|
||||||
if form['order'].value in ('added DESC', 'added ASC', 'name DESC', 'name ASC', 'popularity DESC','popularity ASC'):
|
|
||||||
order_by = form['order'].value
|
if 'order' in form and form['order'].value in ('added|DESC', 'added|ASC', 'name|DESC', 'name|ASC', 'popularity|DESC', 'popularity|ASC'):
|
||||||
|
order_url = form['order'].value
|
||||||
|
order_by = order_url.replace('|',' ')
|
||||||
if 'search' in form:
|
if 'search' in form:
|
||||||
search = form['search'].value
|
search = form['search'].value
|
||||||
|
|
||||||
# Select factoids
|
# Select factoids
|
||||||
if search:
|
if search:
|
||||||
keys = [utils.web.urlunquote(x.strip()) for x in search.split() if len(x.strip()) >=2][:5]
|
keys = utils.web.urlunquote(search).split()[:5]
|
||||||
values = []
|
qterms, values = '', []
|
||||||
if not keys:
|
|
||||||
keys = ['']
|
|
||||||
query1 = "SELECT name, value, author, added, popularity FROM facts WHERE name NOT LIKE '%%-also' AND ("
|
|
||||||
query2 = "SELECT COUNT(*) FROM facts WHERE "
|
|
||||||
bogus = False
|
|
||||||
for k in keys:
|
for k in keys:
|
||||||
values.extend(('%%%s%%' % k, '%%%s%%' % k))
|
if qterms:
|
||||||
if bogus:
|
qterms += ' AND '
|
||||||
query1 += ' OR '
|
qterms += '(name LIKE ? OR value LIKE ? OR value LIKE ?)'
|
||||||
query2 += ' OR '
|
values.extend(['%%%s%%' % k.lower(), '%%%s%%' % k, '%%%s%%' % k.lower()])
|
||||||
query1 += 'name LIKE ? OR value LIKE ?'
|
cur.execute("SELECT name, value, author, added, popularity FROM facts WHERE name NOT LIKE '%%-also' AND %s ORDER BY %s LIMIT %d, %d" %
|
||||||
query2 += 'name LIKE ? OR value LIKE ?'
|
(qterms, order_by, NUM_PER_PAGE * (page - 1), NUM_PER_PAGE), values)
|
||||||
bogus=True
|
|
||||||
|
|
||||||
cur.execute(query1 + ') ORDER BY %s LIMIT %d, %d' % (order_by, NUM_PER_PAGE * page, NUM_PER_PAGE), values)
|
|
||||||
factoids = [Factoid(*x) for x in cur.fetchall()]
|
factoids = [Factoid(*x) for x in cur.fetchall()]
|
||||||
cur.execute(query2, values)
|
cur.execute("SELECT COUNT(*) FROM facts WHERE name NOT LIKE '%%-also' AND %s" % qterms, values)
|
||||||
total = cur.fetchall()[0][0]
|
total = cur.fetchall()[0][0]
|
||||||
else:
|
else:
|
||||||
cur.execute("SELECT name, value, author, added, popularity FROM facts WHERE value NOT LIKE '<alias>%%' AND name NOT LIKE '%%-also' ORDER BY %s LIMIT %d, %d" %
|
cur.execute("SELECT name, value, author, added, popularity FROM facts WHERE value NOT LIKE '<alias>%%' AND name NOT LIKE '%%-also' ORDER BY %s LIMIT %d, %d" %
|
||||||
(order_by, NUM_PER_PAGE * page, NUM_PER_PAGE))
|
(order_by, NUM_PER_PAGE * (page - 1), NUM_PER_PAGE))
|
||||||
factoids = [Factoid(*x) for x in cur.fetchall()]
|
factoids = [Factoid(*x) for x in cur.fetchall()]
|
||||||
cur.execute("SELECT COUNT(*) FROM facts WHERE value NOT LIKE '<alias>%%'")
|
cur.execute("SELECT COUNT(*) FROM facts WHERE value NOT LIKE '<alias>%%' AND name NOT LIKE '%%-also'")
|
||||||
total = cur.fetchall()[0][0]
|
total = cur.fetchall()[0][0]
|
||||||
|
|
||||||
# Pagination links
|
# Pagination links
|
||||||
npages = int(math.ceil(total / float(NUM_PER_PAGE)))
|
plink = ' <a href="factoids.cgi?db=%s&search=%s&order=%%s&page=%%s">%%s</a>' % (database, search)
|
||||||
print('·')
|
npages = int(math.ceil(float(total) / NUM_PER_PAGE))
|
||||||
for i in range(npages):
|
print(' ·\n'.join(list(map(lambda x: plink % (order_url, x, x) if x != page else str(x), range(1, npages+1)))))
|
||||||
print('<a href="factoids.cgi?db=%s&search=%s&order=%s&page=%s">%d</a> ·' % (database, search, order_by, i, i+1))
|
|
||||||
|
|
||||||
print('<br />Order by<br />·');
|
|
||||||
print(' <a href="factoids.cgi?db=%s&search=%s&order=%s&page=0">%s</a> ·' % (database, search, 'name ASC', 'Name +'))
|
|
||||||
print(' <a href="factoids.cgi?db=%s&search=%s&order=%s&page=0">%s</a> ·' % (database, search, 'name DESC', 'Name -'))
|
|
||||||
print(' <a href="factoids.cgi?db=%s&search=%s&order=%s&page=0">%s</a> ·' % (database, search, 'popularity ASC', 'Popularity +'))
|
|
||||||
print(' <a href="factoids.cgi?db=%s&search=%s&order=%s&page=0">%s</a> ·' % (database, search, 'popularity DESC', 'Popularity -'))
|
|
||||||
print(' <a href="factoids.cgi?db=%s&search=%s&order=%s&page=0">%s</a> ·' % (database, search, 'added ASC', 'Date added +'))
|
|
||||||
print(' <a href="factoids.cgi?db=%s&search=%s&order=%s&page=0">%s</a> ·' % (database, search, 'added DESC', 'Date added -'))
|
|
||||||
|
|
||||||
print('''
|
print(' <br />Order by<br />');
|
||||||
<table cellspacing="0">
|
print(' ·\n'.join([plink % ('name|ASC', 1, 'Name +'), plink % ('name|DESC', 1, 'Name -'),
|
||||||
|
plink % ('popularity|ASC', 1, 'Popularity +'), plink % ('popularity|DESC', 1, 'Popularity -'),
|
||||||
|
plink % ('added|ASC', 1, 'Date added +'), plink % ('added|DESC', 1, 'Date added -')]))
|
||||||
|
|
||||||
|
print('''\
|
||||||
|
<table style="border-collapse: collapse;">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th style="width: 10%;">Factoid</th>
|
<th style="width: 15%;">Factoid</th>
|
||||||
<th style="width: 70%;">Value</th>
|
<th style="width: 68%;">Value</th>
|
||||||
<th style="width: 20%;">Author</th>
|
<th style="width: 17%;">Author</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>''')
|
<tbody>''')
|
||||||
|
|
||||||
url_re = re.compile('(?P<url>(https?://\S+|www\S+))')
|
url_re = re.compile('(?P<url>(https?://|www\.)\S+)')
|
||||||
def q(x):
|
def q(x):
|
||||||
x = str(x).replace('&','&').replace('<','<').replace('>','>').replace('\n','<br />')
|
x = x.replace('&','&').replace('<','<').replace('>','>').replace('"','"')
|
||||||
return url_re.sub(link, x)
|
return url_re.sub(link, x)
|
||||||
def link(match):
|
def link(match):
|
||||||
url = match.group('url')
|
url = txt = match.group('url')
|
||||||
txt = url
|
# if len(txt) > 30:
|
||||||
if len(txt) > 30:
|
# txt = '%s…%s' % (txt[:20], txt[-10:])
|
||||||
txt = txt[:20] + '…' + txt[-10:]
|
|
||||||
return '<a href="%s">%s</a>' % (url, txt)
|
return '<a href="%s">%s</a>' % (url, txt)
|
||||||
|
|
||||||
i = 0
|
i = 0
|
||||||
for fact in factoids:
|
for fact in factoids:
|
||||||
name = fact.name
|
cur.execute("SELECT name FROM facts WHERE value LIKE ?", ('<alias>_%s' % fact.name,))
|
||||||
|
name = ' '.join([fact.name] + [x[0] for x in cur.fetchall()])
|
||||||
cur.execute("SELECT value FROM facts WHERE name = ?", ('%s-also' % fact.name,))
|
cur.execute("SELECT value FROM facts WHERE name = ?", ('%s-also' % fact.name,))
|
||||||
more = cur.fetchall()
|
value = ' '.join([fact.value] + [x[0] for x in cur.fetchall()])
|
||||||
if len(more):
|
data = ["Added by %s" % fact.author[:fact.author.find('!')], "Date: %s" % fact.added[:fact.added.rfind('.')]]
|
||||||
name += ' $hr$' + ' $hr$'.join([x[0] for x in more])
|
|
||||||
cur.execute("SELECT name FROM facts WHERE value = ?", ('<alias> %s' % fact.name,))
|
|
||||||
name += ' \n' + ' \n'.join([x[0] for x in cur.fetchall()])
|
|
||||||
data = [ q(x) for x in fact ]
|
|
||||||
cur.execute("SELECT author, added FROM log WHERE name = ? ORDER BY id DESC LIMIT 1", (fact.name,))
|
cur.execute("SELECT author, added FROM log WHERE name = ? ORDER BY id DESC LIMIT 1", (fact.name,))
|
||||||
edit = [Log(*x) for x in cur.fetchall()]
|
edit = [Log(*x) for x in cur.fetchall()]
|
||||||
# edit = [Log(author, added) for (author, added) in cur.fetchall()]
|
|
||||||
if edit:
|
if edit:
|
||||||
log = edit[0]
|
log = edit[0]
|
||||||
data[3] += "<br />Last edited by %s<br />Last modified: %s" % (q(log.author), q(log.added))
|
data.extend(["Last edited by %s" % log.author[:log.author.find('!')], "Date: %s" % log.added[:log.added.rfind('.')]])
|
||||||
else:
|
data.append("Requested %s times" % fact.popularity)
|
||||||
data[3] += "<br />Never edited"
|
|
||||||
data[0] = name
|
|
||||||
sys.stdout.write(' <tr')
|
|
||||||
if i % 2: sys.stdout.write(' class="bg2"')
|
|
||||||
i += 1
|
|
||||||
print('''>
|
|
||||||
<td>%s</td>
|
|
||||||
<td>%s</td>
|
|
||||||
<td>%s<br />
|
|
||||||
Added on: %s<br />
|
|
||||||
Requested %s times</td>
|
|
||||||
</tr>''' % tuple(data))
|
|
||||||
|
|
||||||
print('''
|
print('''\
|
||||||
|
<tr%s>
|
||||||
|
<td>%s</td>
|
||||||
|
<td>%s</td>
|
||||||
|
<td>%s</td>
|
||||||
|
</tr>''' % (' class="bg2"' if i % 2 else '', q(name), q(value), '<br />\n '.join(data)))
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
print('''\
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>''')
|
</table>''')
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
<link rel="stylesheet" href="bot.css" />
|
<link rel="stylesheet" href="bot.css" />
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var DHTML = (document.getElementById || document.all || document.layers);
|
var DHTML = (document.getElementById || document.all || document.layers);
|
||||||
|
|
||||||
function getObj(name) {
|
function getObj(name) {
|
||||||
if (document.getElementById) {
|
if (document.getElementById) {
|
||||||
this.obj = document.getElementById(name);
|
this.obj = document.getElementById(name);
|
||||||
@ -36,32 +36,31 @@
|
|||||||
c.style.display = 'inline';
|
c.style.display = 'inline';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="main">
|
<div class="main">
|
||||||
<h1>Ubotu factoids</h1>
|
<h1>Ubottu factoids</h1>
|
||||||
%e
|
<div class="errors">
|
||||||
|
%e
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
<a href="/">Home</a> ·
|
||||||
|
<a href="https://launchpad.net/ubuntu-bots">Launchpad</a> ·
|
||||||
|
<a href="ubuntu.db">Ubuntu database file</a>
|
||||||
|
</p>
|
||||||
|
<form action="factoids.cgi" method="GET">
|
||||||
|
<input class="input" type="text" name="search" />
|
||||||
|
<input class="input" type="submit" value="Search">
|
||||||
|
</form>
|
||||||
<p>
|
<p>
|
||||||
More help: <a href="http://wiki.ubuntu.com/">wiki.ubuntu.com</a> ·
|
|
||||||
<a href="http://help.ubuntu.com/">help.ubuntu.com</a><br />
|
|
||||||
More factoids: <a href="factoids.cgi?db=ubuntu">Ubuntu</a> ·
|
|
||||||
<a href="factoids.cgi?db=buntudot">buntudot</a> ·
|
|
||||||
<a href="factoids.cgi?db=gnewsense">GNewSense</a><br />
|
|
||||||
<form action="factoids.cgi" method="GET">
|
|
||||||
<input class="input" type="text" name="search" />
|
|
||||||
<input class="input" type="submit" value="Search">
|
|
||||||
</form>
|
|
||||||
<p>
|
|
||||||
%s
|
%s
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<a href="ubuntu.db">Ubuntu factoid database file</a><br />
|
©2006-2007 Dennis Kaarsemaker<br />
|
||||||
©2006 Dennis Kaarsemaker<br/>
|
©2008-2009 Terence Simpson<br />
|
||||||
Edited by Terence Simpson
|
©2018 Krytarik Raido
|
||||||
</p>
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Bot logs</title>
|
|
||||||
<link rel="stylesheet" href="/bot.css" />
|
|
||||||
<link rel="shortcut icon" href="favicon.ico" type="image/png" />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="home">
|
|
||||||
<h1>Logs of unauthorized edits and bot actions</h1>
|
|
||||||
%s
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
File diff suppressed because it is too large
Load Diff
@ -4,17 +4,17 @@
|
|||||||
# Copyright (c) 2010 Elián Hanisch
|
# Copyright (c) 2010 Elián Hanisch
|
||||||
# Copyright (c) 2018 Krytarik Raido
|
# Copyright (c) 2018 Krytarik Raido
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
# (at your option) any later version.
|
# (at your option) any later version.
|
||||||
#
|
#
|
||||||
# This program is distributed in the hope that it will be useful,
|
# This program is distributed in the hope that it will be useful,
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
# GNU General Public License for more details.
|
# GNU General Public License for more details.
|
||||||
#
|
#
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
###
|
###
|
||||||
|
|
||||||
@ -24,7 +24,6 @@ import supybot.conf as conf
|
|||||||
Econf = conf.supybot.plugins.Encyclopedia
|
Econf = conf.supybot.plugins.Encyclopedia
|
||||||
Econf.prefixchar.set('@')
|
Econf.prefixchar.set('@')
|
||||||
|
|
||||||
|
|
||||||
class EncyclopediaTestCase(ChannelPluginTestCase):
|
class EncyclopediaTestCase(ChannelPluginTestCase):
|
||||||
plugins = ('Encyclopedia',)
|
plugins = ('Encyclopedia',)
|
||||||
|
|
||||||
@ -35,7 +34,7 @@ class EncyclopediaTestCase(ChannelPluginTestCase):
|
|||||||
|
|
||||||
def createDB(self):
|
def createDB(self):
|
||||||
import sqlite3, os
|
import sqlite3, os
|
||||||
dbfile = os.path.join(Econf.datadir(), '%s.db' %Econf.database())
|
dbfile = os.path.join(Econf.datadir(), '%s.db' % Econf.database())
|
||||||
try:
|
try:
|
||||||
os.remove(dbfile)
|
os.remove(dbfile)
|
||||||
except:
|
except:
|
||||||
@ -44,18 +43,18 @@ class EncyclopediaTestCase(ChannelPluginTestCase):
|
|||||||
cur = db.cursor()
|
cur = db.cursor()
|
||||||
cur.execute("""CREATE TABLE facts (
|
cur.execute("""CREATE TABLE facts (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
author TEXT NOT NULL,
|
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
added TEXT NOT NULL,
|
|
||||||
value TEXT NOT NULL,
|
value TEXT NOT NULL,
|
||||||
|
author TEXT NOT NULL,
|
||||||
|
added TEXT NOT NULL,
|
||||||
popularity INTEGER NOT NULL DEFAULT 0
|
popularity INTEGER NOT NULL DEFAULT 0
|
||||||
)""")
|
)""")
|
||||||
cur.execute("""CREATE TABLE log (
|
cur.execute("""CREATE TABLE log (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
author TEXT NOT NULL,
|
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
added TEXT NOT NULL,
|
oldvalue TEXT NOT NULL,
|
||||||
oldvalue TEXT NOT NULL
|
author TEXT NOT NULL,
|
||||||
|
added TEXT NOT NULL
|
||||||
)""")
|
)""")
|
||||||
db.commit()
|
db.commit()
|
||||||
db.close()
|
db.close()
|
||||||
@ -84,13 +83,13 @@ class EncyclopediaTestCase(ChannelPluginTestCase):
|
|||||||
world.testing = False
|
world.testing = False
|
||||||
self.prefix = 'user!user@home.com'
|
self.prefix = 'user!user@home.com'
|
||||||
try:
|
try:
|
||||||
self.assertResponse('test-#ubuntu-se is <reply> blah',
|
self.assertResponse('test-#ubuntu-se is <reply> blah',
|
||||||
'Your edit request has been forwarded to #ubuntu-ops. Thank you ' \
|
'Your edit request has been forwarded to #ubuntu-ops. Thank you ' \
|
||||||
'for your attention to detail')
|
'for your attention to detail')
|
||||||
self.assertEqual(self.irc.takeMsg().args[1],
|
self.assertEqual(self.irc.takeMsg().args[1],
|
||||||
'In #test, user said: @test-#ubuntu-se is <reply> blah')
|
'In #test, user said: @test-#ubuntu-se is <reply> blah')
|
||||||
# test in private, it shouldn't use the prefix char.
|
# test in private, it shouldn't use the prefix char.
|
||||||
self.assertResponse('test-#ubuntu-se is <reply> blah',
|
self.assertResponse('test-#ubuntu-se is <reply> blah',
|
||||||
'Your edit request has been forwarded to #ubuntu-ops. Thank you ' \
|
'Your edit request has been forwarded to #ubuntu-ops. Thank you ' \
|
||||||
'for your attention to detail', private=True, usePrefixChar=False)
|
'for your attention to detail', private=True, usePrefixChar=False)
|
||||||
self.assertEqual(self.irc.takeMsg().args[1],
|
self.assertEqual(self.irc.takeMsg().args[1],
|
||||||
@ -99,5 +98,4 @@ class EncyclopediaTestCase(ChannelPluginTestCase):
|
|||||||
world.testing = True
|
world.testing = True
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# vim:set shiftwidth=4 softtabstop=4 tabstop=4 expandtab textwidth=100:
|
# vim:set shiftwidth=4 softtabstop=4 tabstop=4 expandtab textwidth=100:
|
||||||
|
5
bot.css
5
bot.css
@ -5,11 +5,9 @@ body {
|
|||||||
color: #000066;
|
color: #000066;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
tbody {
|
tbody {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.home {
|
div.home {
|
||||||
margin: 20px auto;
|
margin: 20px auto;
|
||||||
width: 300px;
|
width: 300px;
|
||||||
@ -53,7 +51,6 @@ table {
|
|||||||
padding-top: 1em;
|
padding-top: 1em;
|
||||||
clear: both;
|
clear: both;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.main {
|
div.main {
|
||||||
margin: 20px;
|
margin: 20px;
|
||||||
border: 2px solid #000066;
|
border: 2px solid #000066;
|
||||||
|
37
commoncgi.py
37
commoncgi.py
@ -15,7 +15,7 @@
|
|||||||
#
|
#
|
||||||
###
|
###
|
||||||
|
|
||||||
import cgi, cgitb, sys, os, re, math
|
import cgi, cgitb, sys, os, re, math, codecs
|
||||||
import supybot.utils as utils
|
import supybot.utils as utils
|
||||||
|
|
||||||
if sys.version_info < (3,0):
|
if sys.version_info < (3,0):
|
||||||
@ -39,12 +39,12 @@ if 'tz' in cookie:
|
|||||||
class IOWrapper:
|
class IOWrapper:
|
||||||
'''Class to wrap default IO, used with templates'''
|
'''Class to wrap default IO, used with templates'''
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.buf = []
|
self.buf = ''
|
||||||
def write(self, val):
|
def write(self, val):
|
||||||
self.buf.append(val)
|
self.buf += val
|
||||||
def getvalue(self):
|
def getvalue(self):
|
||||||
return self.buf
|
return self.buf
|
||||||
|
|
||||||
sys.stdout = IOWrapper()
|
sys.stdout = IOWrapper()
|
||||||
sys.stderr = IOWrapper()
|
sys.stderr = IOWrapper()
|
||||||
|
|
||||||
@ -52,8 +52,13 @@ def send_page(template):
|
|||||||
'''Sends a template page and exit'''
|
'''Sends a template page and exit'''
|
||||||
data = sys.stdout.getvalue()
|
data = sys.stdout.getvalue()
|
||||||
errdata = sys.stderr.getvalue()
|
errdata = sys.stderr.getvalue()
|
||||||
sys.stdout = sys.__stdout__
|
|
||||||
sys.stderr = sys.__stderr__
|
# Ensure Unicode output
|
||||||
|
if sys.version_info < (3,1):
|
||||||
|
sys.stdout = codecs.getwriter('utf-8')(sys.__stdout__)
|
||||||
|
else:
|
||||||
|
sys.stdout = codecs.getwriter('utf-8')(sys.__stdout__.detach())
|
||||||
|
|
||||||
print("Content-Type: text/html")
|
print("Content-Type: text/html")
|
||||||
print(cookie)
|
print(cookie)
|
||||||
print("")
|
print("")
|
||||||
@ -61,17 +66,19 @@ def send_page(template):
|
|||||||
fd = open(template)
|
fd = open(template)
|
||||||
tmpl = fd.read()
|
tmpl = fd.read()
|
||||||
fd.close()
|
fd.close()
|
||||||
sys.stdout.write(tmpl[:tmpl.find('%e')])
|
estart = tmpl.find('%e')
|
||||||
for e in errdata:
|
sstart = tmpl.find('%s')
|
||||||
sys.stdout.write(e)
|
|
||||||
sys.stdout.write(tmpl[tmpl.find('%e')+2:tmpl.find('%s')])
|
if sys.version_info < (3,0):
|
||||||
# print tmpl[:tmpl.find('%s')]
|
page = u'{}{}{}{}{}'.format(tmpl[:estart], errdata,
|
||||||
for d in data:
|
tmpl[estart+2:sstart], data, tmpl[sstart+2:])
|
||||||
sys.stdout.write(d)
|
else:
|
||||||
sys.stdout.write(tmpl[tmpl.find('%s')+2:])
|
page = '{}{}{}{}{}'.format(tmpl[:estart], errdata,
|
||||||
|
tmpl[estart+2:sstart], data, tmpl[sstart+2:])
|
||||||
|
|
||||||
|
print(page)
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
def q(txt):
|
def q(txt):
|
||||||
'''Simple HTML entity quoting'''
|
'''Simple HTML entity quoting'''
|
||||||
return txt.replace('&','&').replace('<','<').replace('>','>').replace('"','"')
|
return txt.replace('&','&').replace('<','<').replace('>','>').replace('"','"')
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user