-
Notifications
You must be signed in to change notification settings - Fork 4.2k
/
Copy pathcount_users_who_own_specified_gear.js
197 lines (159 loc) · 5.95 KB
/
count_users_who_own_specified_gear.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
var authorName = 'Alys'; // in case script author needs to know when their ...
var authorUuid = 'd904bd62-da08-416b-a816-ba797c9ee265'; //... own data is done
/**
* database_reports/count_users_who_own_specified_gear.js
* https://github.com/HabitRPG/habitica/pull/3884
*/
var thingsOfInterest = {
'Unconventional Armor ownership': {
'data_path': 'items.gear.owned',
'identifyOwnershipWith': 'exists',
'items': [
'eyewear_special_wondercon_red',
'eyewear_special_wondercon_black',
'back_special_wondercon_black',
'back_special_wondercon_red',
'body_special_wondercon_red',
'body_special_wondercon_black',
'body_special_wondercon_gold'
],
},
'Spooky Skins purchases': {
'data_path': 'purchased.skin',
'identifyOwnershipWith': 'true',
'items': [
'monster',
'pumpkin',
'skeleton',
'zombie',
'ghost',
'shadow'
]
}
};
var mongo = require('mongoskin');
var _ = require('lodash');
var dbUsers = mongo.db('localhost:27017/habitrpg?auto_reconnect').collection('users');
var thingsFound = {}; // each key is one "thing" from thingsOfInterest,
// and the value for that key is the number of users who own it
// (for items, 'owned' values of both true and false are counted
// to include items lost on death)
var query = {}; // Not worth limiting search data with query and fields since
var fields = {}; // this will be run over a local copy of the database?
console.warn('Finding data...');
var progressCount = 1000;
var count = 0;
dbUsers.findEach(query, fields, {batchSize:250}, function(err, user) {
if (err) { return exiting(1, 'ERROR! ' + err); }
if (!user) {
console.warn('All users found.');
return displayData();
}
count++;
_.each(thingsOfInterest,function(obj,label){
var data_path = obj['data_path'];
var items = obj['items'];
var identifyOwnershipWith = obj['identifyOwnershipWith'];
var userOwns = path(user, data_path, {});
_.each(items,function(item){
if ( (identifyOwnershipWith == 'exists' && item in userOwns) ||
(identifyOwnershipWith == 'true' && userOwns[item])
) {
if (! thingsFound[label]) { thingsFound[label] = {}; }
thingsFound[label][item] = (thingsFound[label][item] || 0) + 1;
// console.warn(user.auth.local.username + ": " + label + ": " + item); // good for testing, bad for privacy
}
});
});
if (count%progressCount == 0) console.warn(count + ' ' + user._id);
if (user._id == authorUuid) console.warn(authorName + ' processed');
if (user._id == '9' ) console.warn('lefnire' + ' processed');
});
function displayData() {
var today = yyyymmdd(new Date());
var csvReport = '';
var textReport = today + '\n';
_.each(thingsFound,function(obj,label){
csvReport += '\n"' + label + '"' + '\n';
textReport += '\n' + label + ':\n';
var csvHeader = '"date"'; // heading row in CSV data
var csvData = '"' + today + '"'; // data row in CSV data
var sortedKeys = _.sortBy(_.keys(obj), function(key){ return key; });
_.each(sortedKeys,function(key){
var value = obj[key];
csvHeader += ',"' + key + '"';
csvData += ',"' + (value || 0) + '"';
textReport += '\t' + key + ': ' + value + '\n';
});
csvReport += csvHeader + '\n' + csvData + '\n';
});
console.log('\nCSV DATA:\n' + csvReport + '\n\n' +
'READABLE DATA:\n\n' + textReport + '\n\n');
console.warn(count + ' users searched (should be >400k)\n');
// NB: "should be" assumes that no query filter was applied to findEach
return exiting(0);
}
function path(obj, path, def) {
/**
* Retrieve nested item from object/array
* @param {Object|Array} obj
* @param {String} path dot separated
* @param {*} def default value ( if result undefined )
* @returns {*}
* https://stackoverflow.com/a/16190716
* Usage: console.log(path(someObject, pathname));
*/
for(var i = 0,path = path.split('.'),len = path.length; i < len; i++){
if(!obj || typeof obj !== 'object') return def;
obj = obj[path[i]];
}
if(!obj || typeof obj === 'undefined') return def;
return obj;
}
function yyyymmdd(date) {
var yyyy = date.getFullYear().toString();
var mm = (date.getMonth()+1).toString();
var dd = date.getDate().toString();
return yyyy + "-" + (mm[1]?mm:"0"+mm[0]) + "-" + (dd[1]?dd:"0"+dd[0]);
}
function exiting(code, msg) {
code = code || 0; // 0 = success
if (code && !msg) { msg = 'ERROR!'; }
if (msg) {
if (code) { console.error(msg); }
else { console.log( msg); }
}
process.exit(code);
}
/* SAMPLE OUTPUT (STDOUT and STDERR):
$ node database_reports/count_users_who_own_specified_gear.js
Finding data...
Alys processed
lefnire processed
All users found.
CSV DATA:
"Unconventional Armor ownership"
"date","back_special_wondercon_black","back_special_wondercon_red","body_special_wondercon_black","body_special_wondercon_gold","body_special_wondercon_red","eyewear_special_wondercon_black","eyewear_special_wondercon_red"
"2014-09-01","7","7","7","7","7","7","9"
"Spooky Skins purchases"
"date","ghost","monster","pumpkin","shadow","skeleton","zombie"
"2014-09-01","2","3","3","6","4","3"
READABLE DATA:
2014-09-01
Unconventional Armor ownership:
back_special_wondercon_black: 7
back_special_wondercon_red: 7
body_special_wondercon_black: 7
body_special_wondercon_gold: 7
body_special_wondercon_red: 7
eyewear_special_wondercon_black: 7
eyewear_special_wondercon_red: 9
Spooky Skins purchases:
ghost: 2
monster: 3
pumpkin: 3
shadow: 6
skeleton: 4
zombie: 3
400100 users searched (should be >400k)
*/