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:
Krytarik Raido 2018-05-22 00:45:04 +02:00
parent 669293de1e
commit 3368b2d6fe
11 changed files with 749 additions and 944 deletions

View File

@ -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 :).

View File

@ -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'],

View File

@ -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"))

View File

@ -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&amp;search=%s&amp;order=%%s&amp;page=%%s">%%s</a>' % (database, search)
print('&middot;') npages = int(math.ceil(float(total) / NUM_PER_PAGE))
for i in range(npages): print(' &middot;\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> &middot;' % (database, search, order_by, i, i+1))
print('<br />Order by<br />&middot;');
print(' <a href="factoids.cgi?db=%s&search=%s&order=%s&page=0">%s</a> &middot;' % (database, search, 'name ASC', 'Name +'))
print(' <a href="factoids.cgi?db=%s&search=%s&order=%s&page=0">%s</a> &middot;' % (database, search, 'name DESC', 'Name -'))
print(' <a href="factoids.cgi?db=%s&search=%s&order=%s&page=0">%s</a> &middot;' % (database, search, 'popularity ASC', 'Popularity +'))
print(' <a href="factoids.cgi?db=%s&search=%s&order=%s&page=0">%s</a> &middot;' % (database, search, 'popularity DESC', 'Popularity -'))
print(' <a href="factoids.cgi?db=%s&search=%s&order=%s&page=0">%s</a> &middot;' % (database, search, 'added ASC', 'Date added +'))
print(' <a href="factoids.cgi?db=%s&search=%s&order=%s&page=0">%s</a> &middot;' % (database, search, 'added DESC', 'Date added -'))
print(''' print(' <br />Order by<br />');
<table cellspacing="0"> print(' &middot;\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('&','&amp;').replace('<','&lt;').replace('>','&gt;').replace('\n','<br />') x = x.replace('&','&amp;').replace('<','&lt;').replace('>','&gt;').replace('"','&quot;')
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&hellip;%s' % (txt[:20], txt[-10:])
txt = txt[:20] + '&hellip;' + 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>''')

View File

@ -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> &middot;
<a href="https://launchpad.net/ubuntu-bots">Launchpad</a> &middot;
<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> &middot;
<a href="http://help.ubuntu.com/">help.ubuntu.com</a><br />
More factoids: <a href="factoids.cgi?db=ubuntu">Ubuntu</a> &middot;
<a href="factoids.cgi?db=buntudot">buntudot</a> &middot;
<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 /> &copy;2006-2007 Dennis Kaarsemaker<br />
&copy;2006 Dennis Kaarsemaker<br/> &copy;2008-2009 Terence Simpson<br />
Edited by Terence Simpson &copy;2018 Krytarik Raido
</p>
</p> </p>
</div> </div>
</body> </body>

View File

@ -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

View File

@ -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:

View File

@ -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;

View File

@ -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('&','&amp;').replace('<','&lt;').replace('>','&gt;').replace('"','&quot;') return txt.replace('&','&amp;').replace('<','&lt;').replace('>','&gt;').replace('"','&quot;')

View File

@ -1 +0,0 @@
Encyclopedia/logs.tmpl