collapsing all of dr_botzo one directory
This commit is contained in:
0
karma/__init__.py
Normal file
0
karma/__init__.py
Normal file
9
karma/admin.py
Normal file
9
karma/admin.py
Normal file
@@ -0,0 +1,9 @@
|
||||
"""Manage karma models in the admin interface."""
|
||||
|
||||
from django.contrib import admin
|
||||
|
||||
from karma.models import KarmaKey, KarmaLogEntry
|
||||
|
||||
|
||||
admin.site.register(KarmaKey)
|
||||
admin.site.register(KarmaLogEntry)
|
||||
161
karma/ircplugin.py
Normal file
161
karma/ircplugin.py
Normal file
@@ -0,0 +1,161 @@
|
||||
"""Karma hooks for the IRC bot."""
|
||||
|
||||
import logging
|
||||
import re
|
||||
|
||||
import irc.client
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from ircbot.lib import Plugin
|
||||
from karma.models import KarmaKey, KarmaLogEntry
|
||||
|
||||
log = logging.getLogger('karma.ircplugin')
|
||||
|
||||
|
||||
class Karma(Plugin):
|
||||
|
||||
"""Track karma and report on it."""
|
||||
|
||||
def start(self):
|
||||
"""Set up the handlers."""
|
||||
|
||||
self.connection.add_global_handler('pubmsg', self.handle_chatter, -20)
|
||||
self.connection.add_global_handler('privmsg', self.handle_chatter, -20)
|
||||
|
||||
self.connection.reactor.add_global_regex_handler(['pubmsg', 'privmsg'],
|
||||
r'^!karma\s+rank\s+(.*)$',
|
||||
self.handle_rank, -20)
|
||||
self.connection.reactor.add_global_regex_handler(['pubmsg', 'privmsg'],
|
||||
(r'^!karma\s+report\s+(highest|lowest|positive|negative'
|
||||
r'|top|opinionated)'),
|
||||
self.handle_report, -20)
|
||||
self.connection.reactor.add_global_regex_handler(['pubmsg', 'privmsg'],
|
||||
r'^!karma\s+stats\s+([\S]+)',
|
||||
self.handle_stats, -20)
|
||||
|
||||
super(Karma, self).start()
|
||||
|
||||
def stop(self):
|
||||
"""Tear down handlers."""
|
||||
|
||||
self.connection.remove_global_handler('pubmsg', self.handle_chatter)
|
||||
self.connection.remove_global_handler('privmsg', self.handle_chatter)
|
||||
|
||||
self.connection.reactor.remove_global_regex_handler(['pubmsg', 'privmsg'], self.handle_rank)
|
||||
self.connection.reactor.remove_global_regex_handler(['pubmsg', 'privmsg'], self.handle_report)
|
||||
self.connection.reactor.remove_global_regex_handler(['pubmsg', 'privmsg'], self.handle_stats)
|
||||
|
||||
super(Karma, self).stop()
|
||||
|
||||
@staticmethod
|
||||
def handle_chatter(connection, event):
|
||||
"""Watch karma from IRC chatter."""
|
||||
|
||||
what = event.arguments[0].lower()
|
||||
karma_pattern = r'(?:\((.+?)\)|(\S+))(\+\+|--|\+-|-\+)(\s+|$)'
|
||||
|
||||
where = event.target
|
||||
if where in settings.KARMA_IGNORE_CHATTER_TARGETS:
|
||||
log.debug("ignoring chatter in {0:s}".format(where))
|
||||
return
|
||||
|
||||
# check the line for karma
|
||||
log.debug("searching '%s' for karma", what)
|
||||
matches = re.findall(karma_pattern, what, re.IGNORECASE)
|
||||
for match in matches:
|
||||
key = match[0] if match[0] else match[1]
|
||||
value = match[2]
|
||||
|
||||
karma_key, c = KarmaKey.objects.get_or_create(key=key)
|
||||
|
||||
if value == '++':
|
||||
KarmaLogEntry.objects.create(key=karma_key, delta=1, nickmask=event.source)
|
||||
elif value == '--':
|
||||
KarmaLogEntry.objects.create(key=karma_key, delta=-1, nickmask=event.source)
|
||||
elif value == '+-':
|
||||
KarmaLogEntry.objects.create(key=karma_key, delta=1, nickmask=event.source)
|
||||
KarmaLogEntry.objects.create(key=karma_key, delta=-1, nickmask=event.source)
|
||||
elif value == '-+':
|
||||
KarmaLogEntry.objects.create(key=karma_key, delta=-1, nickmask=event.source)
|
||||
KarmaLogEntry.objects.create(key=karma_key, delta=1, nickmask=event.source)
|
||||
|
||||
def handle_rank(self, connection, event, match):
|
||||
"""Report on the rank of a karma item."""
|
||||
|
||||
where = event.target
|
||||
if where in settings.KARMA_IGNORE_COMMAND_TARGETS:
|
||||
log.debug("ignoring command in {0:s}".format(where))
|
||||
return
|
||||
|
||||
key = match.group(1).lower().rstrip()
|
||||
try:
|
||||
karma_key = KarmaKey.objects.get(key=key)
|
||||
return self.bot.reply(event, "{0:s} has {1:d} points of karma (rank {2:d})".format(karma_key.key,
|
||||
karma_key.score(),
|
||||
karma_key.rank()))
|
||||
except KarmaKey.DoesNotExist:
|
||||
return self.bot.reply(event, "i have not seen any karma for {0:s}".format(match.group(1)))
|
||||
|
||||
def handle_report(self, connection, event, match):
|
||||
"""Provide some karma reports."""
|
||||
|
||||
where = event.target
|
||||
if where in settings.KARMA_IGNORE_COMMAND_TARGETS:
|
||||
log.debug("ignoring command in {0:s}".format(where))
|
||||
return
|
||||
|
||||
report = match.group(1).lower()
|
||||
if report == 'highest':
|
||||
sorted_keys = KarmaKey.objects.ranked_scored_order()
|
||||
msg = "top 5 recipients: {0:s}".format(", ".join([str(x) for x in sorted_keys[:5]]))
|
||||
return self.bot.reply(event, msg)
|
||||
elif report == 'lowest':
|
||||
sorted_keys = KarmaKey.objects.ranked_scored_reverse_order()
|
||||
msg = "bottom 5 recipients: {0:s}".format(", ".join([str(x) for x in sorted_keys[:5]]))
|
||||
return self.bot.reply(event, msg)
|
||||
elif report == 'positive':
|
||||
karmaers = KarmaLogEntry.objects.optimists()
|
||||
karmaer_list = ", ".join(["{0:s} ({1:d})".format(irc.client.NickMask(x['nickmask']).nick,
|
||||
x['karma_outlook']) for x in karmaers])
|
||||
msg = "top 5 optimists: {0:s}".format(karmaer_list)
|
||||
return self.bot.reply(event, msg)
|
||||
elif report == 'negative':
|
||||
karmaers = KarmaLogEntry.objects.pessimists()
|
||||
karmaer_list = ", ".join(["{0:s} ({1:d})".format(irc.client.NickMask(x['nickmask']).nick,
|
||||
x['karma_outlook']) for x in karmaers])
|
||||
msg = "top 5 pessimists: {0:s}".format(karmaer_list)
|
||||
return self.bot.reply(event, msg)
|
||||
elif report == 'top' or report == 'opinionated':
|
||||
karmaers = KarmaLogEntry.objects.most_opinionated()
|
||||
karmaer_list = ", ".join(["{0:s} ({1:d})".format(irc.client.NickMask(x['nickmask']).nick,
|
||||
x['karma_count']) for x in karmaers])
|
||||
msg = "top 5 opinionated users: {0:s}".format(karmaer_list)
|
||||
return self.bot.reply(event, msg)
|
||||
|
||||
def handle_stats(self, connection, event, match):
|
||||
"""Provide stats on a karma user."""
|
||||
|
||||
where = event.target
|
||||
if where in settings.KARMA_IGNORE_COMMAND_TARGETS:
|
||||
log.debug("ignoring command in {0:s}".format(where))
|
||||
return
|
||||
|
||||
karmaer = match.group(1)
|
||||
log_entries = KarmaLogEntry.objects.filter(nickmask=karmaer)
|
||||
if len(log_entries) == 0:
|
||||
# fallback, try to match the nick part of nickmask
|
||||
log_entries = KarmaLogEntry.objects.filter(nickmask__startswith='{0:s}!'.format(karmaer))
|
||||
|
||||
if len(log_entries) == 0:
|
||||
return self.bot.reply(event, "karma user {0:s} not found".format(karmaer))
|
||||
|
||||
total_karma = log_entries.count()
|
||||
positive_karma = log_entries.filter(delta__gt=0).count()
|
||||
negative_karma = log_entries.filter(delta__lt=0).count()
|
||||
msg = ("{0:s} has given {1:d} postive karma and {2:d} negative karma, for a total of {3:d} karma"
|
||||
"".format(karmaer, positive_karma, negative_karma, total_karma))
|
||||
return self.bot.reply(event, msg)
|
||||
|
||||
|
||||
plugin = Karma
|
||||
29
karma/migrations/0001_initial.py
Normal file
29
karma/migrations/0001_initial.py
Normal file
@@ -0,0 +1,29 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='KarmaKey',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('key', models.CharField(unique=True, max_length=200)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='KarmaLogEntry',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('delta', models.SmallIntegerField()),
|
||||
('nickmask', models.CharField(default='', max_length=200, blank=True)),
|
||||
('created', models.DateTimeField(auto_now_add=True)),
|
||||
('key', models.ForeignKey(to='karma.KarmaKey')),
|
||||
],
|
||||
),
|
||||
]
|
||||
17
karma/migrations/0002_auto_20150519_2156.py
Normal file
17
karma/migrations/0002_auto_20150519_2156.py
Normal file
@@ -0,0 +1,17 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('karma', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='karmalogentry',
|
||||
options={'verbose_name_plural': 'karma log entries'},
|
||||
),
|
||||
]
|
||||
0
karma/migrations/__init__.py
Normal file
0
karma/migrations/__init__.py
Normal file
136
karma/models.py
Normal file
136
karma/models.py
Normal file
@@ -0,0 +1,136 @@
|
||||
"""Karma logging models."""
|
||||
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
import pytz
|
||||
|
||||
from irc.client import NickMask
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
|
||||
log = logging.getLogger('karma.models')
|
||||
|
||||
|
||||
def perdelta(start, end, delta):
|
||||
curr = start
|
||||
while curr < end:
|
||||
yield curr
|
||||
curr += delta
|
||||
yield end
|
||||
|
||||
|
||||
class KarmaKeyManager(models.Manager):
|
||||
|
||||
"""Manage handy queries for KarmaKey."""
|
||||
|
||||
def ranked_scored_order(self):
|
||||
keys = self.annotate(karma_score=models.Sum('karmalogentry__delta')).order_by('-karma_score')
|
||||
return keys
|
||||
|
||||
def ranked_scored_reverse_order(self):
|
||||
keys = self.annotate(karma_score=models.Sum('karmalogentry__delta')).order_by('karma_score')
|
||||
return keys
|
||||
|
||||
|
||||
class KarmaKey(models.Model):
|
||||
|
||||
"""Track a thing being karmaed."""
|
||||
|
||||
key = models.CharField(max_length=200, unique=True)
|
||||
|
||||
objects = KarmaKeyManager()
|
||||
|
||||
def __str__(self):
|
||||
"""String representation."""
|
||||
|
||||
return "{0:s} ({1:d})".format(self.key, self.score())
|
||||
|
||||
def score(self):
|
||||
"""Determine the score for this karma entry."""
|
||||
|
||||
return KarmaLogEntry.objects.filter(key=self).aggregate(models.Sum('delta'))['delta__sum']
|
||||
|
||||
def rank(self):
|
||||
"""Determine the rank of this karma entry relative to the others."""
|
||||
|
||||
sorted_keys = KarmaKey.objects.ranked_scored_order()
|
||||
for rank, key in enumerate(sorted_keys):
|
||||
if key == self:
|
||||
return rank+1
|
||||
return None
|
||||
|
||||
def history(self, mode='delta'):
|
||||
"""Determine the score for this karma entry at every delta."""
|
||||
|
||||
history = []
|
||||
|
||||
if mode == 'delta':
|
||||
entries = KarmaLogEntry.objects.filter(key=self).order_by('created')
|
||||
for entry in entries:
|
||||
timestamp = entry.created
|
||||
delta = entry.delta
|
||||
score = KarmaLogEntry.objects.filter(key=self).filter(created__lte=entry.created).aggregate(
|
||||
models.Sum('delta'))['delta__sum']
|
||||
|
||||
history.append((timestamp, delta, score))
|
||||
elif mode == 'date':
|
||||
first_entry = KarmaLogEntry.objects.filter(key=self).order_by('created')[0]
|
||||
slice_begin = first_entry.created.date()
|
||||
slice_end = timezone.now().date()
|
||||
for timeslice in perdelta(slice_begin, slice_end, timedelta(days=1)):
|
||||
score = KarmaLogEntry.objects.filter(key=self).filter(
|
||||
created__lte=timeslice+timedelta(days=1)).aggregate(models.Sum('delta'))['delta__sum']
|
||||
if not score:
|
||||
score = 0
|
||||
try:
|
||||
prev_score = history[-1][2]
|
||||
except IndexError:
|
||||
prev_score = 0
|
||||
|
||||
delta = score - prev_score
|
||||
|
||||
history.append((timeslice, delta, score))
|
||||
|
||||
return history
|
||||
|
||||
|
||||
class KarmaLogEntryManager(models.Manager):
|
||||
|
||||
"""Manage handy queries for KarmaLogEntry."""
|
||||
|
||||
def optimists(self):
|
||||
karmaers = self.values('nickmask').annotate(karma_outlook=models.Sum('delta')).order_by('-karma_outlook')
|
||||
return karmaers
|
||||
|
||||
def pessimists(self):
|
||||
karmaers = self.values('nickmask').annotate(karma_outlook=models.Sum('delta')).order_by('karma_outlook')
|
||||
return karmaers
|
||||
|
||||
def most_opinionated(self):
|
||||
karmaers = self.values('nickmask').annotate(karma_count=models.Count('delta')).order_by('-karma_count')
|
||||
return karmaers
|
||||
|
||||
|
||||
class KarmaLogEntry(models.Model):
|
||||
|
||||
"""Track each karma increment/decrement."""
|
||||
|
||||
key = models.ForeignKey('KarmaKey')
|
||||
delta = models.SmallIntegerField()
|
||||
nickmask = models.CharField(max_length=200, default='', blank=True)
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
objects = KarmaLogEntryManager()
|
||||
|
||||
class Meta:
|
||||
verbose_name_plural = 'karma log entries'
|
||||
|
||||
def __str__(self):
|
||||
"""String representation."""
|
||||
|
||||
tz = pytz.timezone(settings.TIME_ZONE)
|
||||
return "{0:s}{1:s} @ {2:s} by {3:s}".format(self.key.key, '++' if self.delta > 0 else '--',
|
||||
self.created.astimezone(tz).strftime('%Y-%m-%d %H:%M:%S %Z'),
|
||||
NickMask(self.nickmask).nick)
|
||||
12
karma/serializers.py
Normal file
12
karma/serializers.py
Normal file
@@ -0,0 +1,12 @@
|
||||
"""Serializers for the karma objects."""
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
from karma.models import KarmaKey
|
||||
|
||||
|
||||
class KarmaKeySerializer(serializers.ModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = KarmaKey
|
||||
fields = ('id', 'key', 'score', 'rank')
|
||||
44
karma/templates/karma/index.html
Normal file
44
karma/templates/karma/index.html
Normal file
@@ -0,0 +1,44 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load static %}
|
||||
|
||||
{% block extra_media %}
|
||||
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/t/bs/jqc-1.12.0,dt-1.10.11/datatables.min.css"/>
|
||||
<script type="text/javascript" src="https://cdn.datatables.net/t/bs/jqc-1.12.0,dt-1.10.11/datatables.min.js"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block title %}karma{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div style="width: 75%; margin-left: auto; margin-right: auto;">
|
||||
<table id="karma" class="display table table-striped table-bordered" cellspacing="0" width="100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Key</th>
|
||||
<th>Score</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<th>Key</th>
|
||||
<th>Score</th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
<tbody>
|
||||
{% for entry in entries %}
|
||||
<tr>
|
||||
<td><a href="{% url 'karma_key_detail' entry.key %}">{{ entry.key }}</a></td>
|
||||
<td>{{ entry.score }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
$('#karma').DataTable( {
|
||||
"order": [[ 1, "desc" ]],
|
||||
"lengthMenu": [[10, 25, 50, -1], [10, 25, 50, "All"]]
|
||||
} );
|
||||
} );
|
||||
</script>
|
||||
{% endblock %}
|
||||
67
karma/templates/karma/karma_key.html
Normal file
67
karma/templates/karma/karma_key.html
Normal file
@@ -0,0 +1,67 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load static %}
|
||||
|
||||
{% block extra_media %}
|
||||
<script type="text/javascript" src="{% get_static_prefix %}js/Chart.min.js"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block title %}karma: {{ entry.key }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h3>{{ entry.key }}</h3>
|
||||
<canvas id="karma_history"></canvas>
|
||||
<script type="text/javascript">
|
||||
Chart.defaults.global.responsive = true;
|
||||
Chart.defaults.global.maintainAspectRatio = true;
|
||||
Chart.types.Line.extend({name : "AltLine",
|
||||
initialize : function(data) {
|
||||
Chart.types.Line.prototype.initialize.apply(this, arguments);
|
||||
this.scale.draw = function() {
|
||||
if (this.display && (this.xLabelRotation > 90)) {
|
||||
this.endPoint = this.height - 5;
|
||||
}
|
||||
Chart.Scale.prototype.draw.apply(this, arguments);
|
||||
};
|
||||
}
|
||||
});
|
||||
var ctx = $("#karma_history").get(0).getContext("2d");
|
||||
var data = {
|
||||
labels: [{% for x in entry_history %}"{{ x.0 }}", {% endfor %}],
|
||||
datasets: [
|
||||
{
|
||||
label: "{{ entry.key }}",
|
||||
fillColor: "rgba(150,150,220,0.2)",
|
||||
strokeColor: "rgba(100,100,220,1)",
|
||||
pointColor: "rgba(50,50,220,1)",
|
||||
pointStrokeColor: "#fff",
|
||||
pointHighlightFill: "#fff",
|
||||
pointHighlightStroke: "rgba(220,220,220,1)",
|
||||
data: [{% for x in entry_history %}{{ x.2 }}, {% endfor %}]
|
||||
}
|
||||
]
|
||||
};
|
||||
var myLineChart = new Chart(ctx).AltLine(data, {
|
||||
pointDot: false,
|
||||
{% if entry_history|length > 100 %}
|
||||
showTooltips: false,
|
||||
scaleShowVerticalLines: false,
|
||||
{% endif %}
|
||||
tooltipEvents: ["click"]
|
||||
});
|
||||
</script>
|
||||
<h5>Log</h5>
|
||||
<table border="1px">
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Delta</th>
|
||||
<th>Score</th>
|
||||
</tr>
|
||||
{% for delta in entry_history %}
|
||||
<tr>
|
||||
<td>{{ delta.0 }}</td>
|
||||
<td>{{ delta.1 }}</td>
|
||||
<td>{{ delta.2 }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endblock %}
|
||||
16
karma/urls.py
Normal file
16
karma/urls.py
Normal file
@@ -0,0 +1,16 @@
|
||||
"""URL patterns for the karma views."""
|
||||
|
||||
from django.conf.urls import patterns, url, include
|
||||
from rest_framework.routers import DefaultRouter
|
||||
|
||||
from karma.views import key_detail, index, KarmaKeyViewSet
|
||||
|
||||
router = DefaultRouter()
|
||||
router.register(r'keys', KarmaKeyViewSet)
|
||||
|
||||
urlpatterns = patterns('races.views',
|
||||
url(r'^$', index, name='karma_index'),
|
||||
url(r'^key/(?P<karma_key>.+)/', key_detail, name='karma_key_detail'),
|
||||
|
||||
url(r'^api/', include(router.urls)),
|
||||
)
|
||||
35
karma/views.py
Normal file
35
karma/views.py
Normal file
@@ -0,0 +1,35 @@
|
||||
"""Present karma data."""
|
||||
|
||||
import logging
|
||||
|
||||
from django.shortcuts import get_object_or_404, render
|
||||
from rest_framework import viewsets
|
||||
|
||||
from karma.models import KarmaKey
|
||||
from karma.serializers import KarmaKeySerializer
|
||||
|
||||
log = logging.getLogger('karma.views')
|
||||
|
||||
|
||||
def index(request):
|
||||
"""Display all karma keys."""
|
||||
|
||||
entries = KarmaKey.objects.all().order_by('key')
|
||||
|
||||
return render(request, 'karma/index.html', {'entries': entries})
|
||||
|
||||
|
||||
def key_detail(request, karma_key):
|
||||
"""Display the requested karma key."""
|
||||
|
||||
entry = get_object_or_404(KarmaKey, key=karma_key.lower())
|
||||
|
||||
return render(request, 'karma/karma_key.html', {'entry': entry, 'entry_history': entry.history(mode='date')})
|
||||
|
||||
|
||||
class KarmaKeyViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
|
||||
"""Provide list and detail actions for karma keys."""
|
||||
|
||||
queryset = KarmaKey.objects.all()
|
||||
serializer_class = KarmaKeySerializer
|
||||
Reference in New Issue
Block a user