diff --git a/app/lib/surface/LayerAdapter.coffee b/app/lib/surface/LayerAdapter.coffee index e867b65eb..b295dcd01 100644 --- a/app/lib/surface/LayerAdapter.coffee +++ b/app/lib/surface/LayerAdapter.coffee @@ -42,7 +42,7 @@ module.exports = LayerAdapter = class LayerAdapter extends CocoClass buildAutomatically: true buildAsync: true resolutionFactor: SPRITE_RESOLUTION_FACTOR - defaultActions: ['idle', 'die', 'move', 'move', 'attack'] + defaultActions: ['idle', 'die', 'move', 'attack'] numThingsLoading: 0 lanks: null spriteSheet: null @@ -147,9 +147,6 @@ module.exports = LayerAdapter = class LayerAdapter extends CocoClass #- Adding, removing children for WebGL layers. addLank: (lank) -> - # TODO: Move this into the production DB rather than setting it dynamically. - if lank.thangType?.get('name') is 'Highlight' - lank.thangType.set('spriteType', 'segmented') lank.options.resolutionFactor = @resolutionFactor lank.layer = @ diff --git a/app/locale/pt-BR.coffee b/app/locale/pt-BR.coffee index e00828b4f..26b3a4328 100644 --- a/app/locale/pt-BR.coffee +++ b/app/locale/pt-BR.coffee @@ -99,13 +99,13 @@ module.exports = nativeDescription: "Português do Brasil", englishDescription: authenticate_gplus: "Autenticar com G+" load_profile: "Carregar Perfil do G+" finishing: "Terminando" - sign_in_with_facebook: "Conectar com Facebook" - sign_in_with_gplus: "Conectar com G+" + sign_in_with_facebook: "Conectar com o Facebook" + sign_in_with_gplus: "Conectar com o G+" signup_switch: "Deseja Criar uma Conta?" signup: email_announcements: "Receber notícias por email." - creating: "Criando a nova conta..." + creating: "Criando uma nova conta..." sign_up: "Criar conta" log_in: "Entre com a senha" social_signup: "Ou, você pode fazer login pelo Facebook ou G+:" @@ -137,7 +137,7 @@ module.exports = nativeDescription: "Português do Brasil", englishDescription: publish: "Publicar" create: "Criar" manual: "Manual" - fork: "Fork" + fork: "Fork" # When used as a verb, like "To fork a repository" play: "Jogar" # When used as an action verb, like "Play next level" retry: "Tente novamente" actions: "Ações" @@ -239,7 +239,7 @@ module.exports = nativeDescription: "Português do Brasil", englishDescription: victory: "Vitória" victory_title_prefix: " Vitória " victory_title_suffix: " Completado!" - victory_sign_up: "Assine para atualizações" + victory_sign_up: "Assine para receber atualizações" victory_sign_up_poke: "Quer receber as últimas novidades por email? Crie uma conta grátis e nós o manteremos informado!" victory_rate_the_level: "Avalie o estágio: " # Only in old-style levels. victory_return_to_ladder: "Retornar para a progressão" @@ -251,7 +251,7 @@ module.exports = nativeDescription: "Português do Brasil", englishDescription: victory_hour_of_code_done: "Terminou?" victory_hour_of_code_done_yes: "Sim, eu terminei minha Hora da Programação!" victory_experience_gained: "XP ganho" - victory_gems_gained: "Gems ganhas" + victory_gems_gained: "Gemas ganhas" victory_new_item: "Novo item" victory_viking_code_school: "Pelas barbas do profeta, esse foi um nível difícil! Se você ainda não é um desenvolvedor de software, você deveria ser. Você acaba de ser priorizado para aceitação na Viking Code School, onde você pode aprender mais e se tornar um desenvolvedor web profissional em 14 semanas." victory_become_a_viking: "Torne-se um viking" @@ -288,7 +288,7 @@ module.exports = nativeDescription: "Português do Brasil", englishDescription: infinite_loop_title: "Loop Infinito Detectado" infinite_loop_description: "O código inicial para construir o mundo nunca parou de rodar. Talvez seja muito lento ou tenha um loop infinito. Ou talvez tenha um bug. Você pode tentar rodar este código novamente ou resetá-lo para o estado inicial. Se isto não consertá-lo, avise-nos por favor." check_dev_console: "Você também pode abrir o terminal do desenvolvedor para ver o que pode estar dando errado." - check_dev_console_link: "(instruções)" + check_dev_console_link: "(Instruções)" infinite_loop_try_again: "Tentar novamente" infinite_loop_reset_level: "Resetar nível" infinite_loop_comment_out: "Comentar Meu Código" @@ -296,7 +296,7 @@ module.exports = nativeDescription: "Português do Brasil", englishDescription: tip_scrub_shortcut: "Ctrl+[ e Ctrl+] rebobina e avança." # {change} tip_guide_exists: "Clique no guia no topo da página para informações úteis." tip_open_source: "CodeCombat é 100% código aberto!" - tip_tell_friends: "Está gostando de CodeCombate? Dibulgue para os seus amigos!" + tip_tell_friends: "Está gostando de CodeCombate? Divulgue para os seus amigos!" tip_beta_launch: "CodeCombat lançou sua versão beta em outubro de 2013." tip_think_solution: "Pense na solução, não no problema." tip_theory_practice: "Na teoria, não existe diferença entre teoria e prática. Mas, na prática, há. - Yogi Berra" @@ -582,15 +582,15 @@ module.exports = nativeDescription: "Português do Brasil", englishDescription: press_paragraph_1_link: "Midia Kit" press_paragraph_1_suffix: ". Todas as logomarcas e imagens podem ser usadas sem nos contactar previamente." team: "Time" - george_title: "CEO" # {change} + george_title: "Cofundador" george_blurb: "Administrador" - scott_title: "Programador" # {change} + scott_title: "Cofundador" scott_blurb: "O Sensato" - nick_title: "Programador" # {change} + nick_title: "Cofundador" nick_blurb: "Guru Motivacional" michael_title: "Programador" michael_blurb: "Administrador de Sistemas" - matt_title: "Programador" # {change} + matt_title: "Cofundador" matt_blurb: "O Ciclista" cat_title: "Chefe Artesão" cat_blurb: "Corta-vento" diff --git a/app/schemas/models/level.coffee b/app/schemas/models/level.coffee index 291904e25..7872e0ab0 100644 --- a/app/schemas/models/level.coffee +++ b/app/schemas/models/level.coffee @@ -29,8 +29,8 @@ defaultTasks = [ 'Release to adventurers via MailChimp.' 'Write the description.' - 'Translate the sample code comments.' - 'Add Io/Clojure/Lua/CoffeeScript.' + 'Add i18n field for the sample code comments.' + 'Add Clojure/Lua/CoffeeScript.' 'Write the guide.' 'Write a loading tip, if needed.' 'Click the Populate i18n button.' @@ -40,7 +40,6 @@ defaultTasks = [ 'Release to everyone via MailChimp.' 'Check completion/engagement/problem analytics.' - 'Do any custom scripting, if needed.' 'Do thorough set decoration.' 'Add a walkthrough video.' ] diff --git a/app/views/ladder/LadderPlayModal.coffee b/app/views/ladder/LadderPlayModal.coffee index 3a729cb9f..473843792 100644 --- a/app/views/ladder/LadderPlayModal.coffee +++ b/app/views/ladder/LadderPlayModal.coffee @@ -101,7 +101,7 @@ module.exports = class LadderPlayModal extends ModalView {id: 'coffeescript', name: 'CoffeeScript (Experimental)'} {id: 'clojure', name: 'Clojure (Experimental)'} {id: 'lua', name: 'Lua'} - {id: 'io', name: 'Io (Experimental)'} + #{id: 'io', name: 'Io (Experimental)'} ] ctx.league = @options.league teamsList = teamDataFromLevel @level diff --git a/app/views/play/common/LadderSubmissionView.coffee b/app/views/play/common/LadderSubmissionView.coffee index fa31ba7e6..e22ba3854 100644 --- a/app/views/play/common/LadderSubmissionView.coffee +++ b/app/views/play/common/LadderSubmissionView.coffee @@ -1,6 +1,7 @@ CocoView = require 'views/core/CocoView' template = require 'templates/play/common/ladder_submission' {createAetherOptions} = require 'lib/aether_utils' +LevelSession = require 'models/LevelSession' module.exports = class LadderSubmissionView extends CocoView className: 'ladder-submission-view' @@ -78,14 +79,20 @@ module.exports = class LadderSubmissionView extends CocoView # Also submit the mirrorSession after the main session submits successfully. mirrorAjaxData = _.clone ajaxData mirrorAjaxData.session = @mirrorSession.id + mirrorCode = @mirrorSession.get('code') if @session.get('team') is 'humans' mirrorAjaxData.transpiledCode = 'hero-placeholder-1': transpiledCode['hero-placeholder'] + mirrorCode['hero-placeholder-1'] = @session.get('code')['hero-placeholder'] else mirrorAjaxData.transpiledCode = 'hero-placeholder': transpiledCode['hero-placeholder-1'] + mirrorCode['hero-placeholder'] = @session.get('code')['hero-placeholder-1'] mirrorAjaxOptions = _.clone ajaxOptions mirrorAjaxOptions.data = mirrorAjaxData - ajaxOptions.success = -> - $.ajax '/queue/scoring', mirrorAjaxOptions + ajaxOptions.success = => + patch = code: mirrorCode, codeLanguage: @session.get('codeLanguage'), submittedCodeLanguage: @session.get('submittedCodeLanguage') + tempSession = new LevelSession _id: @mirrorSession.id + tempSession.save patch, patch: true, type: 'PUT', success: -> + $.ajax '/queue/scoring', mirrorAjaxOptions $.ajax '/queue/scoring', ajaxOptions diff --git a/app/views/play/level/tome/CastButtonView.coffee b/app/views/play/level/tome/CastButtonView.coffee index c0cdb423b..bba485b16 100644 --- a/app/views/play/level/tome/CastButtonView.coffee +++ b/app/views/play/level/tome/CastButtonView.coffee @@ -101,7 +101,9 @@ module.exports = class CastButtonView extends CocoView @casting = false if @hasCastOnce # Don't play this sound the first time @playSound 'cast-end', 0.5 - _.delay (=> @ladderSubmissionView?.rankSession()), 1000 if @ladderSubmissionView + myHeroID = if me.team is 'ogres' then 'Hero Placeholder 1' else 'Hero Placeholder' + if @ladderSubmissionView and not e.world.thangMap[myHeroID]?.errorsOut + _.delay (=> @ladderSubmissionView?.rankSession()), 1000 if @ladderSubmissionView @hasCastOnce = true @updateCastButton() @world = e.world diff --git a/app/views/play/level/tome/Spell.coffee b/app/views/play/level/tome/Spell.coffee index 7e08eafa8..bfe920e48 100644 --- a/app/views/play/level/tome/Spell.coffee +++ b/app/views/play/level/tome/Spell.coffee @@ -36,7 +36,7 @@ module.exports = class Spell else @setLanguage 'javascript' @useTranspiledCode = @shouldUseTranspiledCode() - console.log 'Spell', @spellKey, 'is using transpiled code (should only happen if it\'s an enemy/spectate writable method).' if @useTranspiledCode + #console.log 'Spell', @spellKey, 'is using transpiled code (should only happen if it\'s an enemy/spectate writable method).' if @useTranspiledCode @source = @originalSource @parameters = p.parameters diff --git a/app/views/play/modal/PlayHeroesModal.coffee b/app/views/play/modal/PlayHeroesModal.coffee index 0b104f575..382716511 100644 --- a/app/views/play/modal/PlayHeroesModal.coffee +++ b/app/views/play/modal/PlayHeroesModal.coffee @@ -113,7 +113,7 @@ module.exports = class PlayHeroesModal extends ModalView {id: 'coffeescript', name: "CoffeeScript (#{$.i18n.t('choose_hero.experimental')})"} {id: 'clojure', name: "Clojure (#{$.i18n.t('choose_hero.experimental')})"} {id: 'lua', name: 'Lua'} - {id: 'io', name: "Io (#{$.i18n.t('choose_hero.experimental')})"} + #{id: 'io', name: "Io (#{$.i18n.t('choose_hero.experimental')})"} ] onHeroChanged: (e) -> diff --git a/scripts/helpScoutToCloseIO.js b/scripts/helpScoutToCloseIO.js new file mode 100644 index 000000000..0e233cf9d --- /dev/null +++ b/scripts/helpScoutToCloseIO.js @@ -0,0 +1,221 @@ +// Copy sales leads from HelpScout to Close.io based on HelpScout tags + +// TODO: handle leads with multiple email addresses +// TODO: some feedback email threads get broken up in Close.io + +var CloseIo = function (apiKey, mailboxEmail) { + this.apiKey = apiKey; + this.mailboxEmail = mailboxEmail; +} +CloseIo.prototype.createLead = function (conversation, done) { + console.log('Close.Io - Creating lead for', conversation.customer.email); + var data = { + contacts: [{ + emails: [{ + email: conversation.customer.email, + type: 'office' + }], + name: conversation.customer.firstName + ' ' + conversation.customer.lastName + }] + }; + var options = { + uri: 'https://' + this.apiKey+ ':X@app.close.io/api/v1/lead/', + body: JSON.stringify(data) + }; + request.post(options, function (error, response, body) { + if (error) { + return done(error); + } + return done(null, JSON.parse(body)); + }); +} +CloseIo.prototype.getActivities = function (leadID, done) { + // console.log('Close.Io - Retrieving activities for lead', leadID); + request.get('https://' + this.apiKey + ':X@app.close.io/api/v1/activity/email/?lead_id=' + leadID, function(error, response, body) { + if (error) { + return done(error); + } + return done(null, JSON.parse(body)); + }); +} +CloseIo.prototype.getLead = function (conversation, done) { + // console.log('Close.Io - Retrieving contact', conversation.customer.email); + var uri = 'https://' + this.apiKey + ':X@app.close.io/api/v1/lead/?query=email_address:' + conversation.customer.email; + request.get(uri, (function(error, response, body) { + if (error) return done(error); + var leads = JSON.parse(body); + if (leads.data.length === 1) { + return done(null, leads.data[0]); + } + else if (leads.data.length > 1) { + return done('ERROR: too many leads returned for ' + conversation.customer.email + ' ' + leads.data.length); + } + this.createLead(conversation, (function(error, lead) { + if (error) return done(error); + return done(null, lead); + }).bind(this)); + }).bind(this)); +} +CloseIo.prototype.updateActivity = function (activities, lead, conversation, conversationThread, done) { + // console.log('Close.Io - Updating email thread', conversation.subject); + var data = { + body_html: conversationThread.body, + contact_id: lead.contacts[0].id, + date_created: conversationThread.createdAt, + lead_id: lead.id, + sender: conversationThread.createdBy.email, + _type: 'Email' + } + if (conversation.subject) { + data.subject = conversation.subject; + if (data.subject.substring(0, 4) === 'Re: ') { + data.subject = data.subject.substring(4); + } + } + if (conversationThread.createdBy.email === this.mailboxEmail) { + data.status = 'sent'; + data.to = [conversationThread.customer.email]; + } + else { + data.status = 'inbox'; + data.to = [this.mailboxEmail]; + } + for (var i = 0; i < activities.data.length; i++) { + if (activities.data[i].body_html === data.body_html + && new Date(activities.data[i].date_created).getTime() == new Date(data.date_created).getTime()) { + // console.log('Close.Io - Found existing email', data.subject, data.date_created); + return done(); + } + } + var options = { + uri: 'https://' + this.apiKey + ':X@app.close.io/api/v1/activity/email/', + body: JSON.stringify(data) + }; + request.post(options, function (error, response, body) { + if (error) { + return done(error); + } + return done(); + }); +} +CloseIo.prototype.updateLead = function (conversation, done) { + console.log('Close.Io - Updating lead', conversation.customer.email); + this.getLead(conversation, (function(error, lead) { + if (error) return done(error); + this.updateLeadEmails(lead, conversation, (function(error) { + if (error) { + console.log(error); + return; + } + }).bind(this)); + }).bind(this)); +} +CloseIo.prototype.updateLeadEmails = function (lead, conversation, done) { + console.log('Close.Io - Updating lead emails', lead.display_name, conversation.subject); + if (conversation.type !== 'email') return done(); + this.getActivities(lead.id, (function(error, activities) { + if (error) return done(error); + for (var i = 0; i < conversation.threads.length; i++) { + if (conversation.threads[i].type !== 'message') continue; + if (conversation.threads[i].state !== 'published') continue; + if (!conversation.threads[i].body || conversation.threads[i].body.length === 0) continue; + this.updateActivity(activities, lead, conversation, conversation.threads[i], done); + } + }).bind(this)); +} + +var HelpScout = function (apiKey, mailboxEmails, searchTag) { + this.apiKey = apiKey; + this.mailboxEmails = mailboxEmails; + this.searchTag = searchTag; +} +HelpScout.prototype.getConversation = function (conversationId, done) { + // console.log('HelpScout - Retrieving conversation', conversationId); + request.get('https://' + this.apiKey + ':X@api.helpscout.net/v1/conversations/' + conversationId + '.json', function (error, response, body) { + if (error) return done(error); + var conversation = JSON.parse(body); + return done(null, conversation.item); + }); +} +HelpScout.prototype.getConversations = function (mailboxId, done) { + // console.log('HelpScout - Retrieving conversations for mailbox', mailboxId); + var results = []; + var fetchPage = (function (page) { + // console.error("HelpScout - Fetching conversations page", page); + var uri = 'https://' + this.apiKey + ':X@api.helpscout.net/v1/mailboxes/' + mailboxId + '/conversations.json' + uri += '?page=' + page + '&tag=' + this.searchTag; + request.get(uri, function (error, response, body) { + if (error) return done(error); + var conversations = JSON.parse(body); + results = results.concat(conversations.items); + if (conversations.page < conversations.pages) { + return fetchPage(page + 1); + } + return done(null, results); + }); + }).bind(this); + fetchPage(1); +} +HelpScout.prototype.getMailboxes = function (done) { + // console.log('HelpScout - Retrieving mailboxes'); + var results = []; + request.get('https://' + this.apiKey + ':X@api.helpscout.net/v1/mailboxes.json', (function (error, response, body) { + if (error) return done(error); + var mailboxes = JSON.parse(body); + for (var i = 0 ; i < mailboxes.items.length; i++) { + if (this.mailboxEmails.indexOf(mailboxes.items[i].email) >= 0) { + results.push(mailboxes.items[i]); + } + } + return done(null, results); + }).bind(this)); +} + +// Main program + +if (process.argv.length !== 4) { + log("Usage: node <script> <HelpScout API key> <Close.io API key>"); + process.exit(); +} + +var request = require('request'); +var helpScout = new HelpScout(process.argv[2], ['support@codecombat.com', 'team@codecombat.com'], 'make the sale'); +var closeIo = new CloseIo(process.argv[3], 'matt@codecombat.com'); + +helpScout.getMailboxes(function (error, mailboxes) { + if (error) { + console.log(error); + return; + } + for (var i = 0; i < mailboxes.length; i++) { + var mailbox = mailboxes[i]; + helpScout.getConversations(mailbox.id, function(error, conversations) { + if (error) { + console.log(error); + return; + } + console.log(mailbox.email, 'mailbox has', conversations.length, 'conversations'); + for (var i = 0; i < conversations.length; i++) { + if (conversations[i].type !== 'email') continue; + + if (i > 8) { + console.log('TODO: process all the conversations'); + break; + } + + helpScout.getConversation(conversations[i].id, function(error, conversation) { + if (error) { + console.log(error); + return; + } + closeIo.updateLead(conversation, function(error, lead) { + if (error) { + console.log(error); + return; + } + }); + }); + } + }); + } +}); diff --git a/server/queues/scoring/getTwoGames.coffee b/server/queues/scoring/getTwoGames.coffee index 573506771..a55c3e4f2 100644 --- a/server/queues/scoring/getTwoGames.coffee +++ b/server/queues/scoring/getTwoGames.coffee @@ -17,7 +17,7 @@ sessionSelectionString = 'team totalScore transpiledCode submittedCodeLanguage t sendSessionsResponse = (res) -> (err, sessions) -> if err then return errors.serverError res, "Couldn't get two games to simulate: #{err}" - unless sessions.length is 2 + unless _.filter(sessions).length is 2 console.log 'No games to score.', sessions.length res.send 204, 'No games to score.' return res.end() @@ -110,5 +110,3 @@ findEarliestSubmission = (queryParams, callback) -> return callback err if err result = earliestSubmissionCache[cacheKey] = earliest?.submitDate callback null, result - -