Implement webchat and badge notifications

This commit is contained in:
Trever Fischer
2012-10-23 13:10:06 -04:00
parent c5d644371d
commit 6b8239eb3f
7 changed files with 137 additions and 8 deletions

View File

@@ -1,4 +1,5 @@
from django.conf import settings from django.conf import settings
from django.core.cache import cache
from minecraft.models import Server from minecraft.models import Server
from json import dumps, JSONEncoder from json import dumps, JSONEncoder
import beanstalkc import beanstalkc
@@ -16,6 +17,19 @@ class Event(object):
self.type = type self.type = type
self.data = data self.data = data
class ChatEvent(Event):
def __init__(self, sender, message):
super(ChatEvent, self).__init__(type='chat', data={'sender': sender,
'message': message})
class QuitEvent(Event):
def __init__(self, player):
super(QuitEvent, self).__init__(type='quit', data={'player': player})
class JoinEvent(Event):
def __init__(self, player):
super(JoinEvent, self).__init__(type='join', data={'player': player})
class BroadcastEvent(Event): class BroadcastEvent(Event):
def __init__(self, message): def __init__(self, message):
super(BroadcastEvent, self).__init__(type='broadcast', data={'message': super(BroadcastEvent, self).__init__(type='broadcast', data={'message':
@@ -58,3 +72,33 @@ def player_message(playername, message, *args):
for server in Server.objects.all(): for server in Server.objects.all():
event = PlayerMessageEvent(playername, message) event = PlayerMessageEvent(playername, message)
send_server_event(server, event) send_server_event(server, event)
def web_queue(id):
queueName = 'caminus-web-%s'%id
queue = beanstalkc.Connection(host=settings.CAMINUS_BEANSTALKD_HOST,
port = settings.CAMINUS_BEANSTALKD_PORT)
queue.use(queueName)
queue.watch(queueName)
return queue
def send_web_event(event):
latest = cache.get('minecraft-web-events')
if latest is None:
latest = []
latest.append(dumps(event, cls=EventEncoder))
while len(latest) > 10:
latest.pop(0)
cache.set('minecraft-web-events', latest, 86400);
print 'cache:', latest
if settings.CAMINUS_USE_BEANSTALKD:
queue = beanstalkc.Connection(host=settings.CAMINUS_BEANSTALKD_HOST,
port = settings.CAMINUS_BEANSTALKD_PORT)
json = dumps(event, cls=EventEncoder)
for tube in queue.tubes():
if tube.startswith("caminus-web-"):
queue.use(tube)
queue.put(json)
def chat(playername, message):
evt = ChatEvent(playername, message)
send_web_event(evt)

View File

@@ -1,4 +1,7 @@
from piston.handler import AnonymousBaseHandler, BaseHandler from piston.handler import AnonymousBaseHandler, BaseHandler
import time
from django.core.cache import cache
from django.conf import settings
from django.core.cache import cache from django.core.cache import cache
import appversion import appversion
from minecraft.models import MinecraftProfile from minecraft.models import MinecraftProfile
@@ -10,7 +13,7 @@ from urllib2 import urlopen
import json import json
from datetime import datetime from datetime import datetime
from models import cachePlayerList from models import cachePlayerList
from events import server_queue from events import server_queue, web_queue, chat, server_broadcast, send_web_event, QuitEvent, JoinEvent
class MOTDHandler(AnonymousBaseHandler): class MOTDHandler(AnonymousBaseHandler):
allowed_methods = ('GET',) allowed_methods = ('GET',)
@@ -40,6 +43,7 @@ class NewPlayerSessionHandler(BaseHandler):
server = request.server server = request.server
profile = MinecraftProfile.objects.get(mc_username__exact=playername) profile = MinecraftProfile.objects.get(mc_username__exact=playername)
session = PlayerSession.objects.create(server=server, player=profile, ip=ip) session = PlayerSession.objects.create(server=server, player=profile, ip=ip)
send_web_event(JoinEvent(playername))
return {'success': True, 'error': '', 'permissions': profile.serverPermissions(), 'sessionId': session.id} return {'success': True, 'error': '', 'permissions': profile.serverPermissions(), 'sessionId': session.id}
else: else:
return {'success': False, 'error': 'Your account is inactive.', 'permissions': []} return {'success': False, 'error': 'Your account is inactive.', 'permissions': []}
@@ -52,6 +56,7 @@ class ClosePlayerSessionHandler(BaseHandler):
for session in sessions: for session in sessions:
session.end = datetime.now() session.end = datetime.now()
session.save() session.save()
send_web_event(QuitEvent(playername))
return {'valid': True} return {'valid': True}
class EconomyHandler(BaseHandler): class EconomyHandler(BaseHandler):
@@ -79,7 +84,7 @@ class ServerPingHandler(BaseHandler):
return {'identity': request.server, 'api-version': 2, 'server-version': appversion.version()} return {'identity': request.server, 'api-version': 2, 'server-version': appversion.version()}
class ServerEventHandler(BaseHandler): class ServerEventHandler(BaseHandler):
allowed_methods = ('GET', 'POST') allowed_methods = ('GET', 'POST', 'PUT')
def read(self, request): def read(self, request):
queue = server_queue(request.server) queue = server_queue(request.server)
@@ -95,6 +100,22 @@ class ServerEventHandler(BaseHandler):
queue.delete(int(request.POST['job'])) queue.delete(int(request.POST['job']))
return {'result': 'success'} return {'result': 'success'}
def update(self, request):
events = json.loads(request.POST['events'])['events']
for evt in events:
print repr(evt)
if evt['type'] == 'chat':
chat(evt['payload']['sender'], evt['payload']['message'])
return {'result': 'success'}
class ChatHandler(BaseHandler):
allowed_methods = ('POST',)
def create(self, request):
chat(request.user.minecraftprofile.mc_username, request.POST['message'])
server_broadcast("<%s> %s"%(request.user.minecraftprofile.mc_username,
request.POST['message']))
class PollHandler(BaseHandler): class PollHandler(BaseHandler):
allowed_methods = ('GET',) allowed_methods = ('GET',)
@@ -106,4 +127,19 @@ class PollHandler(BaseHandler):
pollData['server-info'] = cache.get('caminus-server-info') pollData['server-info'] = cache.get('caminus-server-info')
if not request.user.is_anonymous(): if not request.user.is_anonymous():
pollData['user-info']['balance'] = request.user.minecraftprofile.currencyaccount.balance pollData['user-info']['balance'] = request.user.minecraftprofile.currencyaccount.balance
pollData['events'] = []
pollData['poll-id'] = timestamp
if timestamp == "0" and settings.CAMINUS_USE_BEANSTALKD:
pollData['poll-id'] = time.time()
latestEvents = cache.get('minecraft-web-events')
if not latestEvents:
latestEvents = []
for e in latestEvents:
pollData['events'].append(json.loads(e))
else:
eventQueue = web_queue(timestamp)
event = eventQueue.reserve(timeout=30)
if event:
pollData['events'].append(json.loads(event.body))
event.delete()
return pollData return pollData

View File

@@ -1,6 +1,8 @@
from django.db.models.signals import post_save from django.db.models.signals import post_save
from minecraft.models import PlayerSession, Server from minecraft.models import PlayerSession, Server
from django.core.cache import cache from django.core.cache import cache
import badges.api
import events
def cachePlayerList(): def cachePlayerList():
serverInfo = {} serverInfo = {}
@@ -15,3 +17,9 @@ def update_player_lists(sender, instance, created, **kwargs):
cachePlayerList() cachePlayerList()
post_save.connect(update_player_lists, sender=PlayerSession) post_save.connect(update_player_lists, sender=PlayerSession)
def notify_badge(sender, award, *args, **kwargs):
player = award.user.minecraftprofile.mc_username
events.server_broadcast("%s was awarded the %s badge!"%(player, award.badge.name))
badges.api.badge_awarded.connect(notify_badge)

View File

@@ -38,6 +38,11 @@ class ServerResource(Resource):
super(ServerResource, self).__init__(handler, ServerAuther()) super(ServerResource, self).__init__(handler, ServerAuther())
self.csrf_exempt = getattr(self.handler, 'csrf_exempt', True) self.csrf_exempt = getattr(self.handler, 'csrf_exempt', True)
class SimpleResource(Resource):
def __init__(self, handler):
super(SimpleResource, self).__init__(handler)
self.csrf_exempt = getattr(self.handler, 'csrf_exempt', True)
urlpatterns = patterns('api', urlpatterns = patterns('api',
url(r'^motd/(?P<username>.*)$', motdHandler), url(r'^motd/(?P<username>.*)$', motdHandler),
url(r'^server/whoami$', ServerResource(handlers.ServerPingHandler)), url(r'^server/whoami$', ServerResource(handlers.ServerPingHandler)),
@@ -45,5 +50,6 @@ urlpatterns = patterns('api',
url(r'^server/economy/(?P<playername>.*)$', ServerResource(handlers.EconomyHandler)), url(r'^server/economy/(?P<playername>.*)$', ServerResource(handlers.EconomyHandler)),
url(r'^server/session/(?P<playername>.*)/new$', ServerResource(handlers.NewPlayerSessionHandler)), url(r'^server/session/(?P<playername>.*)/new$', ServerResource(handlers.NewPlayerSessionHandler)),
url(r'^server/session/(?P<playername>.*)/close$', ServerResource(handlers.ClosePlayerSessionHandler)), url(r'^server/session/(?P<playername>.*)/close$', ServerResource(handlers.ClosePlayerSessionHandler)),
url(r'^poll/(?P<timestamp>[0-9]+)$', Resource(handlers.PollHandler)), url(r'^poll/(?P<timestamp>.+)$', SimpleResource(handlers.PollHandler)),
url(r'^chat$', SimpleResource(handlers.ChatHandler)),
) )

View File

@@ -1,8 +1,21 @@
function sendChat(message) {
$.post('/api/chat', {'message': message}, function(data) {
$('#chat-line').val('');
$('#chat-line').disabled = false;
});
}
$(document).ready(function() { $(document).ready(function() {
$('#server-interaction .drawer').each(function() { $('#server-interaction .drawer').each(function() {
var canvas = $(this).children('.canvas'); var canvas = $(this).children('.canvas');
$(this, '.drawer-label').click(function() { $(this).children('.drawer-label').click(function() {
canvas.slideToggle("blind"); canvas.slideToggle("blind");
}); });
}); });
$('#chat-line').keypress(function(evt) {
if (evt.charCode == 13) {
$('#chat-line').disabled = true;
sendChat($('#chat-line').val());
}
});
}); });

View File

@@ -1,12 +1,23 @@
function pollMessages() { function pollMessages(id) {
$.get('/api/poll/0', function(data) { $.get('/api/poll/'+id, function(data) {
if (id == 0)
$('#chat-display').html('');
$('#balance-display').html(data['user-info']['balance']); $('#balance-display').html(data['user-info']['balance']);
$(data['events']).each(function(idx, evt) {
if (evt['type'] == "chat") {
$('#chat-display').append("<li>"+evt['payload']['sender']+": "+evt['payload']['message']);
} else if (evt['type'] == 'join') {
$('#chat-display').append("<li><em>"+evt['payload']['player']+" has joined</em></li>");
} else if (evt['type'] == 'quit') {
$('#chat-display').append("<li><em>"+evt['payload']['player']+" has quit</em></li>");
}
});
window.setTimeout(function() {pollMessages(data['poll-id'])}, 1);
}); });
} }
function poll() { function poll() {
pollMessages(); pollMessages(0);
window.setTimeout(pollMessages, 3000);
} }
$(document).ready(function () { $(document).ready(function () {

View File

@@ -169,6 +169,17 @@ s.parentNode.insertBefore(po, s);
{% endif %} {% endif %}
</div> </div>
<div id="server-interaction"> <div id="server-interaction">
<div class="drawer">
<div class="drawer-label">
Chat
</div>
<div class="canvas">
<ul id="chat-display">
<li>Loading...</li>
</ul>
<input type="text" id="chat-line"/>
</div>
</div>
<div class="drawer"> <div class="drawer">
<div class="drawer-label"> <div class="drawer-label">
Online Players Online Players