def process_presence_update(msg): message_type = msg.delivery_info["routing_key"] payload = json.loads(msg.body) namespace = payload["namespace"] if not namespace.startswith("/robin/"): return user_id36 = posixpath.basename(namespace) room_namespace = posixpath.dirname(namespace) room_id = posixpath.basename(room_namespace) account = Account._byID36(user_id36, data=True, stale=True) try: room = RobinRoom._byID(room_id) except tdb_cassandra.NotFoundException: return if not room.is_participant(account): return presence_type = "join" if message_type == "websocket.connect" else "part" websockets.send_broadcast( namespace=room_namespace, type=presence_type, payload={ "user": account.name, }, ) if presence_type == "join": ParticipantPresenceByRoom.mark_joined(room, account) else: ParticipantPresenceByRoom.mark_exited(room, account)
def update_activity(): event_ids = ActiveVisitorsByLiveUpdateEvent._cf.get_range(column_count=1, filter_empty=False) for event_id, is_active in event_ids: count = 0 if is_active: try: count = ActiveVisitorsByLiveUpdateEvent.get_count(event_id) except tdb_cassandra.TRANSIENT_EXCEPTIONS as e: g.log.warning("Failed to fetch activity count for %r: %s", event_id, e) return try: LiveUpdateEvent.update_activity(event_id, count) except tdb_cassandra.TRANSIENT_EXCEPTIONS as e: g.log.warning("Failed to update event activity for %r: %s", event_id, e) try: LiveUpdateActivityHistoryByEvent.record_activity(event_id, count) except tdb_cassandra.TRANSIENT_EXCEPTIONS as e: g.log.warning("Failed to update activity history for %r: %s", event_id, e) is_fuzzed = False if count < ACTIVITY_FUZZING_THRESHOLD: count = utils.fuzz_activity(count) is_fuzzed = True websockets.send_broadcast("/live/" + event_id, type="activity", payload={"count": count, "fuzzed": is_fuzzed}) # ensure that all the amqp messages we've put on the worker's queue are # sent before we allow this script to exit. amqp.worker.join()
def POST_vote(self, form, jquery, room, vote): if self._has_exceeded_ratelimit(form, room): return if not vote: # TODO: error return? return g.stats.simple_event('robin.vote.%s' % vote) room.set_vote(c.user, vote) websockets.send_broadcast( namespace="/robin/" + room.id, type="vote", payload={ "from": c.user.name, "vote": vote, }, ) events.vote( room=room, vote=vote, sent_dt=datetime.datetime.utcnow(), context=c, request=request, )
def _prompt_for_voting(): now = datetime.now(g.tz) print "%s: prompting voting in rooms with less than %s remaining" % ( now, VOTING_PROMPT_TIME) count = 0 for room in RobinRoom.generate_voting_rooms(): # Skip if we've already prompted if room.has_prompted(): continue # Skip this room if too much time still remains alert_after = room.date + (LEVEL_TIMINGS[room.level] - VOTING_PROMPT_TIME) if now < alert_after: continue count += 1 websockets.send_broadcast( namespace="/robin/" + room.id, type="please_vote", payload={}, ) room.mark_prompted() print "prompted %s" % room print "%s: done prompting (%s rooms took %s)" % (datetime.now( g.tz), count, datetime.now(g.tz) - now)
def update_activity(): events = {} event_counts = collections.Counter() query = (ev for ev in LiveUpdateEvent._all() if ev.state == "live" and not ev.banned) for chunk in utils.in_chunks(query, size=100): context_ids = {ev._fullname: ev._id for ev in chunk} view_countable = [ev._fullname for ev in chunk if ev._date >= g.liveupdate_min_date_viewcounts] view_counts_query = ViewCountsQuery.execute_async(view_countable) try: with c.activity_service.retrying(attempts=4) as svc: infos = svc.count_activity_multi(context_ids.keys()) except TTransportException: continue view_counts = view_counts_query.result() for context_id, info in infos.iteritems(): event_id = context_ids[context_id] try: LiveUpdateActivityHistoryByEvent.record_activity( event_id, info.count) except tdb_cassandra.TRANSIENT_EXCEPTIONS as e: g.log.warning("Failed to update activity history for %r: %s", event_id, e) try: event = LiveUpdateEvent.update_activity( event_id, info.count, info.is_fuzzed) except tdb_cassandra.TRANSIENT_EXCEPTIONS as e: g.log.warning("Failed to update event activity for %r: %s", event_id, e) else: events[event_id] = event event_counts[event_id] = info.count websockets.send_broadcast( "/live/" + event_id, type="activity", payload={ "count": info.count, "fuzzed": info.is_fuzzed, "total_views": view_counts.get(context_id), }, ) top_event_ids = [event_id for event_id, count in event_counts.most_common(1000)] top_events = [events[event_id] for event_id in top_event_ids] query_ttl = datetime.timedelta(days=3) with CachedQueryMutator() as m: m.replace(get_active_events(), top_events, ttl=query_ttl) # ensure that all the amqp messages we've put on the worker's queue are # sent before we allow this script to exit. amqp.worker.join()
def _prompt_for_voting(): now = datetime.now(g.tz) print "%s: prompting voting in rooms with less than %s remaining" % ( now, VOTING_PROMPT_TIME) count = 0 for room in RobinRoom.generate_voting_rooms(): # Skip if we've already prompted if room.has_prompted(): continue # Skip this room if too much time still remains alert_after = room.date + (LEVEL_TIMINGS[room.level] - VOTING_PROMPT_TIME) if now < alert_after: continue count += 1 websockets.send_broadcast( namespace="/robin/" + room.id, type="please_vote", payload={}, ) room.mark_prompted() print "prompted %s" % room print "%s: done prompting (%s rooms took %s)" % ( datetime.now(g.tz), count, datetime.now(g.tz) - now)
def alert_no_match(room): print "no match for %s" % room websockets.send_broadcast( namespace="/robin/" + room.id, type="no_match", payload={}, )
def update_activity(): events = {} event_counts = collections.Counter() event_ids = ActiveVisitorsByLiveUpdateEvent._cf.get_range( column_count=1, filter_empty=False) for event_id, is_active in event_ids: count = 0 if is_active: try: count = ActiveVisitorsByLiveUpdateEvent.get_count(event_id) except tdb_cassandra.TRANSIENT_EXCEPTIONS as e: g.log.warning("Failed to fetch activity count for %r: %s", event_id, e) return try: LiveUpdateActivityHistoryByEvent.record_activity(event_id, count) except tdb_cassandra.TRANSIENT_EXCEPTIONS as e: g.log.warning("Failed to update activity history for %r: %s", event_id, e) is_fuzzed = False if count < ACTIVITY_FUZZING_THRESHOLD: count = utils.fuzz_activity(count) is_fuzzed = True try: event = LiveUpdateEvent.update_activity(event_id, count, is_fuzzed) except tdb_cassandra.TRANSIENT_EXCEPTIONS as e: g.log.warning("Failed to update event activity for %r: %s", event_id, e) else: events[event_id] = event event_counts[event_id] = count websockets.send_broadcast( "/live/" + event_id, type="activity", payload={ "count": count, "fuzzed": is_fuzzed, }, ) top_event_ids = [ event_id for event_id, count in event_counts.most_common(1000) ] top_events = [events[event_id] for event_id in top_event_ids] query_ttl = datetime.timedelta(days=3) with CachedQueryMutator() as m: m.replace(get_active_events(), top_events, ttl=query_ttl) # ensure that all the amqp messages we've put on the worker's queue are # sent before we allow this script to exit. amqp.worker.join()
def update_activity(): events = {} event_counts = collections.Counter() query = (ev for ev in LiveUpdateEvent._all() if ev.state == "live" and not ev.banned) for chunk in utils.in_chunks(query, size=100): context_ids = {"LiveUpdateEvent_" + ev._id: ev._id for ev in chunk} try: with c.activity_service.retrying(attempts=4) as svc: infos = svc.count_activity_multi(context_ids.keys()) except TTransportException: continue for context_id, info in infos.iteritems(): event_id = context_ids[context_id] try: LiveUpdateActivityHistoryByEvent.record_activity( event_id, info.count) except tdb_cassandra.TRANSIENT_EXCEPTIONS as e: g.log.warning("Failed to update activity history for %r: %s", event_id, e) try: event = LiveUpdateEvent.update_activity( event_id, info.count, info.is_fuzzed) except tdb_cassandra.TRANSIENT_EXCEPTIONS as e: g.log.warning("Failed to update event activity for %r: %s", event_id, e) else: events[event_id] = event event_counts[event_id] = info.count websockets.send_broadcast( "/live/" + event_id, type="activity", payload={ "count": info.count, "fuzzed": info.is_fuzzed, }, ) top_event_ids = [ event_id for event_id, count in event_counts.most_common(1000) ] top_events = [events[event_id] for event_id in top_event_ids] query_ttl = datetime.timedelta(days=3) with CachedQueryMutator() as m: m.replace(get_active_events(), top_events, ttl=query_ttl) # ensure that all the amqp messages we've put on the worker's queue are # sent before we allow this script to exit. amqp.worker.join()
def update_activity(): events = {} event_counts = collections.Counter() event_ids = ActiveVisitorsByLiveUpdateEvent._cf.get_range( column_count=1, filter_empty=False) for event_id, is_active in event_ids: count = 0 if is_active: try: count = ActiveVisitorsByLiveUpdateEvent.get_count(event_id) except tdb_cassandra.TRANSIENT_EXCEPTIONS as e: g.log.warning("Failed to fetch activity count for %r: %s", event_id, e) return try: LiveUpdateActivityHistoryByEvent.record_activity(event_id, count) except tdb_cassandra.TRANSIENT_EXCEPTIONS as e: g.log.warning("Failed to update activity history for %r: %s", event_id, e) is_fuzzed = False if count < ACTIVITY_FUZZING_THRESHOLD: count = utils.fuzz_activity(count) is_fuzzed = True try: event = LiveUpdateEvent.update_activity(event_id, count, is_fuzzed) except tdb_cassandra.TRANSIENT_EXCEPTIONS as e: g.log.warning("Failed to update event activity for %r: %s", event_id, e) else: events[event_id] = event event_counts[event_id] = count websockets.send_broadcast( "/live/" + event_id, type="activity", payload={ "count": count, "fuzzed": is_fuzzed, }, ) top_event_ids = [event_id for event_id, count in event_counts.most_common(1000)] top_events = [events[event_id] for event_id in top_event_ids] query_ttl = datetime.timedelta(days=3) with CachedQueryMutator() as m: m.replace(get_active_events(), top_events, ttl=query_ttl) # ensure that all the amqp messages we've put on the worker's queue are # sent before we allow this script to exit. amqp.worker.join()
def abandon_room(room): print "abandoning %s" % room room.abandon() websockets.send_broadcast( namespace="/robin/" + room.id, type="abandon", payload={}, ) g.stats.simple_event('robin.reaper.abandon')
def remove_abandoners(room, users): print "removing %s from %s" % (users, room) room.remove_participants(users) websockets.send_broadcast( namespace="/robin/" + room.id, type="users_abandoned", payload={ "users": [user.name for user in users], }, )
def POST_admin_broadcast(self, form, jquery, message): if form.has_errors("message", errors.NO_TEXT, errors.TOO_LONG): return websockets.send_broadcast( namespace="/robin", type="system_broadcast", payload={ "body": message, }, )
def POST_drawrect(self, responder, x, y, width, height): if x is None: # copy the error set by VNumber/VInt c.errors.add( error_name=errors.BAD_NUMBER, field="x", msg_params={ "range": _("%(min)d to %(max)d") % { "min": 0, "max": CANVAS_WIDTH, }, }, ) if y is None: # copy the error set by VNumber/VInt c.errors.add( error_name=errors.BAD_NUMBER, field="y", msg_params={ "range": _("%(min)d to %(max)d") % { "min": 0, "max": CANVAS_HEIGHT, }, }, ) if (responder.has_errors("x", errors.BAD_NUMBER) or responder.has_errors("y", errors.BAD_NUMBER)): # TODO: return 400 with parsable error message? return # prevent drawing outside of the canvas width = min(CANVAS_WIDTH - x, width) height = min(CANVAS_HEIGHT - y, height) batch_payload = [] for _x in xrange(x, x + width): for _y in xrange(y, y + height): pixel = Pixel.create(None, 0, _x, _y) payload = { "author": '', "x": _x, "y": _y, "color": 0, } batch_payload.append(payload) websockets.send_broadcast( namespace="/place", type="batch-place", payload=batch_payload, )
def POST_leave_room(self, form, jquery): room = RobinRoom.get_room_for_user(c.user) if not room: return room.remove_participants([c.user]) websockets.send_broadcast( namespace="/robin/" + room.id, type="users_abandoned", payload={ "users": [c.user.name], }, )
def _update_timer(): if not g.live_config['thebutton_is_active']: print "%s: thebutton is inactive" % datetime.now(g.tz) websockets.send_broadcast(namespace="/thebutton", type="not_started", payload={}) return expiration_time = has_timer_expired() if expiration_time: seconds_elapsed = (datetime.now(g.tz) - expiration_time).total_seconds() print "%s: timer is expired %s ago" % (datetime.now( g.tz), seconds_elapsed) websockets.send_broadcast(namespace="/thebutton", type="expired", payload={"seconds_elapsed": seconds_elapsed}) return if not has_timer_started(): print "%s: timer not started" % datetime.now(g.tz) websockets.send_broadcast(namespace="/thebutton", type="not_started", payload={}) return seconds_left = round(get_seconds_left()) if seconds_left < 0: print "%s: timer just expired" % datetime.now(g.tz) mark_timer_expired(datetime.now(g.tz)) websockets.send_broadcast(namespace="/thebutton", type="just_expired", payload={}) else: now = datetime.now(g.tz) now_str = datetime_to_str(now) tick_mac = make_tick_mac(int(seconds_left), now_str) print "%s: timer is ticking %s" % (datetime.now(g.tz), seconds_left) websockets.send_broadcast( namespace="/thebutton", type="ticking", payload={ "seconds_left": seconds_left, "now_str": now_str, "tick_mac": tick_mac, "participants_text": format_number(get_num_participants(), locale='en'), }, )
def merge_rooms(room1, room2): print "merging %s + %s" % (room1, room2) new_room = RobinRoom.merge(room1, room2) for room in (room1, room2): websockets.send_broadcast( namespace="/robin/" + room.id, type="merge", payload={ "destination": new_room.id, }, ) g.stats.simple_event('robin.reaper.merge')
def process_subreddit_maker(msg): room_id = msg.body try: room = RobinRoom._byID(room_id) except tdb_cassandra.NotFound: try: room = RobinRoomDead._byID(room_id) except tdb_cassandra.NotFound: print "can't find room %s, giving up" % room_id print 'creating sr for room %s' % room subreddit = room.create_sr() print 'got %s from room.create_sr()' % subreddit if subreddit: g.stats.simple_event("robin.subreddit.created") participant_ids = room.get_all_participants() participants = [ Account._byID(participant_id) for participant_id in participant_ids ] moderators = participants[:5] print 'adding moderators to %s' % subreddit for moderator in moderators: subreddit.add_moderator(moderator) print 'adding contributors to %s' % subreddit g.stats.simple_event( "robin.subreddit.contributors_added", delta=len(participants), ) for participant in participants: # To be replaced with UserRel hacking? subreddit.add_contributor(participant) send_sr_message(subreddit, participant) payload = { "body": subreddit.name, } websockets.send_broadcast( namespace="/robin/" + room.id, type="continue", payload=payload, ) else: g.stats.simple_event("robin.subreddit.creation_failed") print 'subreddit creation failed for room %s' % room.id
def broadcast_activity(): try: activity = get_activity_count() websockets.send_broadcast( namespace="/place", type="activity", payload={ "count": activity, }, ) except ActivityError: print "failed to fetch activity" # ensure the message we put on the amqp worker queue is flushed before we # exit. amqp.worker.join()
def _update_timer(): if not g.live_config['thebutton_is_active']: print "%s: thebutton is inactive" % datetime.now(g.tz) websockets.send_broadcast( namespace="/thebutton", type="not_started", payload={}) return expiration_time = has_timer_expired() if expiration_time: seconds_elapsed = (datetime.now(g.tz) - expiration_time).total_seconds() print "%s: timer is expired %s ago" % (datetime.now(g.tz), seconds_elapsed) websockets.send_broadcast( namespace="/thebutton", type="expired", payload={"seconds_elapsed": seconds_elapsed}) return if not has_timer_started(): print "%s: timer not started" % datetime.now(g.tz) websockets.send_broadcast( namespace="/thebutton", type="not_started", payload={}) return seconds_left = round(get_seconds_left()) if seconds_left < 0: print "%s: timer just expired" % datetime.now(g.tz) mark_timer_expired(datetime.now(g.tz)) websockets.send_broadcast( namespace="/thebutton", type="just_expired", payload={}) else: now = datetime.now(g.tz) now_str = datetime_to_str(now) tick_mac = make_tick_mac(int(seconds_left), now_str) print "%s: timer is ticking %s" % (datetime.now(g.tz), seconds_left) websockets.send_broadcast( namespace="/thebutton", type="ticking", payload={ "seconds_left": seconds_left, "now_str": now_str, "tick_mac": tick_mac, "participants_text": format_number(get_num_participants(), locale='en'), }, )
def broadcast_update(): event_ids = ActiveVisitorsByLiveUpdateEvent._cf.get_range( column_count=1, filter_empty=False) for event_id, is_active in event_ids: if is_active: count, is_fuzzed = ActiveVisitorsByLiveUpdateEvent.get_count( event_id, cached=False) else: count, is_fuzzed = 0, False payload = { "count": count, "fuzzed": is_fuzzed, } websockets.send_broadcast( "/live/" + event_id, type="activity", payload=payload) amqp.worker.join()
def broadcast_update(): event_ids = ActiveVisitorsByLiveUpdateEvent._cf.get_range( column_count=1, filter_empty=False) for event_id, is_active in event_ids: if is_active: count, is_fuzzed = ActiveVisitorsByLiveUpdateEvent.get_count( event_id, cached=False) else: count, is_fuzzed = 0, False payload = { "count": count, "fuzzed": is_fuzzed, } websockets.send_broadcast( "/live/" + event_id, type="activity", payload=payload) # ensure that all the amqp messages we've put on the worker's queue are # sent before we allow this script to exit. amqp.worker.join()
def POST_message(self, form, jquery, room, message): if self._has_exceeded_ratelimit(form, room): return if form.has_errors("message", errors.NO_TEXT, errors.TOO_LONG): return websockets.send_broadcast( namespace="/robin/" + room.id, type="chat", payload={ "from": c.user.name, "body": message, }, ) events.message( room=room, message=message, sent_dt=datetime.datetime.utcnow(), context=c, request=request, )
def process_waitinglist(msg): user_id36 = msg.body user = Account._byID36(user_id36, data=True, stale=True) if RobinRoom.get_room_for_user(user): print "%s already in room" % user.name return with g.make_lock("robin_room", "global"): current_room_id = g.gencache.get("current_robin_room") if not current_room_id: current_room = make_new_room() else: try: current_room = RobinRoom._byID(current_room_id) except tdb_cassandra.NotFoundException: current_room_id = None current_room = make_new_room() if not current_room.is_alive or current_room.is_continued: current_room_id = None current_room = make_new_room() current_room.add_participants([user]) print "added %s to %s" % (user.name, current_room.id) if current_room_id: g.gencache.delete("current_robin_room") current_room.persist_computed_name() websockets.send_broadcast( namespace="/robin/" + current_room.id, type="updated_name", payload={ "room_name": current_room.name, }, ) else: g.gencache.set("current_robin_room", current_room.id)
def process_waitinglist(msg): user_id36 = msg.body user = Account._byID36(user_id36, data=True, stale=True) if RobinRoom.get_room_for_user(user): print "%s already in room" % user.name return with g.make_lock("robin_room", "global"): current_room_id = g.cache.get("current_robin_room") if not current_room_id: current_room = make_new_room() else: try: current_room = RobinRoom._byID(current_room_id) except tdb_cassandra.NotFoundException: current_room_id = None current_room = make_new_room() if not current_room.is_alive or current_room.is_continued: current_room_id = None current_room = make_new_room() current_room.add_participants([user]) print "added %s to %s" % (user.name, current_room.id) if current_room_id: g.cache.delete("current_robin_room") current_room.persist_computed_name() websockets.send_broadcast( namespace="/robin/" + current_room.id, type="updated_name", payload={ "room_name": current_room.name, }, ) else: g.cache.set("current_robin_room", current_room.id)
def POST_draw(self, responder, x, y, color): #if c.user._date >= ACCOUNT_CREATION_CUTOFF: # self.abort403() if PLACE_SUBREDDIT.is_banned(c.user): self.abort403() if x is None: # copy the error set by VNumber/VInt c.errors.add( error_name=errors.BAD_NUMBER, field="x", msg_params={ "range": _("%(min)d to %(max)d") % { "min": 0, "max": CANVAS_WIDTH, }, }, ) if y is None: # copy the error set by VNumber/VInt c.errors.add( error_name=errors.BAD_NUMBER, field="y", msg_params={ "range": _("%(min)d to %(max)d") % { "min": 0, "max": CANVAS_HEIGHT, }, }, ) if color is None: c.errors.add(errors.BAD_COLOR, field="color") if (responder.has_errors("x", errors.BAD_NUMBER) or responder.has_errors("y", errors.BAD_NUMBER) or responder.has_errors("color", errors.BAD_COLOR)): # TODO: return 400 with parsable error message? return if c.user_is_admin: wait_seconds = 0 else: wait_seconds = get_wait_seconds(c.user) if wait_seconds > 2: response.status = 429 request.environ['extra_error_data'] = { "error": 429, "wait_seconds": wait_seconds, } return Pixel.create(c.user, color, x, y) c.user.set_flair( subreddit=PLACE_SUBREDDIT, text="({x},{y}) {time}".format(x=x, y=y, time=time.time()), css_class="place-%s" % color, ) websockets.send_broadcast( namespace="/place", type="place", payload={ "author": c.user.name, "x": x, "y": y, "color": color, } ) events.place_pixel(x, y, color) cooldown = 0 if c.user_is_admin else PIXEL_COOLDOWN_SECONDS return { 'wait_seconds': cooldown, }
def send_websocket_broadcast(type, payload): websockets.send_broadcast(namespace="/live/" + c.liveupdate_event._id, type=type, payload=payload)
def send_event_broadcast(event_id, type, payload): """ Send a liveupdate broadcast for a specific event. """ websockets.send_broadcast(namespace="/live/" + event_id, type=type, payload=payload)