diff --git a/common/script/index.coffee b/common/script/index.coffee index 29833b0fc2..7a2006ab29 100644 --- a/common/script/index.coffee +++ b/common/script/index.coffee @@ -449,6 +449,7 @@ api.wrap = (user, main=true) -> user.items.gear.equipped[item.type] = "#{item.type}_base_0" if user.items.gear.equipped[item.type] is lostItem user.items.gear.costume[item.type] = "#{item.type}_base_0" if user.items.gear.costume[item.type] is lostItem user.markModified? 'items.gear' + mixpanel?.track('Death',{'lostItem':lostItem}) cb? (if item then {code:200,message: i18n.t('messageLostItem', {itemText: item.text(req.language)}, req.language)} else null), user reset: (req, cb) -> @@ -483,6 +484,7 @@ api.wrap = (user, main=true) -> task.value = 0 user.stats.hp = 50 cb? null, user + mixpanel?.track("Acquire Item",{'itemName':'Fortify','acquireMethod':'Gems','gemCost':4}) ga?.event('behavior', 'gems', 'reroll').send() rebirth: (req, cb, ga) -> @@ -492,6 +494,8 @@ api.wrap = (user, main=true) -> # only charge people if they are under the max level - ryan if user.stats.lvl < api.maxLevel user.balance -= 2 + mixpanel?.track("Acquire Item",{'itemName':'Rebirth','acquireMethod':'Gems','gemCost':8}) + ga?.event('behavior', 'gems', 'rebirth').send() # Save off user's level, for calculating achievement eligibility later lvl = api.capByLevel(user.stats.lvl) # Turn tasks yellow, zero out streaks @@ -541,7 +545,6 @@ api.wrap = (user, main=true) -> user.stats.buffs = {} # user.markModified? 'stats' cb? null, user - ga?.event('behavior', 'gems', 'rebirth').send() allocateNow: (req, cb) -> _.times user.stats.points, user.fns.autoAllocate @@ -752,7 +755,8 @@ api.wrap = (user, main=true) -> user.balance += .25 user.purchased.plan.gemsBought++ user.stats.gp -= convRate - return cb? {code:200,message:"+1 Gems"}, _.pick(user,$w 'stats balance') + mixpanel?.track("Acquire Item",{'itemName':key,'acquireMethod':'Gold','goldCost':convRate}) + return cb? {code:200,message:"+1 Gem"}, _.pick(user,$w 'stats balance') return cb?({code:404,message:":type must be in [eggs,hatchingPotions,food,quests,gear]"},req) unless type in ['eggs','hatchingPotions','food','quests','gear'] if type is 'gear' @@ -769,6 +773,7 @@ api.wrap = (user, main=true) -> else user.items[type][key] = 0 unless user.items[type][key] > 0 user.items[type][key]++ + mixpanel?.track("Acquire Item",{'itemName':key,'acquireMethod':'Gems','gemCost':item.value}) cb? null, _.pick(user,$w 'items balance') ga?.event('behavior', 'gems', key).send() @@ -784,6 +789,7 @@ api.wrap = (user, main=true) -> user.achievements.beastMasterCount++ user.items.currentPet = "" cb? null, user + mixpanel?.track("Acquire Item",{'itemName':'Kennel Key','acquireMethod':'Gems','gemCost':4}) releaseMounts: (req, cb) -> if user.balance < 1 @@ -797,6 +803,7 @@ api.wrap = (user, main=true) -> user.achievements.mountMasterCount = 0 user.achievements.mountMasterCount++ cb? null, user + mixpanel?.track("Acquire Item",{'itemName':'Kennel Key','acquireMethod':'Gems','gemCost':4}) releaseBoth: (req, cb) -> if user.balance < 1.5 and not user.achievements.triadBingo @@ -804,6 +811,7 @@ api.wrap = (user, main=true) -> else giveTriadBingo = true if not user.achievements.triadBingo + mixpanel?.track("Acquire Item",{'itemName':'Kennel Key','acquireMethod':'Gems','gemCost':6}) user.balance -= 1.5 user.items.currentMount = "" user.items.currentPet = "" @@ -842,6 +850,7 @@ api.wrap = (user, main=true) -> if not user.achievements.ultimateGear and item.last user.fns.ultimateGear() user.stats.gp -= item.value + mixpanel?.track("Acquire Item",{'itemName':key,'acquireMethod':'Gold','goldCost':item.value}) cb? {code:200, message}, _.pick(user,$w 'items achievements stats') buyMysterySet: (req, cb)-> @@ -850,7 +859,9 @@ api.wrap = (user, main=true) -> if window?.confirm? return unless window.confirm("Buy this full set of items for 1 Mystic Hourglass?") return cb?({code:404, message:"Mystery set not found, or set already owned"}) unless mysterySet - _.each mysterySet.items, (i)->user.items.gear.owned[i.key]=true + _.each mysterySet.items, (i)-> + user.items.gear.owned[i.key]=true + mixpanel?.track("Acquire Item",{'itemName':i.key,'acquireMethod':'Hourglass'}) user.purchased.plan.consecutive.trinkets-- cb? null, _.pick(user,$w 'items purchased.plan.consecutive') @@ -922,6 +933,7 @@ api.wrap = (user, main=true) -> user.balance -= cost if ~path.indexOf('gear.') then user.markModified? 'gear.owned' else user.markModified? 'purchased' cb? null, _.pick(user,$w 'purchased preferences items') + mixpanel?.track("Acquire Item",{'itemName':'Customizations','acquireMethod':'Gems','gemCost':(cost / .25)}) ga?.event('behavior', 'gems', path).send() # ------ @@ -961,6 +973,7 @@ api.wrap = (user, main=true) -> user.balance -= .75 _.merge user.stats, {str: 0, con: 0, per: 0, int: 0, points: api.capByLevel(user.stats.lvl)} user.flags.classSelected = false + mixpanel?.track("Acquire Item",{'itemName':klass,'acquireMethod':'Gems','gemCost':3}) ga?.event('behavior', 'gems', 'changeClass').send() #'stats.points': this is handled on the server cb? null, _.pick(user,$w 'stats flags items preferences') @@ -1444,6 +1457,7 @@ api.wrap = (user, main=true) -> user.items.quests[k]++ (user.flags.levelDrops ?= {})[k] = true user.markModified? 'flags.levelDrops' + mixpanel?.track("Acquire Item",{'itemName':k,'acquireMethod':'Drop'}) user._tmp.drop = _.defaults content.quests[k], type: 'Quest' dialog: i18n.t('messageFoundQuest', {questText: content.quests[k].text(req.language)}, req.language) @@ -1621,6 +1635,7 @@ api.wrap = (user, main=true) -> # Analytics user.flags.cronCount?=0 user.flags.cronCount++ + options.mixpanel.track('Cron',{'distinct_id':user._id,'resting':user.preferences.sleep}) options.ga?.event('behavior', 'cron', 'cron', user.flags.cronCount).send(); #TODO userId for cohort # After all is said and done, progress up user's effect on quest, return those values & reset the user's diff --git a/config.json.example b/config.json.example index fd72ea4ce9..a9d31a250b 100644 --- a/config.json.example +++ b/config.json.example @@ -21,6 +21,7 @@ "NEW_RELIC_APPLICATION_ID":"NEW_RELIC_APPLICATION_ID", "NEW_RELIC_API_KEY":"NEW_RELIC_API_KEY", "GA_ID": "GA_ID", + "MP_ID": "MP_ID", "FLAG_REPORT_EMAIL": "email@mod.com", "EMAIL_SERVER": { "url": "http://example.com", diff --git a/website/public/js/controllers/authCtrl.js b/website/public/js/controllers/authCtrl.js index 75e6de864a..48cf71cbe7 100644 --- a/website/public/js/controllers/authCtrl.js +++ b/website/public/js/controllers/authCtrl.js @@ -46,6 +46,16 @@ angular.module('habitrpg') if($rootScope.selectedLanguage) url = url + '?lang=' + $rootScope.selectedLanguage.code; $http.post(url, scope.registerVals).success(function(data, status, headers, config) { runAuth(data.id, data.apiToken); + if (status == 200) { + mixpanel.alias(data._id); + if (data.auth.facebook) { + mixpanel.register({'authType':'facebook','email':data.auth.facebook._json.email}) + } else { + mixpanel.register({'authType':'email','email':data.auth.local.email}) + } + mixpanel.register({'UUID':data._id,'language':data.preferences.language}); + mixpanel.track('Registration'); + } }).error(errorAlert); }; @@ -57,6 +67,11 @@ angular.module('habitrpg') $http.post(ApiUrl.get() + "/api/v2/user/auth/local", data) .success(function(data, status, headers, config) { runAuth(data.id, data.token); + if (status == 200) { + mixpanel.identify(data.id); + mixpanel.register({'UUID':data._id}); + mixpanel.track('Login'); + } }).error(errorAlert); }; @@ -121,13 +136,18 @@ angular.module('habitrpg') // ------ Social ---------- hello.init({ - facebook : window.env.FACEBOOK_KEY, + facebook : window.env.FACEBOOK_KEY }); $scope.socialLogin = function(network){ hello(network).login({scope:'email'}).then(function(auth){ $http.post(ApiUrl.get() + "/api/v2/user/auth/social", auth) .success(function(data, status, headers, config) { + if (status == 200) { + mixpanel.identify(data.id); + mixpanel.register({'UUID':data._id}); + mixpanel.track('Login'); + } runAuth(data.id, data.token); }).error(errorAlert); }, function( e ){ diff --git a/website/public/js/controllers/groupsCtrl.js b/website/public/js/controllers/groupsCtrl.js index 7ef422ea98..eaf23df4d3 100644 --- a/website/public/js/controllers/groupsCtrl.js +++ b/website/public/js/controllers/groupsCtrl.js @@ -320,6 +320,11 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' } $scope.message.content = ''; $scope._sending = false; + if (group.privacy == 'public'){ + mixpanel.track('Group Chat',{'groupType':group.type,'privacy':group.privacy,'groupName':group.name,'message':message}) + } else { + mixpanel.track('Group Chat',{'groupType':group.type,'privacy':group.privacy}) + } }, function(err){ $scope._sending = false; }); @@ -431,6 +436,8 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' if (confirm(window.env.t('confirmGuild'))) { group.$save(function(saved){ + if (saved.privacy == 'public') {mixpanel.track('Join Group',{'owner':true,'groupType':'guild','privacy':saved.privacy,'groupName':saved.name})} + else {mixpanel.track('Join Group',{'owner':true,'groupType':'guild','privacy':saved.privacy})} $rootScope.hardRedirect('/#/options/groups/guilds/' + saved._id); }); } @@ -445,6 +452,8 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' } group.$join(function(joined){ + if (joined.privacy == 'public') {mixpanel.track('Join Group',{'owner':false,'groupType':'guild','privacy':joined.privacy,'groupName':joined.name})} + else {mixpanel.track('Join Group',{'owner':false,'groupType':'guild','privacy':joined.privacy})} $rootScope.hardRedirect('/#/options/groups/guilds/' + joined._id); }) } @@ -510,6 +519,7 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' $scope.create = function(group){ group.$save(function(){ + mixpanel.track('Join Group',{'owner':true,'groupType':'party','privacy':'private'}); $rootScope.hardRedirect('/#/options/groups/party'); }); } @@ -517,6 +527,7 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' $scope.join = function(party){ var group = new Groups.Group({_id: party.id, name: party.name}); group.$join(function(){ + mixpanel.track('Join Group',{'owner':false,'groupType':'party','privacy':'private'}); $rootScope.hardRedirect('/#/options/groups/party'); }); } diff --git a/website/public/js/controllers/inventoryCtrl.js b/website/public/js/controllers/inventoryCtrl.js index 9dd2f698aa..deae46e618 100644 --- a/website/public/js/controllers/inventoryCtrl.js +++ b/website/public/js/controllers/inventoryCtrl.js @@ -180,6 +180,7 @@ habitrpg.controller("InventoryCtrl", $rootScope.selectedQuest = undefined; } $scope.questInit = function(){ + mixpanel.track("Quest",{"owner":true,"response":"accept","questName":$scope.selectedQuest.key}); $rootScope.party.$questAccept({key:$scope.selectedQuest.key}, function(){ $rootScope.party.$get(); }); diff --git a/website/public/js/controllers/notificationCtrl.js b/website/public/js/controllers/notificationCtrl.js index cd211e8493..a6ca023876 100644 --- a/website/public/js/controllers/notificationCtrl.js +++ b/website/public/js/controllers/notificationCtrl.js @@ -87,6 +87,7 @@ habitrpg.controller('NotificationCtrl', Notification.drop(User.user._tmp.drop.dialog); } $rootScope.playSound('Item_Drop'); + mixpanel.track("Acquire Item",{'itemName':after.key,'acquireMethod':'Drop'}) }); $rootScope.$watch('user.achievements.streak', function(after, before){ diff --git a/website/public/js/controllers/tasksCtrl.js b/website/public/js/controllers/tasksCtrl.js index c070727f8f..fbd8e90912 100644 --- a/website/public/js/controllers/tasksCtrl.js +++ b/website/public/js/controllers/tasksCtrl.js @@ -21,7 +21,9 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N if (direction === 'down') $rootScope.playSound('Minus_Habit'); else if (direction === 'up') $rootScope.playSound('Plus_Habit'); } - User.user.ops.score({params:{id: task.id, direction:direction}}) + User.user.ops.score({params:{id: task.id, direction:direction}}); + mixpanel.register({'Gold':Math.floor(User.user.stats.gp),'Health':Math.ceil(User.user.stats.hp),'Experience':Math.floor(User.user.stats.exp),'Level':User.user.stats.lvl,'Mana':Math.floor(User.user.stats.mp),'Class':User.user.stats.class,'subscription':User.user.purchased.plan.planId,'contributorLevel':User.user.contributor.level,'UUID':User.user._id}); + mixpanel.track('Score Task',{'taskType':task.type,'direction':direction}); }; function addTask(addTo, listDef, task) { diff --git a/website/public/js/services/guideServices.js b/website/public/js/services/guideServices.js index 3a0a37bd95..1e130188c9 100644 --- a/website/public/js/services/guideServices.js +++ b/website/public/js/services/guideServices.js @@ -166,11 +166,13 @@ function($rootScope, User, $timeout, $state) { return $timeout(function(){}); } window.ga && ga('send', 'event', 'behavior', 'tour', k, i+1); + mixpanel.track('Tutorial',{'tour':k+'-web','step':i+1,'complete':false}); } step.onHide = function(){ if (step.final) { // -2 indicates complete var ups={};ups['flags.tour.'+k] = -2; User.set(ups); + mixpanel.track('Tutorial',{'tour':k+'-web','step':i+1,'complete':true}); } } }) diff --git a/website/src/controllers/payments/index.js b/website/src/controllers/payments/index.js index 31d691a7fe..3a86a101d9 100644 --- a/website/src/controllers/payments/index.js +++ b/website/src/controllers/payments/index.js @@ -72,6 +72,7 @@ exports.createSubscription = function(data, cb) { if (!data.gift) utils.txnEmail(data.user, 'subscription-begins'); utils.ga.event('commerce', 'subscribe', data.paymentMethod, block.price).send(); utils.ga.transaction(data.user._id, block.price).item(block.price, 1, data.paymentMethod.toLowerCase() + '-subscription', data.paymentMethod).send(); + utils.mixpanel.track('purchase',{'distinct_id':data.user._id,'itemPurchased':block.key,'purchaseValue':block.price}) } data.user.purchased.txnCount++; if (data.gift){ @@ -123,6 +124,7 @@ exports.buyGems = function(data, cb) { if(isProduction) { if (!data.gift) utils.txnEmail(data.user, 'donation'); utils.ga.event('commerce', 'checkout', data.paymentMethod, amt).send(); + utils.mixpanel.track('purchase',{'distinct_id':data.user._id,'itemPurchased':'Gems','purchaseValue':amt}) //TODO ga.transaction to reflect whether this is gift or self-purchase utils.ga.transaction(data.user._id, amt).item(amt, 1, data.paymentMethod.toLowerCase() + "-checkout", "Gems > " + data.paymentMethod).send(); } diff --git a/website/src/controllers/user.js b/website/src/controllers/user.js index eaab7279b4..0fb27aef1b 100644 --- a/website/src/controllers/user.js +++ b/website/src/controllers/user.js @@ -334,7 +334,7 @@ api.update = function(req, res, next) { api.cron = function(req, res, next) { var user = res.locals.user, - progress = user.fns.cron({ga:ga}), + progress = user.fns.cron({ga:ga, mixpanel:utils.mixpanel}), ranCron = user.isModified(), quest = shared.content.quests[user.party.quest.key]; diff --git a/website/src/middleware.js b/website/src/middleware.js index 5c77e0a5dd..abd98ea048 100644 --- a/website/src/middleware.js +++ b/website/src/middleware.js @@ -199,7 +199,8 @@ module.exports.locals = function(req, res, next) { mods: require('./models/user').mods, tavern: tavern, // for world boss worldDmg: (tavern && tavern.quest && tavern.quest.extra && tavern.quest.extra.worldDmg) || {}, - _: _ + _: _, + MP_ID: nconf.get('MP_ID') }); // Put query-string party (& guild but use partyInvite for backward compatibility) diff --git a/website/src/utils.js b/website/src/utils.js index 00ee9c106b..85ffb664ec 100644 --- a/website/src/utils.js +++ b/website/src/utils.js @@ -179,6 +179,8 @@ module.exports.setupConfig = function(){ baseUrl = nconf.get('BASE_URL'); module.exports.ga = require('universal-analytics')(nconf.get('GA_ID')); + var mixpanel = require('mixpanel') + module.exports.mixpanel = mixpanel.init(nconf.get('MP_ID')); }; var algorithm = 'aes-256-ctr'; diff --git a/website/views/index.jade b/website/views/index.jade index 8c93d28e04..cf70ebccdf 100644 --- a/website/views/index.jade +++ b/website/views/index.jade @@ -21,6 +21,11 @@ html(ng-app="habitrpg", ng-controller="RootCtrl", ng-class='{"applying-action":a script(type='text/javascript'). window.env = !{JSON.stringify(env)}; + script(type='text/javascript'). + (function(f,b){if(!b.__SV){var a,e,i,g;window.mixpanel=b;b._i=[];b.init=function(a,e,d){function f(b,h){var a=h.split(".");2==a.length&&(b=b[a[0]],h=a[1]);b[h]=function(){b.push([h].concat(Array.prototype.slice.call(arguments,0)))}}var c=b;"undefined"!==typeof d?c=b[d]=[]:d="mixpanel";c.people=c.people||[];c.toString=function(b){var a="mixpanel";"mixpanel"!==d&&(a+="."+d);b||(a+=" (stub)");return a};c.people.toString=function(){return c.toString(1)+".people (stub)"};i="disable track track_pageview track_links track_forms register register_once alias unregister identify name_tag set_config people.set people.set_once people.increment people.append people.union people.track_charge people.clear_charges people.delete_user".split(" "); + for(g=0;g