Merge branch 'master' into production

This commit is contained in:
Nick Winter 2015-12-09 06:23:14 -08:00
commit 8230004962
4 changed files with 104 additions and 51 deletions

View file

@ -142,7 +142,7 @@ module.exports = nativeDescription: "Ελληνικά", englishDescription: "Gre
save: "Αποθήκευση"
publish: "Δημοσίευση"
create: "Δημιουργία"
# fork: "Fork"
fork: "Κλώνος"
play: "Παίξε" # When used as an action verb, like "Play next level"
retry: "Ξαναδοκίμασε"
actions: "Ενέργειες"
@ -352,7 +352,7 @@ module.exports = nativeDescription: "Ελληνικά", englishDescription: "Gre
tip_solve_then_write: "Πρώτα, λύσε το πρόβλημα. Μετά, γράψε τον κώδικα. - John Johnson"
game_menu:
inventory_tab: "Απογραφή"
inventory_tab: "εξοπλισμος"
save_load_tab: "Αποθήκευση/Φόρτωση"
options_tab: "Επιλογές"
guide_tab: "Οδηγός"
@ -374,12 +374,12 @@ module.exports = nativeDescription: "Ελληνικά", englishDescription: "Gre
top_players: "Κορυφαιοι Παικτες "
day: "Ημερας"
week: "Εβδομαδας"
all: "Ολων των Εποχων"
time: "τη διαρκεια της"
damage_taken: "Ζημιά που δέχθηκες"
damage_dealt: "Ζημιά που αντιμετώπησες"
difficulty: "δυσκολία"
gold_collected: "Χρυσός που μαζεύτηκε"
all: "Αιωνιοτητας"
time: "στη διαρκεια της"
damage_taken: "Δέχθηκες"
damage_dealt: "Προκάλεσες"
difficulty: "Δυσκολία"
gold_collected: "Συλλέχθηκε Χρυσός "
inventory:
choose_inventory: "Εξοπλισμός"
@ -589,7 +589,7 @@ module.exports = nativeDescription: "Ελληνικά", englishDescription: "Gre
press_paragraph_1_suffix: ". Όλα τα λογότυπα και οι εικόνες μπορούν χρησιμοποιηθούν χωρίς να επικοινωνήσετε με εμάς άμεσα."
team: "Ομάδα"
george_title: "Συνιδρυτής"
# george_blurb: "Businesser"
george_blurb: " Ο Επιχειρηματικός"
scott_title: "Συνιδρυτής"
scott_blurb: "Ο Λογικός"
nick_title: "Συνιδρυτής"
@ -740,17 +740,17 @@ module.exports = nativeDescription: "Ελληνικά", englishDescription: "Gre
wrong_password: "Λάθος Κωδικός"
upload_picture: "Ανέβασμα φωτογραφίας"
delete_this_account: "Μόνιμη διαγραφή λογαριασμού"
# reset_progress_tab: "Reset All Progress"
# reset_your_progress: "Clear all your progress and start over"
reset_progress_tab: "Εκκαθαριση Προοδου"
reset_your_progress: "Εκκαθάριση Πρόοδου και Επανεκκίνηση"
god_mode: "Κατάσταση Θεού!"
password_tab: "Κωδικός"
emails_tab: "Emails"
admin: "Διαχειριστής"
# manage_subscription: "Click here to manage your subscription."
manage_subscription: "Κάνε κλικ εδώ για διαχείριση της σύνδρομης σου."
new_password: "Καινούργιος Κωδικός"
new_password_verify: " Επαλήθευση Κωδικού"
type_in_email: "Γράψτε τη διεύθυνση ηλεκτρονικού ταχυδρομείου σας για να επιβεβαιώσετε τη διαγραφή του λογαριασμού."
# type_in_email_progress: "Type in your email to confirm deleting your progress."
type_in_email_progress: "Πληκτρολόγησε το email σου για επιβεβαιώση της εκκαθάρισης πρόοδου."
type_in_password: "Επίσης, γράψτε τον κωδικό σας."
email_subscriptions: "Συνδρομές ηλεκτρονικού ταχυδρομείου"
email_subscriptions_none: "Δεν υπάρχουν συνδρομές ηλεκτρονικού ταχυδρομείου."
@ -1044,7 +1044,7 @@ module.exports = nativeDescription: "Ελληνικά", englishDescription: "Gre
create_system_title: "Δημιουργία Νέου Συστήματος"
new_component_title: "Δημιουργία Νέου Δομ. Συστατικού"
new_component_field_system: "Σύστημα"
new_article_title: "Δημιουργία Νέου Άρθροτ"
new_article_title: "Δημιουργία Νέου Άρθρου"
new_thang_title: "Δημιουργία Νέου Τύπου Thang"
new_level_title: "Δημιουργία Νέου Επιπέδου"
new_article_title_login: "Συνδέσου για να Δημιουργήσεις ένα Νέο Άρθρο"
@ -1460,9 +1460,9 @@ module.exports = nativeDescription: "Ελληνικά", englishDescription: "Gre
ladder_prizes:
title: "Βραβεία Τουρνουά" # This section was for an old tournament and doesn't need new translations now.
# blurb_1: "These prizes will be awarded according to"
# blurb_2: "the tournament rules"
# blurb_3: "to the top human and ogre players."
blurb_1: "Tα βραβεία θα απονεμηθούν σύμφωνα με"
blurb_2: "κανόνες του τουρνουά"
blurb_3: "στους κορυφαίους παίκτες των ανθρώπων και των ogres."
blurb_4: "Δύο ομάδες σημαίνει διπλά βραβεία!"
blurb_5: "(Θα υπάρχουν δύο 1οι νικητές, δύο δεύτεροι νικητές, κλπ.)"
rank: "Θέση"
@ -1489,8 +1489,8 @@ module.exports = nativeDescription: "Ελληνικά", englishDescription: "Gre
looking_for: "Αναζήτηση για:"
last_updated: "Τελευταία ενημερώθηκε:"
contact: "Επικοινωνία"
# active: "Looking for interview offers now"
# inactive: "Not looking for offers right now"
active: "Επιθυμώ προσφορές για συνέντευξη"
inactive: "Δεν επιθυμώ προσφορές για συνέντευξη αυτή την περίοδο"
complete: "Ολοκληρωμένο"
next: "Επόμενο"
next_city: "πόλη;"
@ -1498,8 +1498,8 @@ module.exports = nativeDescription: "Ελληνικά", englishDescription: "Gre
next_name: "όνομα;"
next_short_description: "γράψε μία μικρή περιγραφή."
next_long_description: "περιέγραψε την επιθυμητή σου θέση."
# next_skills: "list at least five skills."
# next_work: "chronicle your work history."
next_skills: "κατόνομασε τουλάχιστον πέντε δεξιότητες."
next_work: "Γράψε το ιστορικό σου στην αγορά εργασίας."
# next_education: "recount your educational ordeals."
# next_projects: "show off up to three projects you've worked on."
# next_links: "add any personal or social links."

View file

@ -17,8 +17,7 @@ database.connect()
UserHandler = require '../server/users/user_handler'
User = require '../server/users/User'
#startDate = new Date 2015, 11, 1
startDate = new Date 2015, 11, 8 # Testing
startDate = new Date 2015, 11, 1
query = dateCreated: {$gt: startDate}, emailLower: {$exists: true}
selection = 'name emailLower schoolName courseInstances clans ageRange dateCreated referrer points lastIP hourOfCode preferredLanguage lastLevel'
@ -33,10 +32,10 @@ nextPrompt = (users, question) ->
return console.log('Done.') or process.exit() unless [userToSchool, suggestions] = findUserToSchool users
question ?= formatSuggestions userToSchool, suggestions
prompt question, (answer) ->
return console.log('Bye.') or process.exit() if answer in ['q', 'quit']
answer = answer.trim()
return console.log('Bye.') or process.exit() if answer in ['q', 'quit']
if answer is ''
users = _.without users, userToSchool
return nextPrompt _.without users, userToSchool
else unless _.isNaN(num = parseInt(answer, 10))
schoolName = if num then suggestions[num - 1]?.schoolName else userToSchool.schoolName
return finalizePrompt userToSchool, suggestions, schoolName, users
@ -45,13 +44,13 @@ nextPrompt = (users, question) ->
return nextPrompt users, "> "
else
return finalizePrompt userToSchool, suggestions, answer, users
nextPrompt users
finalizePrompt = (userToSchool, suggestions, schoolName, users) ->
console.log "Selected schoolName: \"#{schoolName}\""
question = "Also apply this to other users? Ex.: 'all', '0 1 2 5 9-14', 'all but 38 59-65', '0' to just do this one, or blank to retype school name.\n> "
question = "Also apply this to other users? Ex.: 'all', '0 1 2 5 9-14', 'all but 38 59-65', '0' to just do this one, q to quit, or blank to retype school name.\n> "
prompt question, (answer) ->
answer = answer.trim()
return console.log('Bye.') or process.exit() if answer in ['q', 'quit']
if answer is ''
console.log "Should just do", userToSchool._id, userToSchool.emailLower, userToSchool.schoolName
targets = [userToSchool]
@ -69,8 +68,8 @@ finalizePrompt = (userToSchool, suggestions, schoolName, users) ->
numbers = findNumbers answer, suggestions.length
targets = _.filter ((if number then suggestions[number - 1].user else userToSchool) for number in numbers)
console.log "Doing #{targets.length} users for #{numbers}..."
#User.update {_id: {$in: (_.map targets, '_id')}}, {schoolName: schoolName}, {multi: true}, (err, result) ->
User.update {_id: {$in: []}}, {schoolName: schoolName}, {multi: true}, (err, result) ->
User.update {_id: {$in: (_.map targets, '_id')}}, {schoolName: schoolName}, {multi: true}, (err, result) ->
#User.update {_id: {$in: []}}, {schoolName: schoolName}, {multi: true}, (err, result) ->
if err
console.error "Ran into error doing the save:", err
return finalizePrompt userToSchool, suggestions, schoolName, users
@ -92,11 +91,11 @@ findNumbers = (answer, max) ->
numbers
formatUser = (user) ->
# TODO: replace date string with relative time since signup compared to target user
# TODO: replace date string with relative time since signup compared to target user, and actually make suggestions based on students that signed up at almost the same time
_.values(_.pick(user, ['name', 'emailLower', 'ageRange', 'dateCreated', 'lastLevel', 'points', 'referrer', 'hourOfCode'])).join(' ')
formatSuggestions = (userToSchool, suggestions) ->
suggestionPrompts = ("#{_.str.rpad(i + 1, 3)} #{_.str.rpad(s.schoolName, 50)} #{s.reasons.join(' + ')}\tfrom user: #{formatUser(s.user)}" for s, i in suggestions).join('\n')
suggestionPrompts = ("#{_.str.rpad(i + 1, 3)} #{_.str.rpad(s.schoolName, 50)} #{s.reasons.length} #{if s.reasons.length > 1 then 'Matches' else 'Match'}: #{s.reasons.join(', ')}\tfrom user: #{formatUser(s.user)}" for s, i in suggestions).join('\n')
"""
What should the school for this user be?
0 #{_.str.rpad(userToSchool.schoolName, 50)} #{formatUser(userToSchool)}
@ -107,35 +106,60 @@ formatSuggestions = (userToSchool, suggestions) ->
findUserToSchool = (users) ->
# We find the top user from the top group that we can make the most reasoned suggestions about what the school name would be.
# TODO: don't show users where everyone in the suggestion already has the same school (because we have already done this group)
[bestTarget, bestTargetSuggestions, mostReasons] = [null, [], 0]
for field, groups of topGroups
largestGroup = groups[0]
target = userCategories[field][largestGroup][0]
suggestions = findSuggestions target
reasons = _.reduce suggestions, ((sum, suggestion) -> sum + (if suggestion.schoolName then suggestion.reasons.length else 0)), 0
if reasons > mostReasons
bestTarget = target
bestTargetSuggestions = suggestions
mostReasons = reasons
for nextLargestGroup in groups
possibleTargets = userCategories[field][nextLargestGroup]
schoolNames = _.uniq possibleTargets, 'schoolName'
# TODO: better method to avoid showing users where everyone in the suggestion already has the same school (because we have already done this group)
for schoolName in schoolNames
if _.filter(possibleTargets, schoolName: schoolName).length > 0.5 * possibleTargets.length
alreadyDone = true
continue if alreadyDone
nSamples = Math.min 15, Math.max(4, Math.floor possibleTargets.length / 20)
console.log 'Checking', nSamples, 'samples of', possibleTargets.length, 'players in the biggest', field, 'group:', nextLargestGroup
for i in [0 ... nSamples]
target = possibleTargets[Math.floor i * possibleTargets.length / (nSamples + 1)]
suggestions = findSuggestions target
reasons = _.reduce suggestions, ((sum, suggestion) ->
for suggestion in suggestions
for reason in suggestion.reasons
sum += switch reason
when 'Course instances' then 50
when 'IP' then 40
when 'Name' then 30
when 'Referrer' then 20
when 'Domain' then (if getDomain(target) is 'cps.edu' then 1 else 10)
when 'Clans' then 0.1
sum
), 0
if reasons > mostReasons
bestTarget = target
bestTargetSuggestions = suggestions
mostReasons = reasons
break
return [bestTarget, bestTargetSuggestions]
findSuggestions = (target) ->
# Look for other users with the same IP, course instances, clans, or similar school names or non-common shared email domains.
suggestions = []
t0 = new Date()
console.log ' Checking suggestions for', target.emailLower, target.schoolName, (new Date()) - t0
if target.lastIP
for otherUser in userCategories.lastIP[target.lastIP] when otherUser isnt target
suggestions.push schoolName: otherUser.schoolName, reasons: ["IP match"], user: otherUser
for otherUser in (userCategories.lastIP[target.lastIP] ? []) when otherUser isnt target
suggestions.push schoolName: otherUser.schoolName, reasons: ['IP'], user: otherUser
for leagueType in ['courseInstances', 'clans']
console.log ' Now checking', leagueType, (new Date()) - t0
if target[leagueType]?.length
for league in target[leagueType]
for otherUser in userCategories[leagueType][league] when otherUser isnt target
reason = "#{_.str.humanize(leagueType)} match"
for otherUser in (userCategories[leagueType][league] ? []) when otherUser isnt target
reason = _.str.humanize(leagueType)
if existingSuggestion = _.find(suggestions, user: otherUser)
existingSuggestion.reasons.push reason
else
suggestions.push schoolName: otherUser.schoolName, reasons: [reason], user: otherUser
if target.schoolName?.length > 5
console.log ' Now checking schoolName', (new Date()) - t0
nameMatches = []
for otherSchoolName in topGroups.schoolName
score = stringScore otherSchoolName, target.schoolName, 0.8
@ -143,20 +167,31 @@ findSuggestions = (target) ->
nameMatches.push schoolName: otherSchoolName, score: score
nameMatches = (match.schoolName for match in (_.sortBy nameMatches, (match) -> -match.score))
for match in nameMatches.slice(0, 10)
reason = "Name match"
for otherUser in userCategories.schoolName[match] when otherUser isnt target
reason = "Name"
for otherUser in (userCategories.schoolName[match] ? []) when otherUser isnt target
if existingSuggestion = _.find(suggestions, user: otherUser)
existingSuggestion.reasons.push reason
else
suggestions.push schoolName: match, reasons: [reason], user: otherUser
console.log ' Now checking domain', (new Date()) - t0
if domain = getDomain target
for otherUser in userCategories.domain[domain] when otherUser isnt target
reason = "Domain match"
for otherUser in (userCategories.domain[domain] ? []) when otherUser isnt target
reason = "Domain"
if existingSuggestion = _.find(suggestions, user: otherUser)
existingSuggestion.reasons.push reason
else
suggestions.push schoolName: otherUser.schoolName, reasons: [reason], user: otherUser
return _.uniq suggestions, 'user'
console.log ' Now checking referrer', (new Date()) - t0
if referrer = getReferrer target
for otherUser in (userCategories.referrer[referrer] ? []) when otherUser isnt target
reason = "Referrer"
if existingSuggestion = _.find(suggestions, user: otherUser)
existingSuggestion.reasons.push reason
else
suggestions.push schoolName: otherUser.schoolName, reasons: [reason], user: otherUser
suggestions = _.sortBy suggestions, 'schoolName'
suggestions = _.sortBy suggestions, (s) -> -s.reasons.length
return suggestions
userCategories = {}
topGroups = {}
@ -165,7 +200,7 @@ usersCategorized = {}
sortUsers = (users) ->
users = _.sortBy users, (u) -> -u.points
users = _.sortBy users, ['schoolName', 'lastIP']
for field in ['courseInstances', 'lastIP', 'schoolName', 'domain', 'clans']
for field in ['courseInstances', 'lastIP', 'schoolName', 'domain', 'clans', 'referrer']
userCategories[field] = categorizeUsers users, field
topGroups[field] = _.sortBy _.keys(userCategories[field]), (key) -> -userCategories[field][key].length
topGroups[field] = (group for group in topGroups[field] when 2 < userCategories[field][group].length < (if field is 'clans' then 30 else 5000))
@ -175,6 +210,8 @@ categorizeUsers = (users, field) ->
for user in users
if field is 'domain'
value = getDomain user
else if field is 'referrer'
value = getReferrer user
else
value = user[field]
continue unless value
@ -185,13 +222,27 @@ categorizeUsers = (users, field) ->
categories[value].push user
categories
typoCache = {}
getDomain = (user) ->
domain = user.emailLower.split('@')[1]
return null unless domain = user.emailLower.split('@')[1]
return null if commonEmailDomainMap[domain]
typo = typoCache[domain]
return null if typo
return domain if typo is false
typo = _.find commonEmailDomains, (commonDomain) -> stringScore(commonDomain, domain, 0.8) > 0.9
typoCache[domain] = Boolean(typo)
return null if typo
domain
commonReferrersRegex = /(google|bing\.|yahoo|duckduckgo|jobs\.lever|code\.org|twitter|facebook|dollarclick|stumbleupon|vk\.com|playpcesor|reddit|lifehacker|favorite|bnext|freelance|taringa|blogthinkbig|graphism|inside\.com|korben|habrahabr|iplaysoft|geekbrains|playground|ycombinator|github\.com)/
getReferrer = (user) ->
return null unless referrer = user.referrer?.toLowerCase().trim()
referrer = referrer.replace /^https?:\/\//, ''
return null if commonReferrersRegex.test referrer
return classCode if classCode = referrer.match(/\?_cc=(\S+)/)?[1]
return null if /codecombat/.test referrer
referrer
# https://github.com/joshaven/string_score
stringScore = (_a, word, fuzziness) ->
return 1 if word is _a

View file

@ -3,6 +3,7 @@ async = require 'async'
bayes = new (require 'bayesian-battle')()
LevelSession = require '../../levels/sessions/LevelSession'
User = require '../../users/User'
perfmon = require '../../commons/perfmon'
SIMULATOR_VERSION = 3
@ -199,6 +200,7 @@ module.exports.updateUserSimulationCounts = (reqUserID, callback) ->
incrementUserSimulationCount reqUserID, 'simulatedBy', (err) =>
if err? then return callback err
#console.log 'Incremented user simulation count!'
perfmon.client.increment 'simulations'
unless @isRandomMatch
incrementUserSimulationCount @levelSession.creator, 'simulatedFor', callback
else