├── eventos ├── .nvmrc ├── assets │ ├── firebase-credentials.private.json.sample │ ├── images │ │ ├── comms.webp │ │ ├── flex.webp │ │ ├── ghost.png │ │ ├── powered.png │ │ ├── segment.webp │ │ ├── testeira.png │ │ ├── twiliodevs.png │ │ ├── sad-robot-coffee.png │ │ ├── twilio-sticker.webp │ │ └── questions_background.png │ ├── files │ │ └── mugs │ │ │ ├── mug_1.png │ │ │ ├── mug_2.png │ │ │ ├── mug_3.png │ │ │ ├── mug_4.png │ │ │ ├── mug_1.jpeg │ │ │ ├── mug_2.jpeg │ │ │ ├── mug_3.jpeg │ │ │ ├── mug_4.jpeg │ │ │ ├── mug_1_square.png │ │ │ ├── mug_2_square.png │ │ │ ├── mug_3_square.png │ │ │ └── mug_4_square.png │ ├── javascript │ │ ├── jsqr │ │ │ ├── decoder │ │ │ │ ├── decodeData │ │ │ │ │ ├── shiftJISTable.d.ts │ │ │ │ │ ├── BitStream.d.ts │ │ │ │ │ └── index.d.ts │ │ │ │ ├── reedsolomon │ │ │ │ │ ├── index.d.ts │ │ │ │ │ ├── GenericGFPoly.d.ts │ │ │ │ │ └── GenericGF.d.ts │ │ │ │ ├── decoder.d.ts │ │ │ │ └── version.d.ts │ │ │ ├── binarizer │ │ │ │ └── index.d.ts │ │ │ ├── extractor │ │ │ │ └── index.d.ts │ │ │ ├── locator │ │ │ │ └── index.d.ts │ │ │ ├── BitMatrix.d.ts │ │ │ └── index.d.ts │ │ ├── coordenador.js │ │ ├── questions.js │ │ ├── tela.js │ │ ├── gerenciar.js │ │ └── orders.js │ ├── css │ │ ├── tela.css │ │ └── orders.css │ ├── questions_screen.html │ └── coordenador.html ├── .env.sample ├── package.json ├── functions │ ├── imprime-networking.protected.js │ ├── verifica-email.protected.js │ ├── vendingmachine-estoque.protected.js │ ├── fotografia-lista.js │ ├── ligacao.protected.js │ ├── verifica-sympla.protected.js │ ├── registro-manual.js │ ├── rdstation-certificado.js │ ├── add-nome.protected.js │ ├── verifica-participante-pontos.protected.js │ ├── send-cidade.protected.js │ ├── send-barista.protected.js │ ├── fotografia-reset.js │ ├── fotografia-chama.js │ ├── util.private.js │ ├── remove-fila-fotografia.protected.js │ ├── verifica-evento-participante.protected.js │ ├── fotografia-cancela.js │ ├── add-optin.protected.js │ ├── add-linkedin.protected.js │ ├── emite-certificado.protected.js │ ├── add-pontos.protected.js │ ├── verifica-sorteio-palavra.protected.js │ ├── fotografia-atende.js │ ├── add-sorteio-palavra.protected.js │ ├── add-fila-fotografia.protected.js │ ├── videomatik-webhook.js │ ├── add-registro.protected.js │ ├── add-palestra.protected.js │ ├── verifica-palavrachave.protected.js │ ├── vendingmachine-resgate.protected.js │ ├── barista-status.js │ ├── executa-comando-sorteio.protected.js │ ├── verifica-palestra.protected.js │ ├── fotografia-finaliza.js │ └── verifica-participante.protected.js ├── .gitignore └── .twilioserverlessrc ├── scripts ├── firebase-credentials.json.sample ├── printers │ ├── assets │ │ ├── demo.png │ │ ├── frente.png │ │ ├── fundo.png │ │ ├── twilio-logo.png │ │ ├── frente_test_2.png │ │ └── devfestbelem2023 │ │ │ └── frente.png │ ├── package.json │ ├── print-canon-selphy.js │ └── print-thermal.js ├── .env.sample ├── package.json ├── util.js ├── airtable.js ├── importa-csv-email.js └── divoom.js ├── README.md └── LICENSE /eventos/.nvmrc: -------------------------------------------------------------------------------- 1 | 14 -------------------------------------------------------------------------------- /scripts/firebase-credentials.json.sample: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /eventos/assets/firebase-credentials.private.json.sample: -------------------------------------------------------------------------------- 1 | { 2 | 3 | } -------------------------------------------------------------------------------- /eventos/assets/images/comms.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luisleao/twilio_whatsapp_eventos/HEAD/eventos/assets/images/comms.webp -------------------------------------------------------------------------------- /eventos/assets/images/flex.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luisleao/twilio_whatsapp_eventos/HEAD/eventos/assets/images/flex.webp -------------------------------------------------------------------------------- /eventos/assets/images/ghost.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luisleao/twilio_whatsapp_eventos/HEAD/eventos/assets/images/ghost.png -------------------------------------------------------------------------------- /scripts/printers/assets/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luisleao/twilio_whatsapp_eventos/HEAD/scripts/printers/assets/demo.png -------------------------------------------------------------------------------- /eventos/assets/files/mugs/mug_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luisleao/twilio_whatsapp_eventos/HEAD/eventos/assets/files/mugs/mug_1.png -------------------------------------------------------------------------------- /eventos/assets/files/mugs/mug_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luisleao/twilio_whatsapp_eventos/HEAD/eventos/assets/files/mugs/mug_2.png -------------------------------------------------------------------------------- /eventos/assets/files/mugs/mug_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luisleao/twilio_whatsapp_eventos/HEAD/eventos/assets/files/mugs/mug_3.png -------------------------------------------------------------------------------- /eventos/assets/files/mugs/mug_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luisleao/twilio_whatsapp_eventos/HEAD/eventos/assets/files/mugs/mug_4.png -------------------------------------------------------------------------------- /eventos/assets/images/powered.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luisleao/twilio_whatsapp_eventos/HEAD/eventos/assets/images/powered.png -------------------------------------------------------------------------------- /eventos/assets/images/segment.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luisleao/twilio_whatsapp_eventos/HEAD/eventos/assets/images/segment.webp -------------------------------------------------------------------------------- /eventos/assets/images/testeira.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luisleao/twilio_whatsapp_eventos/HEAD/eventos/assets/images/testeira.png -------------------------------------------------------------------------------- /scripts/printers/assets/frente.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luisleao/twilio_whatsapp_eventos/HEAD/scripts/printers/assets/frente.png -------------------------------------------------------------------------------- /scripts/printers/assets/fundo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luisleao/twilio_whatsapp_eventos/HEAD/scripts/printers/assets/fundo.png -------------------------------------------------------------------------------- /eventos/assets/files/mugs/mug_1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luisleao/twilio_whatsapp_eventos/HEAD/eventos/assets/files/mugs/mug_1.jpeg -------------------------------------------------------------------------------- /eventos/assets/files/mugs/mug_2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luisleao/twilio_whatsapp_eventos/HEAD/eventos/assets/files/mugs/mug_2.jpeg -------------------------------------------------------------------------------- /eventos/assets/files/mugs/mug_3.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luisleao/twilio_whatsapp_eventos/HEAD/eventos/assets/files/mugs/mug_3.jpeg -------------------------------------------------------------------------------- /eventos/assets/files/mugs/mug_4.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luisleao/twilio_whatsapp_eventos/HEAD/eventos/assets/files/mugs/mug_4.jpeg -------------------------------------------------------------------------------- /eventos/assets/images/twiliodevs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luisleao/twilio_whatsapp_eventos/HEAD/eventos/assets/images/twiliodevs.png -------------------------------------------------------------------------------- /eventos/assets/javascript/jsqr/decoder/decodeData/shiftJISTable.d.ts: -------------------------------------------------------------------------------- 1 | export declare const shiftJISTable: { 2 | [key: number]: number; 3 | }; 4 | -------------------------------------------------------------------------------- /eventos/assets/javascript/jsqr/decoder/reedsolomon/index.d.ts: -------------------------------------------------------------------------------- 1 | export declare function decode(bytes: number[], twoS: number): Uint8ClampedArray; 2 | -------------------------------------------------------------------------------- /scripts/printers/assets/twilio-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luisleao/twilio_whatsapp_eventos/HEAD/scripts/printers/assets/twilio-logo.png -------------------------------------------------------------------------------- /eventos/assets/files/mugs/mug_1_square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luisleao/twilio_whatsapp_eventos/HEAD/eventos/assets/files/mugs/mug_1_square.png -------------------------------------------------------------------------------- /eventos/assets/files/mugs/mug_2_square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luisleao/twilio_whatsapp_eventos/HEAD/eventos/assets/files/mugs/mug_2_square.png -------------------------------------------------------------------------------- /eventos/assets/files/mugs/mug_3_square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luisleao/twilio_whatsapp_eventos/HEAD/eventos/assets/files/mugs/mug_3_square.png -------------------------------------------------------------------------------- /eventos/assets/files/mugs/mug_4_square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luisleao/twilio_whatsapp_eventos/HEAD/eventos/assets/files/mugs/mug_4_square.png -------------------------------------------------------------------------------- /eventos/assets/images/sad-robot-coffee.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luisleao/twilio_whatsapp_eventos/HEAD/eventos/assets/images/sad-robot-coffee.png -------------------------------------------------------------------------------- /eventos/assets/images/twilio-sticker.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luisleao/twilio_whatsapp_eventos/HEAD/eventos/assets/images/twilio-sticker.webp -------------------------------------------------------------------------------- /scripts/printers/assets/frente_test_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luisleao/twilio_whatsapp_eventos/HEAD/scripts/printers/assets/frente_test_2.png -------------------------------------------------------------------------------- /eventos/assets/images/questions_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luisleao/twilio_whatsapp_eventos/HEAD/eventos/assets/images/questions_background.png -------------------------------------------------------------------------------- /scripts/printers/assets/devfestbelem2023/frente.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luisleao/twilio_whatsapp_eventos/HEAD/scripts/printers/assets/devfestbelem2023/frente.png -------------------------------------------------------------------------------- /eventos/assets/javascript/jsqr/decoder/decoder.d.ts: -------------------------------------------------------------------------------- 1 | import { BitMatrix } from "../BitMatrix"; 2 | import { DecodedQR } from "./decodeData"; 3 | export declare function decode(matrix: BitMatrix): DecodedQR; 4 | -------------------------------------------------------------------------------- /eventos/assets/javascript/jsqr/decoder/decodeData/BitStream.d.ts: -------------------------------------------------------------------------------- 1 | export declare class BitStream { 2 | private bytes; 3 | private byteOffset; 4 | private bitOffset; 5 | constructor(bytes: Uint8ClampedArray); 6 | readBits(numBits: number): number; 7 | available(): number; 8 | } 9 | -------------------------------------------------------------------------------- /scripts/.env.sample: -------------------------------------------------------------------------------- 1 | TWILIO_ACCOUNT_ID= 2 | TWILIO_AUTH_TOKEN= 3 | EVENT_ID="NOME_EVENTO_NO_FIRESTORE" 4 | GOOGLE_APPLICATION_CREDENTIALS="SEU_ARQUIVO_CREDENCIAL_SERVICO_GOOGLE_CLOUD" 5 | NODE_TLS_REJECT_UNAUTHORIZED=0 6 | MY_PHONE_NUMBER="SEU_NUMERO_TELEFONE" 7 | 8 | 9 | 10 | FOTOGRAFIA_PASTA_RAIZ= 11 | FOTOGRAFIA_BUCKET_PADRAO= -------------------------------------------------------------------------------- /eventos/assets/javascript/jsqr/binarizer/index.d.ts: -------------------------------------------------------------------------------- 1 | import { BitMatrix } from "../BitMatrix"; 2 | export declare function binarize(data: Uint8ClampedArray, width: number, height: number, returnInverted: boolean): { 3 | binarized: BitMatrix; 4 | inverted: BitMatrix; 5 | } | { 6 | binarized: BitMatrix; 7 | inverted?: undefined; 8 | }; 9 | -------------------------------------------------------------------------------- /eventos/assets/javascript/jsqr/extractor/index.d.ts: -------------------------------------------------------------------------------- 1 | import { BitMatrix } from "../BitMatrix"; 2 | import { QRLocation } from "../locator"; 3 | export declare function extract(image: BitMatrix, location: QRLocation): { 4 | matrix: BitMatrix; 5 | mappingFunction: (x: number, y: number) => { 6 | x: number; 7 | y: number; 8 | }; 9 | }; 10 | -------------------------------------------------------------------------------- /eventos/assets/javascript/jsqr/locator/index.d.ts: -------------------------------------------------------------------------------- 1 | import { BitMatrix } from "../BitMatrix"; 2 | export interface Point { 3 | x: number; 4 | y: number; 5 | } 6 | export interface QRLocation { 7 | topRight: Point; 8 | bottomLeft: Point; 9 | topLeft: Point; 10 | alignmentPattern: Point; 11 | dimension: number; 12 | } 13 | export declare function locate(matrix: BitMatrix): QRLocation[]; 14 | -------------------------------------------------------------------------------- /eventos/assets/javascript/jsqr/decoder/version.d.ts: -------------------------------------------------------------------------------- 1 | export interface Version { 2 | infoBits: number; 3 | versionNumber: number; 4 | alignmentPatternCenters: number[]; 5 | errorCorrectionLevels: Array<{ 6 | ecCodewordsPerBlock: number; 7 | ecBlocks: Array<{ 8 | numBlocks: number; 9 | dataCodewordsPerBlock: number; 10 | }>; 11 | }>; 12 | } 13 | export declare const VERSIONS: Version[]; 14 | -------------------------------------------------------------------------------- /eventos/assets/javascript/jsqr/BitMatrix.d.ts: -------------------------------------------------------------------------------- 1 | export declare class BitMatrix { 2 | static createEmpty(width: number, height: number): BitMatrix; 3 | width: number; 4 | height: number; 5 | private data; 6 | constructor(data: Uint8ClampedArray, width: number); 7 | get(x: number, y: number): boolean; 8 | set(x: number, y: number, v: boolean): void; 9 | setRegion(left: number, top: number, width: number, height: number, v: boolean): void; 10 | } 11 | -------------------------------------------------------------------------------- /scripts/printers/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "canon-printer", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1" 7 | }, 8 | "keywords": [], 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "axios": "1.7.2", 13 | "blob-stream": "0.1.3", 14 | "dotenv": "16.4.5", 15 | "firebase-admin": "11.11.1", 16 | "node-thermal-printer": "4.4.1" 17 | }, 18 | "description": "" 19 | } 20 | -------------------------------------------------------------------------------- /eventos/.env.sample: -------------------------------------------------------------------------------- 1 | 2 | SYMPLA_KEY= 3 | EVENTO_ATUAL= 4 | VIDEOMATIK_API_KEY= 5 | VIDEOMATIK_CALLBACK_URL=https://URL?id={{id}}&participanteId={{participanteId}} 6 | TWILIO_WHATSAPP_NUMBER=+5511987654321 7 | TWILIO_WHATSAPP_NUMBER_TDC=+5511987654321 8 | 9 | 10 | EVENTO_NOME=FrontinSampa 11 | EVENTO_DURACAO=9%20horas 12 | EVENTO_IMAGEM_CERTIFICADO=/certificado-frontinsampa2022.png 13 | 14 | FOTOGRAFIA_PASTA_RAIZ= 15 | FOTOGRAFIA_BUCKET_PADRAO= 16 | VENDINGMACHINE_DEFAULT=VENDING 17 | GAME_TENTATIVAS_MAXIMO=5 18 | LIMITE_RESGATES=5 -------------------------------------------------------------------------------- /scripts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scripts", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "axios": "0.27.2", 14 | "csvtojson": "2.0.10", 15 | "dotenv": "16.0.1", 16 | "dymojs": "1.2.0", 17 | "firebase": "9.9.0", 18 | "firebase-admin": "11.0.0", 19 | "inquirer": "9.0.0", 20 | "sympla": "0.0.3", 21 | "twilio": "3.78.0", 22 | "xml-js": "1.6.11" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /eventos/assets/javascript/jsqr/decoder/reedsolomon/GenericGFPoly.d.ts: -------------------------------------------------------------------------------- 1 | import GenericGF from "./GenericGF"; 2 | export default class GenericGFPoly { 3 | private field; 4 | private coefficients; 5 | constructor(field: GenericGF, coefficients: Uint8ClampedArray); 6 | degree(): number; 7 | isZero(): boolean; 8 | getCoefficient(degree: number): number; 9 | addOrSubtract(other: GenericGFPoly): GenericGFPoly; 10 | multiply(scalar: number): GenericGFPoly; 11 | multiplyPoly(other: GenericGFPoly): GenericGFPoly; 12 | multiplyByMonomial(degree: number, coefficient: number): GenericGFPoly; 13 | evaluateAt(a: number): number; 14 | } 15 | -------------------------------------------------------------------------------- /eventos/assets/javascript/jsqr/decoder/reedsolomon/GenericGF.d.ts: -------------------------------------------------------------------------------- 1 | import GenericGFPoly from "./GenericGFPoly"; 2 | export declare function addOrSubtractGF(a: number, b: number): number; 3 | export default class GenericGF { 4 | primitive: number; 5 | size: number; 6 | generatorBase: number; 7 | zero: GenericGFPoly; 8 | one: GenericGFPoly; 9 | private expTable; 10 | private logTable; 11 | constructor(primitive: number, size: number, genBase: number); 12 | multiply(a: number, b: number): number; 13 | inverse(a: number): number; 14 | buildMonomial(degree: number, coefficient: number): GenericGFPoly; 15 | log(a: number): number; 16 | exp(a: number): number; 17 | } 18 | -------------------------------------------------------------------------------- /eventos/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eventos", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1", 7 | "start": "twilio-run", 8 | "deploy": "twilio-run deploy" 9 | }, 10 | "dependencies": { 11 | "@twilio/runtime-handler": "1.3.0", 12 | "@videomatik/api": "2.0.0", 13 | "airtable": "0.11.6", 14 | "axios": "0.27.2", 15 | "firebase-admin": "11.0.0", 16 | "google-auth-library": "8.2.0", 17 | "googleapis": "105.0.0", 18 | "md5": "2.3.0", 19 | "sympla": "0.0.3", 20 | "twilio": "^4.18.0" 21 | }, 22 | "devDependencies": { 23 | "twilio-run": "^3.4.2" 24 | }, 25 | "engines": { 26 | "node": "14" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /eventos/assets/javascript/jsqr/decoder/decodeData/index.d.ts: -------------------------------------------------------------------------------- 1 | export interface Chunk { 2 | type: Mode; 3 | text: string; 4 | } 5 | export interface ByteChunk { 6 | type: Mode.Byte | Mode.Kanji; 7 | bytes: number[]; 8 | } 9 | export interface ECIChunk { 10 | type: Mode.ECI; 11 | assignmentNumber: number; 12 | } 13 | export declare type Chunks = Array; 14 | export interface DecodedQR { 15 | text: string; 16 | bytes: number[]; 17 | chunks: Chunks; 18 | version: number; 19 | } 20 | export declare enum Mode { 21 | Numeric = "numeric", 22 | Alphanumeric = "alphanumeric", 23 | Byte = "byte", 24 | Kanji = "kanji", 25 | ECI = "eci" 26 | } 27 | export declare function decode(data: Uint8ClampedArray, version: number): DecodedQR; 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ativações da Twilio com WhatsApp em Eventos 2 | 3 | ![Marcas da Twilio e WhatsApp](./eventos/assets/images/powered.png) 4 | 5 | Este repositório contém os códigos das ativações em eventos utilizando a API de WhatsApp da Twilio. 6 | 7 | Caso você tenha interesse em implementar, existem 3 pastas principais: 8 | * `eventos`: corresponde às funções serverless que estão hospedadas na estrutura da Twilio. 9 | * `scripts`: são scripts que utilizamos em uma máquina local para ações que dependem de hardware ou relatórios. Impressão de etiquetas por exemplo e sincronia de dados entre um Airtable com o Firestore. 10 | * `studio`: possui os fluxos de conversação que foram criados utilizando o Twilio Studio. 11 | 12 | 13 | Para executar esta aplicação você deve possuir uma conta da Twilio e um projeto do Firebase da Google. 14 | -------------------------------------------------------------------------------- /eventos/assets/javascript/jsqr/index.d.ts: -------------------------------------------------------------------------------- 1 | import { Chunks } from "./decoder/decodeData"; 2 | import { Point } from "./locator"; 3 | export interface QRCode { 4 | binaryData: number[]; 5 | data: string; 6 | chunks: Chunks; 7 | version: number; 8 | location: { 9 | topRightCorner: Point; 10 | topLeftCorner: Point; 11 | bottomRightCorner: Point; 12 | bottomLeftCorner: Point; 13 | topRightFinderPattern: Point; 14 | topLeftFinderPattern: Point; 15 | bottomLeftFinderPattern: Point; 16 | bottomRightAlignmentPattern?: Point; 17 | }; 18 | } 19 | export interface Options { 20 | inversionAttempts?: "dontInvert" | "onlyInvert" | "attemptBoth" | "invertFirst"; 21 | } 22 | declare function jsQR(data: Uint8ClampedArray, width: number, height: number, providedOptions?: Options): QRCode | null; 23 | export default jsQR; 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Luís Leão 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /eventos/functions/imprime-networking.protected.js: -------------------------------------------------------------------------------- 1 | const admin = require('firebase-admin'); 2 | process.env.GOOGLE_APPLICATION_CREDENTIALS = Runtime.getAssets()['/firebase-credentials.json'].path; 3 | const { escondeNumero, limpaNumero, getDDD, sendNotification, convertNewLine, fillParams } = require(Runtime.getFunctions()['util'].path); 4 | 5 | if (!admin.apps.length) { 6 | admin.initializeApp({}); 7 | } else { 8 | admin.app(); 9 | } 10 | const firestore = admin.firestore(); 11 | const md5 = require('md5'); 12 | 13 | /* 14 | event: evento, token, from, to 15 | */ 16 | exports.handler = async function(context, event, callback) { 17 | let participanteId = await md5(limpaNumero(event.from)); 18 | let idPlayerEvent = await md5(`${event.evento}:${limpaNumero(event.from)}`); 19 | 20 | let id = await firestore.collection('labels') 21 | .add({ 22 | url: `https://wa.me/${limpaNumero(event.to, true)}?text=${idPlayerEvent}`, 23 | participanteId, 24 | idPlayerEvent, 25 | evento: event.evento, 26 | printer: event.token.toUpperCase(), 27 | telefone: escondeNumero(limpaNumero(event.from)), 28 | participanteId: participanteId 29 | }).then(s => { 30 | return s.id 31 | }); 32 | 33 | callback(null, id); 34 | 35 | }; 36 | -------------------------------------------------------------------------------- /eventos/functions/verifica-email.protected.js: -------------------------------------------------------------------------------- 1 | const admin = require('firebase-admin'); 2 | process.env.GOOGLE_APPLICATION_CREDENTIALS = Runtime.getAssets()['/firebase-credentials.json'].path; 3 | 4 | if (!admin.apps.length) { 5 | admin.initializeApp({}); 6 | } else { 7 | admin.app(); 8 | } 9 | const firestore = admin.firestore(); 10 | 11 | 12 | 13 | exports.handler = async function(context, event, callback) { 14 | console.log('email', event.email) 15 | 16 | // verificar se ja usou o codigo 17 | const registroEmail = await firestore.collection('events').doc(event.evento).collection('emails').doc(event.email.toLowerCase()).get(); 18 | if (!registroEmail.exists) { 19 | // Erro - participante já utilizou este codigo 20 | return callback(null, { 21 | resultado: 'INEXISTENTE', 22 | mensagem: 'E-mail informado não foi encontrado no sistema.' 23 | }); 24 | } 25 | const emailData = registroEmail.data(); 26 | 27 | console.log('emailData', emailData); 28 | 29 | if (emailData.usado) { 30 | callback(null, { 31 | resultado: 'USADO', 32 | mensagem: 'Este e-mail já foi utilizado para um registro.' 33 | }); 34 | } 35 | 36 | callback(null, { 37 | resultado: 'VALIDO', 38 | existe: registroEmail.exists, 39 | ...emailData 40 | }); 41 | }; 42 | -------------------------------------------------------------------------------- /eventos/functions/vendingmachine-estoque.protected.js: -------------------------------------------------------------------------------- 1 | const admin = require('firebase-admin'); 2 | process.env.GOOGLE_APPLICATION_CREDENTIALS = Runtime.getAssets()['/firebase-credentials.json'].path; 3 | const { escondeNumero, limpaNumero, getDDD, sendNotification, convertNewLine, fillParams } = require(Runtime.getFunctions()['util'].path); 4 | 5 | if (!admin.apps.length) { 6 | admin.initializeApp({}); 7 | } else { 8 | admin.app(); 9 | } 10 | const firestore = admin.firestore(); 11 | const md5 = require('md5'); 12 | 13 | /* 14 | event: codigoItem, quantidadeItem 15 | */ 16 | exports.handler = async function(context, event, callback) { 17 | 18 | // TODO: Atualizar estoque de produto da vending machine 19 | let data = {}; 20 | let mensagem = []; 21 | 22 | await firestore.collection('vendingmachine') 23 | .doc(process.env.VENDINGMACHINE_DEFAULT).collection('estoque') 24 | .doc(event.codigoItem).set({ 25 | estoque: parseInt(event.quantidadeItem), 26 | updatedAt: admin.firestore.FieldValue.serverTimestamp() 27 | }, { merge: true}).then(() => { 28 | mensagem.push('Estoque atualizado com sucesso!'); 29 | }).catch(e => { 30 | mensagem.push('Ocorreu um erro ao mudar o estoque.'); 31 | }); 32 | 33 | data.mensagem = mensagem.join('\n\n'); 34 | 35 | callback(null, data); 36 | 37 | }; 38 | -------------------------------------------------------------------------------- /eventos/assets/javascript/coordenador.js: -------------------------------------------------------------------------------- 1 | import * as firebase from "https://www.gstatic.com/firebasejs/9.18.0/firebase-app.js"; 2 | import { getFirestore, onSnapshot, collection, query, where, orderBy, limit } from "https://www.gstatic.com/firebasejs/9.18.0/firebase-firestore.js"; 3 | 4 | const CURRENT_EVENT = 'tdcbusiness2023'; 5 | 6 | const firebaseConfig = { 7 | apiKey: "AIzaSyC6I8Rwo9YiJgd7zK-U5RpHBZDSPsDt0ME", 8 | authDomain: "br-events.firebaseapp.com", 9 | projectId: "br-events", 10 | storageBucket: "br-events.appspot.com", 11 | messagingSenderId: "603831187236", 12 | appId: "1:603831187236:web:b1b3e97f5c6a38a4eb25b1" 13 | }; 14 | 15 | const app = firebase.initializeApp(firebaseConfig); 16 | const db = getFirestore(app); 17 | 18 | const trails = document.getElementById("trails"); 19 | const q = query(collection(db, "events", CURRENT_EVENT, "trilhas"), orderBy("trilhaNome", "asc")); 20 | window.load = function() { 21 | onSnapshot(q, (querySnapshot) => { 22 | querySnapshot.forEach((doc) => { 23 | const trilha = doc.data(); 24 | const li = document.createElement("li"); 25 | li.innerHTML = `${trilha.trilhaNome} | Gerenciar | TV`; 26 | li.setAttribute("data-id", doc.id); 27 | trails.appendChild(li); 28 | }); 29 | }) 30 | } -------------------------------------------------------------------------------- /eventos/functions/fotografia-lista.js: -------------------------------------------------------------------------------- 1 | const admin = require('firebase-admin'); 2 | process.env.GOOGLE_APPLICATION_CREDENTIALS = Runtime.getAssets()['/firebase-credentials.json'].path; 3 | const { escondeNumero, limpaNumero, getDDD, sendNotification, convertNewLine, fillParams } = require(Runtime.getFunctions()['util'].path); 4 | 5 | if (!admin.apps.length) { 6 | admin.initializeApp({}); 7 | } else { 8 | admin.app(); 9 | } 10 | const firestore = admin.firestore(); 11 | const md5 = require('md5'); 12 | 13 | /* 14 | 15 | */ 16 | exports.handler = async function(context, event, callback) { 17 | 18 | 19 | const eventos = await firestore.collection('events') 20 | .where('default', '==', true) 21 | .where('active', '==', true) 22 | .get() 23 | .then(snapshot => { 24 | return snapshot.docs.map(doc => { 25 | return { key: doc.id, ...doc.data() } 26 | }); 27 | }); 28 | 29 | const evento = eventos.length > 0 ? eventos[0].key : 'tdcbusiness2022'; 30 | 31 | // mudar status para 'cancelado' 32 | let lista = await firestore.collection('events') 33 | .doc(evento).collection('agendamento') 34 | .orderBy('posicao', 'asc') 35 | .get().then(s => { 36 | return s.docs.map(d => { 37 | return { id: d.id, ...d.data()} 38 | }) 39 | }); 40 | 41 | callback(null, { 42 | lista 43 | }) 44 | 45 | }; 46 | -------------------------------------------------------------------------------- /eventos/functions/ligacao.protected.js: -------------------------------------------------------------------------------- 1 | const admin = require('firebase-admin'); 2 | process.env.GOOGLE_APPLICATION_CREDENTIALS = Runtime.getAssets()['/firebase-credentials.json'].path; 3 | const { escondeNumero, limpaNumero, getDDD, sendNotification, convertNewLine, fillParams, adicionaNove } = require(Runtime.getFunctions()['util'].path); 4 | 5 | 6 | /* 7 | event: from, numero1, numero2 8 | 9 | */ 10 | exports.handler = async function(context, event, callback) { 11 | 12 | console.log('LIGACAO'); 13 | console.log(limpaNumero(event.numero1), 'vira', adicionaNove(limpaNumero(event.numero1))); 14 | console.log(limpaNumero(event.numero2), 'vira', adicionaNove(limpaNumero(event.numero2))); 15 | 16 | const client = await context.getTwilioClient(); 17 | let mensagem = []; 18 | 19 | const twiml = new Twilio.twiml.VoiceResponse(); 20 | twiml.say({ 21 | language: 'pt-br', 22 | voice: 'Alice' 23 | }, 'Um momento... Estamos conectando você com a organização do sorteio.'); 24 | 25 | // twiml.dial({ 26 | // callerId: limpaNumero(event.from) 27 | // }, limpaNumero(event.numero2)); 28 | 29 | twiml.dial(adicionaNove(limpaNumero(event.numero2))); 30 | 31 | await client.calls.create({ 32 | from: limpaNumero(event.from), 33 | to: adicionaNove(limpaNumero(event.numero1)), 34 | twiml: twiml.toString() 35 | }); 36 | 37 | mensagem.push(`Em breve você receberá uma ligação.`); 38 | 39 | callback(null, { 40 | mensagem: mensagem.join('\n\n') 41 | }); 42 | }; 43 | -------------------------------------------------------------------------------- /eventos/functions/verifica-sympla.protected.js: -------------------------------------------------------------------------------- 1 | let { Sympla } = require('sympla'); 2 | const sympla = new Sympla(process.env.SYMPLA_KEY); 3 | 4 | const admin = require('firebase-admin'); 5 | process.env.GOOGLE_APPLICATION_CREDENTIALS = Runtime.getAssets()['/firebase-credentials.json'].path; 6 | 7 | if (!admin.apps.length) { 8 | admin.initializeApp({}); 9 | } else { 10 | admin.app(); 11 | } 12 | const firestore = admin.firestore(); 13 | 14 | 15 | 16 | exports.handler = async function(context, event, callback) { 17 | console.log('ticket_number', event.ticket_number) 18 | let participante = await sympla.listarParticipantesEvento(process.env.EVENTO_ATUAL,{ 19 | ticket_number: event.ticket_number 20 | }).then(lista => { 21 | console.log('RETORNOU ', lista); 22 | if (lista.data && lista.data.length == 1) { 23 | return lista.data[0]; 24 | } 25 | return null; 26 | }); 27 | 28 | if (participante != null) { 29 | // verificar se ja usou o codigo 30 | const sympla = await firestore.collection('events').doc(event.evento).collection('sympla').doc(event.ticket_number.toUpperCase()).get(); 31 | if (sympla.exists) { 32 | // Erro - participante já utilizou este codigo 33 | return callback(null, { 34 | resultado: 'USADO', 35 | mensagem: 'Este código do Sympla já foi utilizado.' 36 | }); 37 | } 38 | 39 | } 40 | 41 | callback(null, { 42 | resultado: participante != null ? 'VALIDO' : 'INEXISTENTE', 43 | existe: participante != null, 44 | ...participante 45 | }); 46 | }; 47 | -------------------------------------------------------------------------------- /eventos/functions/registro-manual.js: -------------------------------------------------------------------------------- 1 | let { Sympla } = require('sympla'); 2 | const sympla = new Sympla(process.env.SYMPLA_KEY); 3 | 4 | const admin = require('firebase-admin'); 5 | process.env.GOOGLE_APPLICATION_CREDENTIALS = Runtime.getAssets()['/firebase-credentials.json'].path; 6 | 7 | if (!admin.apps.length) { 8 | admin.initializeApp({}); 9 | } else { 10 | admin.app(); 11 | } 12 | const firestore = admin.firestore(); 13 | 14 | 15 | 16 | exports.handler = async function(context, event, callback) { 17 | 18 | // console.log('ticket_number', event.ticket_number) 19 | // let participante = await sympla.listarParticipantesEvento(process.env.EVENTO_ATUAL,{ 20 | // ticket_number: event.ticket_number 21 | // }).then(lista => { 22 | // console.log('RETORNOU ', lista); 23 | // if (lista.data && lista.data.length == 1) { 24 | // return lista.data[0]; 25 | // } 26 | // return null; 27 | // }); 28 | 29 | // if (participante != null) { 30 | // // verificar se ja usou o codigo 31 | // const sympla = await firestore.collection('events').doc(event.evento).collection('sympla').doc(event.ticket_number.toUpperCase()).get(); 32 | // if (sympla.exists) { 33 | // // Erro - participante já utilizou este codigo 34 | // return callback(null, { 35 | // resultado: 'USADO', 36 | // mensagem: 'Este código do Sympla já foi utilizado.' 37 | // }); 38 | // } 39 | 40 | // } 41 | 42 | // callback(null, { 43 | // resultado: participante != null ? 'VALIDO' : 'INEXISTENTE', 44 | // existe: participante != null, 45 | // ...participante 46 | // }); 47 | 48 | callback(null, "OK"); 49 | 50 | }; 51 | -------------------------------------------------------------------------------- /eventos/functions/rdstation-certificado.js: -------------------------------------------------------------------------------- 1 | const admin = require('firebase-admin'); 2 | process.env.GOOGLE_APPLICATION_CREDENTIALS = Runtime.getAssets()['/firebase-credentials.json'].path; 3 | 4 | if (!admin.apps.length) { 5 | admin.initializeApp({}); 6 | } else { 7 | admin.app(); 8 | } 9 | const firestore = admin.firestore(); 10 | 11 | 12 | exports.handler = async function(context, event, callback) { 13 | const EVENTO = 'frontin'; 14 | 15 | let leads = event.leads; 16 | if (leads) { 17 | console.log('leads', leads.length); 18 | console.log(leads[0]); 19 | const { job_title, name, company, city, email, user, tags, personal_phone } = leads[0]; 20 | 21 | await firestore.collection('events').doc(EVENTO).collection('certificado').doc(leads[0].email).set({ 22 | job_title, 23 | name, 24 | company, 25 | city, 26 | email, 27 | user, 28 | tags, 29 | telefone: personal_phone.replace(/\D+/g, ''), 30 | personal_phone 31 | }, { merge: true }); 32 | 33 | const client = await context.getTwilioClient(); 34 | const message = `Olá ${name}.\n\nAgradecemos por participar do *FrontinSampa 2022*.\nSeu certificado de participação já está disponível.` 35 | 36 | await client.messages.create({ 37 | from: `whatsapp:${process.env.TWILIO_WHATSAPP_NUMBER}`, 38 | to: `whatsapp:${personal_phone.replace(/\D+/g, '')}`, 39 | body: message, 40 | }); 41 | 42 | } 43 | console.log('RDStation ', JSON.stringify(event)); 44 | console.log(event); 45 | 46 | return callback(null, "OK"); 47 | } 48 | -------------------------------------------------------------------------------- /eventos/assets/javascript/questions.js: -------------------------------------------------------------------------------- 1 | import * as firebase from "https://www.gstatic.com/firebasejs/9.18.0/firebase-app.js"; 2 | import { getFirestore, onSnapshot, collection, query, where, orderBy, limit } from "https://www.gstatic.com/firebasejs/9.18.0/firebase-firestore.js"; 3 | 4 | 5 | const urlParams = new URLSearchParams(window.location.search); 6 | const CURRENT_EVENT = urlParams.get('currentEvent'); 7 | if (!CURRENT_EVENT) { 8 | alert('You need to add the query param currentEvent before continue!'); 9 | 10 | } else { 11 | 12 | const firebaseConfig = { 13 | apiKey: "AIzaSyC6I8Rwo9YiJgd7zK-U5RpHBZDSPsDt0ME", 14 | authDomain: "br-events.firebaseapp.com", 15 | projectId: "br-events", 16 | storageBucket: "br-events.appspot.com", 17 | messagingSenderId: "603831187236", 18 | appId: "1:603831187236:web:b1b3e97f5c6a38a4eb25b1" 19 | }; 20 | 21 | const app = firebase.initializeApp(firebaseConfig); 22 | const db = getFirestore(app); 23 | 24 | const trails = document.getElementById("trails"); 25 | const q = query(collection(db, "events", CURRENT_EVENT, "trilhas"), orderBy("trilhaNome", "asc")); 26 | window.load = function() { 27 | onSnapshot(q, (querySnapshot) => { 28 | querySnapshot.forEach((doc) => { 29 | const trilha = doc.data(); 30 | const li = document.createElement("li"); 31 | li.innerHTML = `${trilha.trilhaNome} | Gerenciar | TV`; 32 | li.setAttribute("data-id", doc.id); 33 | trails.appendChild(li); 34 | }); 35 | }) 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /eventos/functions/add-nome.protected.js: -------------------------------------------------------------------------------- 1 | const admin = require('firebase-admin'); 2 | process.env.GOOGLE_APPLICATION_CREDENTIALS = Runtime.getAssets()['/firebase-credentials.json'].path; 3 | const { escondeNumero, limpaNumero, getDDD, sendNotification, convertNewLine, fillParams } = require(Runtime.getFunctions()['util'].path); 4 | 5 | if (!admin.apps.length) { 6 | admin.initializeApp({}); 7 | } else { 8 | admin.app(); 9 | } 10 | const firestore = admin.firestore(); 11 | const md5 = require('md5'); 12 | 13 | /* 14 | event: evento, from, nome, to 15 | */ 16 | exports.handler = async function(context, event, callback) { 17 | let participanteId = await md5(limpaNumero(event.from)); 18 | let idPlayerEvent = await md5(`${event.evento}:${limpaNumero(event.from)}`); 19 | 20 | let mensagem = []; 21 | 22 | console.log('ADICIONANDO NOME', event); 23 | 24 | await firestore.collection('events') 25 | .doc(event.evento).collection('participantes') 26 | .doc(idPlayerEvent).set({ 27 | participanteId, 28 | idPlayerEvent, 29 | updatedAt: admin.firestore.FieldValue.serverTimestamp(), 30 | phoneNumber: limpaNumero(event.from), 31 | nome: event.nome 32 | }, { merge: true }); 33 | 34 | await firestore.collection('participantes') 35 | .doc(participanteId).set({ 36 | updatedAt: admin.firestore.FieldValue.serverTimestamp(), 37 | phoneNumber: limpaNumero(event.from), 38 | nome: event.nome, 39 | ultimoEvento: event.evento 40 | }, { merge: true }); 41 | 42 | mensagem.push(`Seu nome foi registrado com sucesso!`); 43 | 44 | 45 | callback(null, { 46 | mensagem: mensagem.join('\n\n'), 47 | }); 48 | 49 | }; 50 | -------------------------------------------------------------------------------- /eventos/functions/verifica-participante-pontos.protected.js: -------------------------------------------------------------------------------- 1 | const admin = require('firebase-admin'); 2 | process.env.GOOGLE_APPLICATION_CREDENTIALS = Runtime.getAssets()['/firebase-credentials.json'].path; 3 | const { escondeNumero, limpaNumero, getDDD, sendNotification, convertNewLine, fillParams } = require(Runtime.getFunctions()['util'].path); 4 | 5 | if (!admin.apps.length) { 6 | admin.initializeApp({}); 7 | } else { 8 | admin.app(); 9 | } 10 | const firestore = admin.firestore(); 11 | const md5 = require('md5'); 12 | 13 | /* 14 | event: evento, token 15 | */ 16 | exports.handler = async function(context, event, callback) { 17 | let participanteId = event.token; 18 | 19 | 20 | let participante = await firestore.collection('events') 21 | .doc(event.evento).collection('participantes') 22 | .doc(participanteId).get().then(async s => { 23 | if (s.exists) { 24 | return s.data(); 25 | } else { 26 | return { 27 | pontosAcumulados: 0, 28 | pontosCorrente: 0, 29 | recebeuOptin: false, 30 | ativouNetworking: false, 31 | impressoes: 0 32 | }; 33 | } 34 | }); 35 | 36 | let participanteGeral = await firestore.collection('participantes') 37 | .doc(participante.participanteId).get().then(async s => { 38 | if (s.exists) { 39 | return s.data(); 40 | } else { 41 | return {} 42 | } 43 | }); 44 | 45 | 46 | 47 | let data = { 48 | podeDescontarPontos: participante.pontosCorrente > 0, 49 | pontosCorrente: participante.pontosCorrente, 50 | pontosAcumulados: participante.pontosAcumulados, 51 | participante, 52 | participanteGeral 53 | }; 54 | 55 | callback(null, data); 56 | 57 | }; 58 | -------------------------------------------------------------------------------- /eventos/functions/send-cidade.protected.js: -------------------------------------------------------------------------------- 1 | const admin = require('firebase-admin'); 2 | process.env.GOOGLE_APPLICATION_CREDENTIALS = Runtime.getAssets()['/firebase-credentials.json'].path; 3 | const { escondeNumero, limpaNumero, getDDD, sendNotification, convertNewLine, fillParams } = require(Runtime.getFunctions()['util'].path); 4 | 5 | if (!admin.apps.length) { 6 | admin.initializeApp({}); 7 | } else { 8 | admin.app(); 9 | } 10 | const firestore = admin.firestore(); 11 | const md5 = require('md5'); 12 | 13 | const AGENDAMENTO_ATIVADO = false; 14 | 15 | 16 | /* 17 | event: from 18 | */ 19 | exports.handler = async function(context, event, callback) { 20 | const participanteId = await md5(limpaNumero(event.from)); 21 | const idPlayerEvent = await md5(`${event.evento}:${limpaNumero(event.from)}`); 22 | 23 | const { WHATSAPP_MESSAGE_SERVICE_SID, WHATSAPP_ONBOARDING_TDC_TEMPLATE_CIDADE_SID, ACCOUNT_SID, AUTH_TOKEN } = process.env; 24 | // const client = context.getTwilioClient(); 25 | const client = require('twilio')(ACCOUNT_SID, AUTH_TOKEN); 26 | 27 | // Enviar mensagem Content API com cidades 28 | console.log('MESSAGE', { 29 | contentSid: WHATSAPP_ONBOARDING_TDC_TEMPLATE_CIDADE_SID, 30 | messagingServiceSid: WHATSAPP_MESSAGE_SERVICE_SID, 31 | to: event.from, //`whatsapp:${participante.phoneNumber}`, 32 | // ContentVariables: {} 33 | }); 34 | await client.messages.create({ 35 | contentSid: WHATSAPP_ONBOARDING_TDC_TEMPLATE_CIDADE_SID, 36 | messagingServiceSid: WHATSAPP_MESSAGE_SERVICE_SID, 37 | to: event.from, //`whatsapp:${participante.phoneNumber}`, 38 | // ContentVariables: {} 39 | }).then(m => { 40 | console.log('SENT CONTENT MESSAGE', m.sid); 41 | callback(null, m.sid); 42 | }).catch(e => { 43 | callback(e, null); 44 | }); 45 | 46 | 47 | }; 48 | -------------------------------------------------------------------------------- /eventos/functions/send-barista.protected.js: -------------------------------------------------------------------------------- 1 | // Verificar possibilidade pedido de café e disparar mensagem com template 2 | 3 | 4 | 5 | const admin = require('firebase-admin'); 6 | process.env.GOOGLE_APPLICATION_CREDENTIALS = Runtime.getAssets()['/firebase-credentials.json'].path; 7 | const { escondeNumero, limpaNumero, getDDD, sendNotification, convertNewLine, fillParams } = require(Runtime.getFunctions()['util'].path); 8 | 9 | 10 | 11 | if (!admin.apps.length) { 12 | admin.initializeApp({}); 13 | } else { 14 | admin.app(); 15 | } 16 | const firestore = admin.firestore(); 17 | const md5 = require('md5'); 18 | 19 | 20 | /* 21 | event: evento, palestraId, from, choice, 22 | */ 23 | exports.handler = async function(context, event, callback) { 24 | const palestraId = event.palestraId; 25 | const participanteId = await md5(limpaNumero(event.from)); 26 | const idPlayerEvent = await md5(`${event.evento}:${limpaNumero(event.from)}`); 27 | 28 | console.log('Verifica Barista'); 29 | // const WHATSAPP_BARISTA_TDC_TEMPLATE_SID = 'HXffa8c7f8cb308373b2a317d4afefd292'; 30 | 31 | 32 | const { WHATSAPP_MESSAGE_SERVICE_SID, WHATSAPP_BARISTA_TDC_TEMPLATE_SID, ACCOUNT_SID, AUTH_TOKEN } = process.env; 33 | // const client = context.getTwilioClient(); 34 | const client = require('twilio')(ACCOUNT_SID, AUTH_TOKEN); 35 | 36 | // Enviar mensagem Content API com opções de café 37 | 38 | // TODO: Verificar se participante está online ou não. 39 | 40 | await client.messages.create({ 41 | contentSid: WHATSAPP_BARISTA_TDC_TEMPLATE_SID, 42 | messagingServiceSid: WHATSAPP_MESSAGE_SERVICE_SID, 43 | to: event.from, //`whatsapp:${participante.phoneNumber}`, 44 | // ContentVariables: {} 45 | }).then(m => { 46 | console.log('SENT CONTENT MESSAGE', m.sid); 47 | callback(null, m.sid); 48 | }).catch(e => { 49 | callback(e, null); 50 | }); 51 | 52 | // TODO: verificar se já não tem pedido em andamento ou se 53 | // TODO: verificar se possui pontuação mínima para pedido 54 | }; 55 | -------------------------------------------------------------------------------- /scripts/util.js: -------------------------------------------------------------------------------- 1 | 2 | exports.escondeNumero = function(number) { 3 | // +5511999991234 => +55119****-1234 4 | if (number) number = number.replace('whatsapp:', ''); 5 | // if (!number || number.length < 12) return '+-----****-----'; 6 | return number.substr(0, number.length - 8) + '****-' + number.substr(number.length - 4 ) 7 | } 8 | 9 | exports.limpaNumero = function(number, removeMais) { 10 | if (number) number = number.replace('whatsapp:', ''); 11 | if (number && removeMais) number = number.replace('+', ''); 12 | return number; 13 | } 14 | 15 | exports.getDDD = function(number) { 16 | if (number) number = number.replace('whatsapp:+', ''); 17 | number = number.substr(0,4); 18 | return number; 19 | } 20 | 21 | exports.validateEmail = function(email) { 22 | const re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; 23 | return re.test(email); 24 | } 25 | 26 | exports.convertNewLine = (text) => { 27 | return text.split('').join('\n'); 28 | } 29 | 30 | exports.fillParams = (text, params) => { 31 | return Object.keys(params).reduce((prev, key)=> { 32 | return prev.split(`{{${key}}}`).join(params[key]); 33 | }, `${text}`); 34 | } 35 | 36 | exports.sendNotification = async (client, from, to, message) => { 37 | return await client.messages.create({ 38 | from: from, 39 | to: to, 40 | body: message 41 | }); 42 | } 43 | 44 | exports.sendNotificationMedia = async (client, from, to, message, mediaUrl) => { 45 | return await client.messages.create({ 46 | from: from, 47 | to: to, 48 | body: message, 49 | mediaUrl 50 | }); 51 | } 52 | 53 | 54 | exports.adicionaNove = (number) => { 55 | 56 | if (number.split('+55').length > 1) { 57 | if (number.length == 13) { 58 | return number.substr(0, number.length - 8) + '9' + number.substr(number.length - 8) 59 | } 60 | return number 61 | } 62 | return number; 63 | } 64 | -------------------------------------------------------------------------------- /eventos/functions/fotografia-reset.js: -------------------------------------------------------------------------------- 1 | const admin = require('firebase-admin'); 2 | process.env.GOOGLE_APPLICATION_CREDENTIALS = Runtime.getAssets()['/firebase-credentials.json'].path; 3 | const { escondeNumero, limpaNumero, getDDD, sendNotification, convertNewLine, fillParams } = require(Runtime.getFunctions()['util'].path); 4 | 5 | if (!admin.apps.length) { 6 | admin.initializeApp({}); 7 | } else { 8 | admin.app(); 9 | } 10 | const firestore = admin.firestore(); 11 | 12 | /* 13 | event: filaId 14 | */ 15 | exports.handler = async function(context, event, callback) { 16 | const client = await context.getTwilioClient(); 17 | 18 | const participante = await firestore.collection('participantes') 19 | .doc(event.filaId).get().then(s => { 20 | if (s.exists) { 21 | return s.data(); 22 | } else { 23 | return null; 24 | } 25 | }); 26 | 27 | if (!participante) { 28 | 29 | return callback(null, { 30 | erro: true, 31 | mensagem: 'Participante não encontrado!\n\nInforme para responsável da Twilio.' 32 | }); 33 | 34 | } 35 | 36 | 37 | // mudar status para 'fila' 38 | await firestore.collection('events') 39 | .doc(participante.ultimoEvento).collection('agendamento') 40 | .doc(event.filaId).set({ 41 | updatedAt: admin.firestore.FieldValue.serverTimestamp(), 42 | status: 'fila' 43 | }, { merge: true }); 44 | 45 | 46 | 47 | await firestore.collection('participantes').doc(event.filaId).set({ 48 | updatedAt: admin.firestore.FieldValue.serverTimestamp(), 49 | fotografiaResets: admin.firestore.FieldValue.increment(1) 50 | }, { merge: true }) 51 | 52 | await firestore.collection('events').doc(participante.ultimoEvento) 53 | .collection('participantes').doc(event.filaId).set({ 54 | updatedAt: admin.firestore.FieldValue.serverTimestamp(), 55 | fotografiaResets: admin.firestore.FieldValue.increment(1) 56 | }, { merge: true }) 57 | 58 | callback(null, { 59 | erro: false, 60 | mensagem: 'Participante retornou para a fila!' 61 | }) 62 | 63 | }; 64 | -------------------------------------------------------------------------------- /eventos/functions/fotografia-chama.js: -------------------------------------------------------------------------------- 1 | const admin = require('firebase-admin'); 2 | process.env.GOOGLE_APPLICATION_CREDENTIALS = Runtime.getAssets()['/firebase-credentials.json'].path; 3 | const { escondeNumero, limpaNumero, getDDD, sendNotification, convertNewLine, fillParams } = require(Runtime.getFunctions()['util'].path); 4 | 5 | if (!admin.apps.length) { 6 | admin.initializeApp({}); 7 | } else { 8 | admin.app(); 9 | } 10 | const firestore = admin.firestore(); 11 | 12 | /* 13 | event: filaId 14 | */ 15 | exports.handler = async function(context, event, callback) { 16 | const client = await context.getTwilioClient(); 17 | 18 | const participante = await firestore.collection('participantes') 19 | .doc(event.filaId).get().then(s => { 20 | if (s.exists) { 21 | return s.data(); 22 | } else { 23 | return null; 24 | } 25 | }); 26 | 27 | if (!participante) { 28 | return callback(null, { 29 | erro: true, 30 | mensagem: 'Participante não encontrado!\n\nInforme para responsável da Twilio.' 31 | }); 32 | 33 | } 34 | // enviar mensagem para pessoa selecionada 35 | let mensagem = `Olá *${participante.nome}*!\n\nEstamos aguardando você no *Connect Foto Iteris* - O estúdio de foto profissional do TDC.` 36 | await sendNotification( 37 | client, 38 | `whatsapp:${process.env.TWILIO_WHATSAPP_NUMBER_TDC}`, 39 | `whatsapp:${participante.phoneNumber}`, 40 | mensagem 41 | ); 42 | 43 | await firestore.collection('participantes').doc(event.filaId).set({ 44 | updatedAt: admin.firestore.FieldValue.serverTimestamp(), 45 | fotografiaChamados: admin.firestore.FieldValue.increment(1) 46 | }, { merge: true }) 47 | 48 | await firestore.collection('events').doc(participante.ultimoEvento) 49 | .collection('participantes').doc(event.filaId).set({ 50 | updatedAt: admin.firestore.FieldValue.serverTimestamp(), 51 | fotografiaChamados: admin.firestore.FieldValue.increment(1) 52 | }, { merge: true }) 53 | 54 | callback(null, { 55 | erro: false, 56 | mensagem: 'Participante notificado com sucesso!' 57 | }) 58 | 59 | }; 60 | -------------------------------------------------------------------------------- /eventos/functions/util.private.js: -------------------------------------------------------------------------------- 1 | 2 | exports.escondeNumero = function(number) { 3 | // +5511999991234 => +55119****-1234 4 | if (number) number = number.replace('whatsapp:', ''); 5 | if (!number || number.length < 12) return '+-----****-----'; 6 | return number.substr(0, number.length - 8) + '****-' + number.substr(number.length - 4 ) 7 | } 8 | 9 | exports.limpaNumero = function(number, removeMais) { 10 | if (number) number = number.replace('whatsapp:', ''); 11 | if (number && removeMais) number = number.replace('+', ''); 12 | return number; 13 | } 14 | 15 | exports.getDDD = function(number) { 16 | if (number) number = number.replace('whatsapp:+', ''); 17 | number = number.substr(0,4); 18 | return number; 19 | } 20 | 21 | exports.validateEmail = function(email) { 22 | const re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; 23 | return re.test(email); 24 | } 25 | 26 | exports.convertNewLine = (text) => { 27 | return text.split('').join('\n'); 28 | } 29 | 30 | exports.fillParams = (text, params) => { 31 | return Object.keys(params).reduce((prev, key)=> { 32 | return prev.split(`{{${key}}}`).join(params[key]); 33 | }, `${text}`); 34 | } 35 | 36 | exports.sendNotification = async (client, from, to, message) => { 37 | return await client.messages.create({ 38 | from: from, 39 | to: to, 40 | body: message 41 | }); 42 | } 43 | 44 | exports.sendNotificationMedia = async (client, from, to, message, mediaUrl) => { 45 | return await client.messages.create({ 46 | from: from, 47 | to: to, 48 | body: message, 49 | mediaUrl 50 | }); 51 | } 52 | 53 | exports.adicionaNove = (number) => { 54 | if (number.split('+55').length > 1) { 55 | if (number.length == 13) { 56 | return number.substr(0, number.length - 8) + '9' + number.substr(number.length - 8); 57 | } 58 | return number; 59 | } 60 | return number; 61 | } 62 | 63 | 64 | exports.replaceVariablesTemplateMessage = (message, data) => { 65 | message = message.split('\\n').join('\n'); 66 | 67 | Object.keys(data).forEach((field) => { 68 | message = message.split(`{{${field}}}`).join(data[field]) 69 | }); 70 | return message; 71 | } 72 | -------------------------------------------------------------------------------- /eventos/functions/remove-fila-fotografia.protected.js: -------------------------------------------------------------------------------- 1 | const admin = require('firebase-admin'); 2 | process.env.GOOGLE_APPLICATION_CREDENTIALS = Runtime.getAssets()['/firebase-credentials.json'].path; 3 | const { escondeNumero, limpaNumero, getDDD, sendNotification, convertNewLine, fillParams } = require(Runtime.getFunctions()['util'].path); 4 | 5 | if (!admin.apps.length) { 6 | admin.initializeApp({}); 7 | } else { 8 | admin.app(); 9 | } 10 | const firestore = admin.firestore(); 11 | const md5 = require('md5'); 12 | 13 | /* 14 | event: evento, token, from 15 | */ 16 | exports.handler = async function(context, event, callback) { 17 | let participanteId = await md5(limpaNumero(event.from)); 18 | 19 | let agendamentoTotal = null; 20 | let agendamentoPosicao = null; 21 | 22 | await firestore.collection('events') 23 | .doc(event.evento).collection('agendamento') 24 | .doc(participanteId).delete(); 25 | 26 | let mensagem = `Remoção da fila foi realizada com sucesso.` 27 | 28 | let data = { 29 | mensagem 30 | }; 31 | 32 | 33 | // TODO: verificar pontuação de networking 34 | // /events/{eventId}/participantes/{participanteId}/ [PontosAcumulados, PontosCorrente] 35 | // como resoler pontuação igual? 36 | 37 | // TODO: montar mensagem padrão para participante com resumo das informações 38 | 39 | 40 | 41 | // console.log('VERIFICA PALAVRA: ', event); 42 | 43 | // const client = await context.getTwilioClient(); 44 | // if (!event.token) { 45 | // console.log('TOKEN VAZIO'); 46 | // return callback(null, { 47 | // type: 'not-found' 48 | // }); 49 | // } 50 | 51 | // let activation = await firestore.collection('events').doc(event.evento).collection('palavras').doc(event.token).get(); 52 | // if (!activation.exists) { 53 | // activation = await firestore.collection('events').doc(event.evento).collection('palavras').doc(event.token.toLowerCase()).get(); 54 | // if (!activation.exists) { 55 | // // token não encontrado 56 | // console.log(event.token, 'Palavra não existe no evento', event.evento); 57 | // return callback(null, { 58 | // type: 'not-found' 59 | // }); 60 | // } 61 | // } 62 | 63 | console.log('DATA', data); 64 | 65 | 66 | callback(null, data); 67 | 68 | }; 69 | -------------------------------------------------------------------------------- /eventos/functions/verifica-evento-participante.protected.js: -------------------------------------------------------------------------------- 1 | const admin = require('firebase-admin'); 2 | process.env.GOOGLE_APPLICATION_CREDENTIALS = Runtime.getAssets()['/firebase-credentials.json'].path; 3 | const { escondeNumero, limpaNumero, getDDD } = require(Runtime.getFunctions()['util'].path); 4 | 5 | if (!admin.apps.length) { 6 | admin.initializeApp({}); 7 | } else { 8 | admin.app(); 9 | } 10 | const firestore = admin.firestore(); 11 | const md5 = require('md5'); 12 | 13 | 14 | exports.handler = async function(context, event, callback) { 15 | let participanteId = await md5(limpaNumero(event.from)); 16 | 17 | let evento = null; 18 | let mensagem = ''; 19 | let mediaUrl = ''; 20 | 21 | console.log('participanteId', participanteId); 22 | 23 | const participante = await firestore.collection('participantes') 24 | .doc(participanteId) 25 | .get() 26 | .then( p => p.exists ? p.data() : null ); 27 | 28 | if (participante) { 29 | evento = participante.evento || null; 30 | // Verificar se evento estiver ativo 31 | if (evento != null) { 32 | const participanteEvento = await firestore.collection('events') 33 | .doc(evento) 34 | .get() 35 | .then( e => e.exists ? e.data() : null ); 36 | evento = participanteEvento && participanteEvento.active ? evento : null; 37 | mensagem = participante && participanteEvento.message ? participanteEvento.message : ''; 38 | mediaUrl = participante && participanteEvento.mediaUrl ? participanteEvento.mediaUrl : ''; 39 | } 40 | } 41 | 42 | // Caso não tenha evento definido ou ativo, carregar evento default ativo 43 | if (!evento) { 44 | // Sem evento - definir default 45 | const eventos = await firestore.collection('events') 46 | .where('default', '==', true) 47 | .where('active', '==', true) 48 | .get() 49 | .then(snapshot => { 50 | return snapshot.docs.map(doc => { 51 | return { key: doc.id, ...doc.data() } 52 | }); 53 | }); 54 | 55 | if (eventos.length > 0) { 56 | evento = eventos[0].key 57 | mensagem = eventos[0].message ? eventos[0].message : ''; 58 | mediaUrl = eventos[0].mediaUrl ? eventos[0].mediaUrl : ''; 59 | } 60 | } else { 61 | 62 | } 63 | mensagem = mensagem.split('').join('\n'); 64 | 65 | callback(null, { 66 | evento, mensagem, mediaUrl 67 | }); 68 | 69 | }; 70 | -------------------------------------------------------------------------------- /scripts/airtable.js: -------------------------------------------------------------------------------- 1 | require("dotenv").config(); 2 | 3 | 4 | 5 | // Initialize Firebase 6 | var admin = require("firebase-admin"); 7 | 8 | 9 | if (!admin.apps.length) { 10 | admin.initializeApp(); 11 | } else { 12 | admin.app(); // if already initialized, use that one 13 | } 14 | const firestore = admin.firestore(); 15 | 16 | 17 | 18 | const { AIRTABLE_API_KEY, AIRTABLE_BASE_ID, EVENT_ID } = process.env; 19 | 20 | var Airtable = require('airtable'); 21 | var base = new Airtable({ apiKey: AIRTABLE_API_KEY }).base(AIRTABLE_BASE_ID); 22 | 23 | const listPalestras = async () => { 24 | await base('Palestras').select({ 25 | pageSize: 5, 26 | view: "Grid view" 27 | }).eachPage(async function page(records, fetchNextPage) { 28 | // This function (`page`) will get called for each page of records. 29 | 30 | await records.forEach(async (record) => { 31 | // Adicionar no banco 32 | const { id, fields } = record; 33 | // /events/[process.env.EVENT_ID]/palestras/[fields.Codigo from Airtable] 34 | await firestore.collection('events').doc(EVENT_ID) 35 | .collection('palestras').doc(fields.Codigo).set({ 36 | titulo: fields.Titulo, 37 | horario: fields.Horario, 38 | trilhaId: fields.Codigo.split('-')[0], //fields.TrilhaId, 39 | trilhaNome: fields.Trilha, 40 | status: 'Fechado', 41 | updatedAt: admin.firestore.FieldValue.serverTimestamp() 42 | }, { merge: true }) 43 | 44 | await firestore.collection('events').doc(EVENT_ID) 45 | .collection('trilhas').doc(fields.Codigo.split('-')[0]).set({ 46 | trilhaId: fields.Codigo.split('-')[0], 47 | trilhaNome: fields.Trilha, 48 | updatedAt: admin.firestore.FieldValue.serverTimestamp() 49 | }, { merge: false }) 50 | 51 | console.log('RECORD', record.id, record.fields); 52 | 53 | 54 | // Remover registro da lista 55 | await base('Palestras').destroy(record.id); 56 | 57 | return true; 58 | }); 59 | 60 | // To fetch the next page of records, call `fetchNextPage`. 61 | // If there are more records, `page` will get called again. 62 | // If there are no more records, `done` will get called. 63 | fetchNextPage(); 64 | 65 | }, function done(err) { 66 | if (err) { console.error(err); return; } 67 | }); 68 | 69 | } 70 | 71 | listPalestras(); 72 | -------------------------------------------------------------------------------- /eventos/functions/fotografia-cancela.js: -------------------------------------------------------------------------------- 1 | const admin = require('firebase-admin'); 2 | process.env.GOOGLE_APPLICATION_CREDENTIALS = Runtime.getAssets()['/firebase-credentials.json'].path; 3 | const { escondeNumero, limpaNumero, getDDD, sendNotification, convertNewLine, fillParams } = require(Runtime.getFunctions()['util'].path); 4 | 5 | if (!admin.apps.length) { 6 | admin.initializeApp({}); 7 | } else { 8 | admin.app(); 9 | } 10 | const firestore = admin.firestore(); 11 | const md5 = require('md5'); 12 | 13 | /* 14 | event: filaId 15 | */ 16 | exports.handler = async function(context, event, callback) { 17 | const client = await context.getTwilioClient(); 18 | 19 | const participante = await firestore.collection('participantes') 20 | .doc(event.filaId).get().then(s => { 21 | if (s.exists) { 22 | return s.data(); 23 | } else { 24 | return null; 25 | } 26 | }); 27 | 28 | if (!participante) { 29 | 30 | return callback(null, { 31 | erro: true, 32 | mensagem: 'Participante não encontrado!\n\nInforme para responsável da Twilio.' 33 | }); 34 | 35 | } 36 | 37 | 38 | // mudar status para 'cancelado' 39 | await firestore.collection('events') 40 | .doc(participante.ultimoEvento).collection('agendamento') 41 | .doc(event.filaId).delete() 42 | 43 | // TODO: listar 1 e 5 com status 'fila' e mandar mensagem 44 | 45 | 46 | // enviar mensagem para pessoa selecionada 47 | let mensagem = `Olá *${participante.nome}*!\n\nCancelamos sua reserva para a foto profissional.\n\nVocê ainda pode entrar novamente na fila.` 48 | await sendNotification( 49 | client, 50 | `whatsapp:${process.env.TWILIO_WHATSAPP_NUMBER_TDC}`, 51 | `whatsapp:${participante.phoneNumber}`, 52 | mensagem 53 | ); 54 | 55 | await firestore.collection('participantes').doc(event.filaId).set({ 56 | updatedAt: admin.firestore.FieldValue.serverTimestamp(), 57 | fotografiaCancelamentos: admin.firestore.FieldValue.increment(1) 58 | }, { merge: true }) 59 | 60 | await firestore.collection('events').doc(participante.ultimoEvento) 61 | .collection('participantes').doc(event.filaId).set({ 62 | updatedAt: admin.firestore.FieldValue.serverTimestamp(), 63 | fotografiaCancelamentos: admin.firestore.FieldValue.increment(1) 64 | }, { merge: true }) 65 | 66 | callback(null, { 67 | erro: false, 68 | mensagem: 'Participante removido da fila com sucesso!' 69 | }) 70 | 71 | }; 72 | -------------------------------------------------------------------------------- /eventos/assets/css/tela.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "Fira Sans Condensed", sans-serif; 3 | } 4 | 5 | main { 6 | display: flex; 7 | flex-direction: column; 8 | height: 100vh; 9 | margin: 40px; 10 | } 11 | #container { 12 | display: flex; 13 | flex-direction: row; 14 | } 15 | 16 | #image-content { 17 | display: flex; 18 | flex-direction: column; 19 | justify-content: center; 20 | align-items: center; 21 | width: 40%; 22 | height: 100%; 23 | } 24 | 25 | #image-content-qr { 26 | display: flex; 27 | flex-direction: column; 28 | justify-content: center; 29 | align-items: center; 30 | width: 40%; 31 | height: 100%; 32 | } 33 | 34 | #image-content-qr img { 35 | width: 100%; 36 | height: 100%; 37 | display: block; 38 | } 39 | 40 | #image-content img { 41 | width: 100%; 42 | /* height: 100%; */ 43 | display: block; 44 | } 45 | 46 | #text-content { 47 | display: flex; 48 | flex-direction: column; 49 | width: 60%; 50 | height: 100%; 51 | } 52 | 53 | #code-container { 54 | display: flex; 55 | flex-direction: column; 56 | justify-content: center; 57 | align-items: center; 58 | } 59 | #code-container span { 60 | font-weight: bold; 61 | } 62 | 63 | #brand-container { 64 | display: flex; 65 | flex-direction: row; 66 | margin-bottom: 50px; 67 | } 68 | 69 | h1 { 70 | font-size: 4.5rem; 71 | display: inline; 72 | } 73 | 74 | h2 { 75 | font-size: 4rem; 76 | font-weight: lighter; 77 | } 78 | 79 | h2 span { 80 | display: block; 81 | font-weight: bold; 82 | } 83 | 84 | p { 85 | font-size: 3rem; 86 | text-align: center; 87 | margin: 0;; 88 | } 89 | 90 | .responsive { 91 | width: 100%; 92 | height: auto; 93 | } 94 | 95 | .hidden { 96 | display: none !important; 97 | } 98 | 99 | #empty { 100 | margin-top: 300px; 101 | display: flex; 102 | flex-direction: row; 103 | } 104 | 105 | #login-form { 106 | display: flex; 107 | flex-direction: column; 108 | justify-content: center; 109 | align-items: center; 110 | } 111 | 112 | 113 | 114 | ul { 115 | margin: 0; 116 | padding: 0; 117 | } 118 | 119 | li { 120 | font-size: 1.5em; 121 | display: block; 122 | /* overflow: visible; */ 123 | list-style-type: none; 124 | padding: .5em; 125 | margin-bottom: .1em; 126 | box-sizing: content-box; 127 | } 128 | 129 | 130 | #qrcode { 131 | padding: 20pt; 132 | width: 70%; 133 | background-color: #fff; 134 | margin-top: 20pt; 135 | } -------------------------------------------------------------------------------- /eventos/.gitignore: -------------------------------------------------------------------------------- 1 | # Twilio Serverless 2 | .twiliodeployinfo 3 | 4 | # Logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | lerna-debug.log* 11 | .pnpm-debug.log* 12 | 13 | # Diagnostic reports (https://nodejs.org/api/report.html) 14 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 15 | 16 | # Runtime data 17 | pids 18 | *.pid 19 | *.seed 20 | *.pid.lock 21 | 22 | # Directory for instrumented libs generated by jscoverage/JSCover 23 | lib-cov 24 | 25 | # Coverage directory used by tools like istanbul 26 | coverage 27 | *.lcov 28 | 29 | # nyc test coverage 30 | .nyc_output 31 | 32 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 33 | .grunt 34 | 35 | # Bower dependency directory (https://bower.io/) 36 | bower_components 37 | 38 | # node-waf configuration 39 | .lock-wscript 40 | 41 | # Compiled binary addons (https://nodejs.org/api/addons.html) 42 | build/Release 43 | 44 | # Dependency directories 45 | node_modules/ 46 | jspm_packages/ 47 | 48 | # Snowpack dependency directory (https://snowpack.dev/) 49 | web_modules/ 50 | 51 | # TypeScript cache 52 | *.tsbuildinfo 53 | 54 | # Optional npm cache directory 55 | .npm 56 | 57 | # Optional eslint cache 58 | .eslintcache 59 | 60 | # Optional stylelint cache 61 | .stylelintcache 62 | 63 | # Microbundle cache 64 | .rpt2_cache/ 65 | .rts2_cache_cjs/ 66 | .rts2_cache_es/ 67 | .rts2_cache_umd/ 68 | 69 | # Optional REPL history 70 | .node_repl_history 71 | 72 | # Output of 'npm pack' 73 | *.tgz 74 | 75 | # Yarn Integrity file 76 | .yarn-integrity 77 | 78 | # dotenv environment variable files 79 | .env 80 | .env.development.local 81 | .env.test.local 82 | .env.production.local 83 | .env.local 84 | 85 | # parcel-bundler cache (https://parceljs.org/) 86 | .cache 87 | .parcel-cache 88 | 89 | # Next.js build output 90 | .next 91 | out 92 | 93 | # Nuxt.js build / generate output 94 | .nuxt 95 | dist 96 | 97 | # Gatsby files 98 | .cache/ 99 | # Comment in the public line in if your project uses Gatsby and not Next.js 100 | # https://nextjs.org/blog/next-9-1#public-directory-support 101 | # public 102 | 103 | # vuepress build output 104 | .vuepress/dist 105 | 106 | # vuepress v2.x temp and cache directory 107 | .temp 108 | .cache 109 | 110 | # Docusaurus cache and generated files 111 | .docusaurus 112 | 113 | # Serverless directories 114 | .serverless/ 115 | 116 | # FuseBox cache 117 | .fusebox/ 118 | 119 | # DynamoDB Local files 120 | .dynamodb/ 121 | 122 | # TernJS port file 123 | .tern-port 124 | 125 | # Stores VSCode versions used for testing VSCode extensions 126 | .vscode-test 127 | 128 | # yarn v2 129 | .yarn/cache 130 | .yarn/unplugged 131 | .yarn/build-state.yml 132 | .yarn/install-state.gz 133 | .pnp.* 134 | -------------------------------------------------------------------------------- /eventos/functions/add-optin.protected.js: -------------------------------------------------------------------------------- 1 | const admin = require('firebase-admin'); 2 | process.env.GOOGLE_APPLICATION_CREDENTIALS = Runtime.getAssets()['/firebase-credentials.json'].path; 3 | const { escondeNumero, limpaNumero, getDDD, sendNotification, convertNewLine, fillParams } = require(Runtime.getFunctions()['util'].path); 4 | 5 | if (!admin.apps.length) { 6 | admin.initializeApp({}); 7 | } else { 8 | admin.app(); 9 | } 10 | const firestore = admin.firestore(); 11 | const md5 = require('md5'); 12 | 13 | /* 14 | event: evento, from, optin 15 | */ 16 | exports.handler = async function(context, event, callback) { 17 | let participanteId = await md5(limpaNumero(event.from)); 18 | let idPlayerEvent = await md5(`${event.evento}:${limpaNumero(event.from)}`); 19 | 20 | let mensagem = []; 21 | 22 | console.log('ADICIONANDO OPTIN', event); 23 | 24 | await firestore.collection('events') 25 | .doc(event.evento).collection('participantes') 26 | .doc(idPlayerEvent).set({ 27 | participanteId, 28 | updatedAt: admin.firestore.FieldValue.serverTimestamp(), 29 | phoneNumber: limpaNumero(event.from), 30 | recebeuOptin: true, 31 | aceitouOptin: parseInt(event.optin) == 1, 32 | cidade: event.cidade 33 | }, { merge: true }); 34 | 35 | await firestore.collection('events') 36 | .doc(event.evento).set({ 37 | stats: { 38 | cidades: { 39 | [event.cidade]: admin.firestore.FieldValue.increment(1) 40 | } 41 | } 42 | }, { merge: true }); 43 | 44 | 45 | switch(parseInt(event.optin) == 1) { 46 | case true: 47 | mensagem.push(`Agradecemos por aceitar!`); 48 | mensagem.push(`A organização do evento receberá seu contato e poderá enviar mensagens pelo WhatsApp.`); 49 | // mensagem.push(`Thank you for accepting!`); 50 | // mensagem.push(`The event organization will receive your contact and will be able to send messages via WhatsApp.`); 51 | break; 52 | case false: 53 | mensagem.push(`Agradecemos por nos informar!`); 54 | mensagem.push(`Você receberá mensagens apenas durante o evento e quando realizar alguma ativação.`); 55 | mensagem.push(`Após o evento, não será enviada nenhuma mensagem da Twilio ou da organização do evento.`); 56 | // mensagem.push(`Thanks for letting us know!`); 57 | // mensagem.push(`You will only receive messages during the event and when you perform an activation.`); 58 | // mensagem.push(`After the event, no messages will be sent from Twilio or the event organization.`); 59 | break; 60 | } 61 | 62 | callback(null, { 63 | mensagem: mensagem.join('\n\n') 64 | }); 65 | 66 | }; 67 | -------------------------------------------------------------------------------- /eventos/functions/add-linkedin.protected.js: -------------------------------------------------------------------------------- 1 | const admin = require('firebase-admin'); 2 | process.env.GOOGLE_APPLICATION_CREDENTIALS = Runtime.getAssets()['/firebase-credentials.json'].path; 3 | const { escondeNumero, limpaNumero, getDDD, sendNotification, convertNewLine, fillParams } = require(Runtime.getFunctions()['util'].path); 4 | 5 | if (!admin.apps.length) { 6 | admin.initializeApp({}); 7 | } else { 8 | admin.app(); 9 | } 10 | const firestore = admin.firestore(); 11 | const md5 = require('md5'); 12 | 13 | /* 14 | event: evento, from, linkedin, to 15 | */ 16 | exports.handler = async function(context, event, callback) { 17 | let participanteId = await md5(limpaNumero(event.from)); 18 | let idPlayerEvent = await md5(`${event.evento}:${limpaNumero(event.from)}`); 19 | 20 | let mensagem = []; 21 | 22 | console.log('ADICIONANDO LINKEDIN', event); 23 | 24 | await firestore.collection('events') 25 | .doc(event.evento).collection('participantes') 26 | .doc(idPlayerEvent).set({ 27 | idPlayerEvent, 28 | participanteId, 29 | updatedAt: admin.firestore.FieldValue.serverTimestamp(), 30 | phoneNumber: limpaNumero(event.from), 31 | ativouNetworking: true, 32 | game: true, 33 | linkedin: event.linkedin 34 | }, { merge: true }); 35 | 36 | 37 | await firestore.collection('participantes') 38 | .doc(participanteId).set({ 39 | idPlayerEvent, 40 | updatedAt: admin.firestore.FieldValue.serverTimestamp(), 41 | phoneNumber: limpaNumero(event.from), 42 | ativouNetworking: true, 43 | game: true, 44 | linkedin: event.linkedin, 45 | ultimoEvento: event.evento 46 | }, { merge: true }); 47 | 48 | // mensagem.push(`Seu perfil do linkedin foi registrado com sucesso!`); 49 | // mensagem.push(`Você pode mostrar este QRCode para se conectar com participantes do evento.`) 50 | mensagem.push(`Para imprimir seu QRCode, vá até o estande da Twilio e peça a impressão.`); 51 | mensagem.push(``); 52 | mensagem.push(``); 53 | mensagem.push(`Envie uma selfie no evento para gerar um video dinâmico com a Videomatik.`); 54 | mensagem.push(`Se passar no estande você pode ganhar brindes.`); 55 | 56 | 57 | // TODO: gerar QRCode. 58 | 59 | // increment stats 60 | await firestore.collection('events') 61 | .doc(event.evento).set({ 62 | stats: { 63 | pessoas: admin.firestore.FieldValue.increment(1) 64 | } 65 | }, { merge: true }); 66 | 67 | 68 | 69 | let media = `https://chart.googleapis.com/chart?chs=500x500&cht=qr&chl=https://wa.me/551150393737?text=${idPlayerEvent}`; 70 | 71 | 72 | callback(null, { 73 | mensagem: mensagem.join('\n\n'), 74 | media 75 | }); 76 | 77 | }; 78 | -------------------------------------------------------------------------------- /eventos/functions/emite-certificado.protected.js: -------------------------------------------------------------------------------- 1 | const admin = require('firebase-admin'); 2 | process.env.GOOGLE_APPLICATION_CREDENTIALS = Runtime.getAssets()['/firebase-credentials.json'].path; 3 | const { escondeNumero, limpaNumero, getDDD, convertNewLine } = require(Runtime.getFunctions()['util'].path); 4 | 5 | if (!admin.apps.length) { 6 | admin.initializeApp({}); 7 | } else { 8 | admin.app(); 9 | } 10 | const firestore = admin.firestore(); 11 | const md5 = require('md5'); 12 | 13 | 14 | exports.handler = async function(context, event, callback) { 15 | const client = await context.getTwilioClient(); 16 | const EVENTO = 'frontin'; 17 | 18 | let mensagem = ''; 19 | let mediaUrl = ''; 20 | 21 | console.log('EVENT >>>>>', event); 22 | 23 | if (event.email) { 24 | 25 | let certificado = await firestore.collection('events').doc(EVENTO).collection('certificado').doc(event.email).get().then(s => { 26 | if (s.exists) { 27 | return s.data(); 28 | } 29 | return null; 30 | }); 31 | 32 | if (certificado) { 33 | let participanteId = await md5(limpaNumero(event.from)); 34 | console.log('Certificado para ', participanteId); 35 | 36 | // adicionar registro de certificado 37 | await firestore.collection('events').doc(EVENTO).collection('certificado').doc(event.email).set({ 38 | certificadoImpresso: true, 39 | certificadoEmitidoEm: admin.firestore.FieldValue.serverTimestamp(), 40 | participanteId 41 | }, { merge: true }); 42 | 43 | // informar no evento que participante emitiu certificado 44 | await firestore.collection('events').doc(EVENTO).collection('participantes').doc(participanteId).set({ 45 | certificado: true, 46 | certificadoImpressoes: admin.firestore.FieldValue.increment(1), 47 | certificadoEmitidoEm: admin.firestore.FieldValue.serverTimestamp(), 48 | certificadoNome: certificado.name 49 | }, { merge: true }); 50 | 51 | 52 | mensagem = `Seu certificado será emitido para *${certificado.name}*`; 53 | mediaUrl = `https://certificado-5264.twil.io/pdf?nomeCor=%233ed427&nomeUpperCase=true&nome=${encodeURI(certificado.name)}&completo=false&palestra=${process.env.EVENTO_NOME}&duracao=${process.env.EVENTO_DURACAO}&evento=${process.env.EVENTO_NOME}&imagem=${process.env.EVENTO_IMAGEM_CERTIFICADO}`; 54 | 55 | 56 | } else { 57 | mensagem = 'O e-mail informado não foi informado\n\nComece o processo novamente por favor.' 58 | 59 | } 60 | 61 | } else { 62 | mensagem = 'Você deve informar um e-mail para poder emitir o certificado\n\nComece o processo novamente por favor.' 63 | } 64 | 65 | 66 | 67 | 68 | return callback(null, { 69 | mensagem, 70 | mediaUrl 71 | }); 72 | } 73 | -------------------------------------------------------------------------------- /eventos/functions/add-pontos.protected.js: -------------------------------------------------------------------------------- 1 | const admin = require('firebase-admin'); 2 | process.env.GOOGLE_APPLICATION_CREDENTIALS = Runtime.getAssets()['/firebase-credentials.json'].path; 3 | const { escondeNumero, limpaNumero, getDDD, sendNotification, convertNewLine, fillParams } = require(Runtime.getFunctions()['util'].path); 4 | 5 | if (!admin.apps.length) { 6 | admin.initializeApp({}); 7 | } else { 8 | admin.app(); 9 | } 10 | const firestore = admin.firestore(); 11 | const md5 = require('md5'); 12 | 13 | /* 14 | event: evento, to, token, points 15 | */ 16 | exports.handler = async function(context, event, callback) { 17 | const client = await context.getTwilioClient(); 18 | 19 | let participanteId = event.token; 20 | 21 | const points = event.points * -1; 22 | 23 | await firestore.collection('events') 24 | .doc(event.evento).collection('participantes') 25 | .doc(participanteId).set({ 26 | pontosCorrente: admin.firestore.FieldValue.increment(points), 27 | pontosAcumulados: admin.firestore.FieldValue.increment(points > 0 ? points : 0), 28 | updatedAt: admin.firestore.FieldValue.serverTimestamp() 29 | 30 | }, { merge: true}); 31 | 32 | 33 | let participante = await firestore.collection('events') 34 | .doc(event.evento).collection('participantes') 35 | .doc(participanteId).get().then(async s => { 36 | if (s.exists) { 37 | return s.data(); 38 | } else { 39 | return { 40 | pontosAcumulados: 0, 41 | pontosCorrente: 0, 42 | }; 43 | } 44 | }); 45 | 46 | // Enviar mensagem informando que adicionou ou removeu pontos 47 | let mensagemPt = []; 48 | mensagemPt.push(`${Math.abs(points)} ${Math.abs(points) > 1 ? 'pontos foram' : 'ponto foi'} ${points > 0 ? 'adicionado' : 'descontado'}${Math.abs(points) > 1 ? 's' : ''}!`); 49 | mensagemPt.push([ 50 | `Saldo de pontos é *${participante.pontosCorrente} ${participante.pontosCorrente > 1 ? 'pontos' : 'ponto'}*.`, 51 | `Total de pontos acumulados: ${participante.pontosAcumulados}.` 52 | ].join('\n')); 53 | 54 | let mensagemEn = []; 55 | mensagemEn.push(`${Math.abs(points)} ${Math.abs(points) > 1 ? 'points' : 'points'} ${points > 0 ? 'added' : 'withdrawn'}!`); 56 | mensagemEn.push([ 57 | `Balance: *${participante.pontosCorrente} ${participante.pontosCorrente > 1 ? 'points' : 'point'}*.`, 58 | `Accumulated: ${participante.pontosAcumulados}.` 59 | ].join('\n')); 60 | 61 | if (participante.phoneNumber) { 62 | await sendNotification( 63 | client, 64 | event.to, 65 | `whatsapp:${participante.phoneNumber}`, 66 | mensagemPt.join('\n\n') 67 | ); 68 | } 69 | 70 | let data = { 71 | mensagem: mensagemEn.join('\n\n'), 72 | podeDescontarPontos: participante.pontosCorrente > 0, 73 | pontosCorrente: participante.pontosCorrente, 74 | pontosAcumulados: participante.pontosAcumulados, 75 | participante 76 | }; 77 | 78 | callback(null, data); 79 | 80 | }; 81 | -------------------------------------------------------------------------------- /eventos/assets/css/orders.css: -------------------------------------------------------------------------------- 1 | 2 | ul { 3 | margin: 1em; 4 | padding: 0; 5 | /* list-style-type: none; */ 6 | } 7 | 8 | li { 9 | clear: both; 10 | font-size: 1.5em; 11 | display: block; 12 | padding: .5em; 13 | margin-bottom: 1em; 14 | } 15 | li span { 16 | /* display: inline-block; */ 17 | } 18 | 19 | .cleatBoth { 20 | clear: both; 21 | } 22 | 23 | button { 24 | font-size: .8em; 25 | /* float: right; */ 26 | margin: 0 0 0 0.5em; 27 | display: inline-block; 28 | } 29 | 30 | 31 | 32 | li.pendente .pronto, 33 | li.preparo .chamar, 34 | li.preparo .preparar, 35 | li.preparo .cancelar 36 | { 37 | display: none; 38 | } 39 | 40 | li.pendente { 41 | background-color: #f37466; 42 | } 43 | li.preparo { 44 | background-color: #fcf47c; 45 | } 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | body { 57 | font-family: "Fira Sans Condensed", sans-serif; 58 | margin: 0; 59 | padding: 1em; 60 | background-color: #f0f0f0; 61 | } 62 | 63 | 64 | 65 | #login-form, #filter { 66 | margin: 20px; 67 | padding: 20px; 68 | background-color: #fff; 69 | border-radius: 8px; 70 | } 71 | 72 | label { 73 | display: block; 74 | margin-bottom: 8px; 75 | } 76 | 77 | input, select, button { 78 | /* width: 100%; */ 79 | /* margin-bottom: 16px; */ 80 | 81 | padding: 8px; 82 | border: 1px solid #ccc; 83 | border-radius: 4px; 84 | } 85 | 86 | button { 87 | /* background-color: #4caf50; 88 | color: #fff; */ 89 | cursor: pointer; 90 | } 91 | 92 | button:hover { 93 | background-color: #45a049; 94 | } 95 | 96 | #logout-btn { 97 | background-color: #f44336; 98 | } 99 | 100 | #logout-btn:hover { 101 | background-color: #d32f2f; 102 | } 103 | 104 | .hidden { 105 | display: none !important; 106 | } 107 | 108 | 109 | /* Adaptações para dispositivos móveis */ 110 | @media only screen and (max-width: 600px) { 111 | #login-form, #filter, #request-list { 112 | margin: 10px; 113 | padding: 10px; 114 | } 115 | 116 | button { 117 | width: auto; 118 | } 119 | } 120 | 121 | 122 | /* Adicionei estilos para a lista de perguntas */ 123 | .request { 124 | display: grid; 125 | grid-template-columns: 1fr 150px; /* Duas colunas, a primeira com tamanho automático e a segunda com 100px */ 126 | gap: 10px; /* Espaçamento entre as colunas */ 127 | margin-bottom: 10px; /* Espaçamento entre as perguntas */ 128 | } 129 | 130 | /* Adicionei estilos para a caixa de seleção */ 131 | .status-selector select { 132 | width: 100%; /* Ajustar a largura para preencher a coluna */ 133 | } 134 | 135 | 136 | /* drinks, coffee, mug */ 137 | 138 | body.drinks li.mug, 139 | body.drinks li.coffee, 140 | body.mug li.drinks, 141 | body.mug li.coffee, 142 | body.coffee li.drinks, 143 | body.coffee li.mug 144 | { 145 | display: none; 146 | } 147 | 148 | 149 | 150 | .mug .picture img { 151 | max-height: 150px; 152 | } 153 | 154 | .mug .code { 155 | font-size: 2em 156 | } 157 | 158 | .mug .text { 159 | background-color: white; 160 | width: 100%; 161 | padding: 10pt; 162 | } 163 | 164 | .drink .item { 165 | font-weight: bold; 166 | font-size: 1.2em; 167 | } -------------------------------------------------------------------------------- /scripts/importa-csv-email.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | require("dotenv").config(); 3 | 4 | 5 | // Initialize Firebase 6 | var admin = require("firebase-admin"); 7 | 8 | if (!admin.apps.length) { 9 | admin.initializeApp(); 10 | }else { 11 | admin.app(); // if already initialized, use that one 12 | } 13 | const firestore = admin.firestore(); 14 | 15 | const csv = require('csvtojson'); 16 | 17 | 18 | let eventos = {}; 19 | let evento = null; 20 | 21 | const importFile = async (arquivo) => { 22 | console.log('Importando arquivo: ', arquivo); 23 | 24 | 25 | 26 | 27 | 28 | const lista = [...new Set(await csv() 29 | .fromFile(arquivo) 30 | .then((lista) => { 31 | return lista; //lista.map(e => e.Email) 32 | }))]; 33 | 34 | console.log(`Encontrei ${lista.length} registros!`); 35 | console.log(`Adicionando no banco de dados do evento "${evento.key}"...`); 36 | let totalAdded = 0; 37 | 38 | for (registro of lista) { 39 | // console.log(registro) 40 | 41 | let participante = { 42 | csvUpdatedAt: admin.firestore.FieldValue.serverTimestamp(), 43 | syncVersion: admin.firestore.FieldValue.increment(1) 44 | }; 45 | 46 | const allowedElements = ['nome', 'email', 'firstName', 'lastName', 'empresa', 'cargo']; 47 | Object.keys(allowedElements).map((e, idx) => { 48 | if (registro[allowedElements[e]]) { 49 | participante[allowedElements[e]] = registro[allowedElements[e]] 50 | } 51 | }); 52 | 53 | await firestore.collection('events').doc(evento.key).collection('emails') 54 | .doc(participante.email.toLowerCase().trim()).set({ 55 | ...participante 56 | }, { merge: true }).then( writeResult => { 57 | console.log('Adicionado ', participante.email); 58 | totalAdded++; 59 | }); 60 | 61 | 62 | } 63 | console.log( `${totalAdded} registro(s) adicionado(s).`); 64 | 65 | } 66 | 67 | const init = async () => { 68 | console.log('Iniciando importação de registro de CSV...'); 69 | 70 | // Carregar evento ativo e default 71 | const eventos = await firestore.collection('events') 72 | .where('default', '==', true) 73 | .where('active', '==', true) 74 | .get() 75 | .then(snapshot => { 76 | return snapshot.docs.map(doc => { 77 | return { key: doc.id, ...doc.data() } 78 | }); 79 | }); 80 | 81 | if (eventos.length == 0) { 82 | console.log('ERRO: nenhum evento ativo e default encontrado!'); 83 | return 84 | } 85 | evento = eventos[0]; 86 | 87 | // evento.key 88 | console.log('Utilizando evento', evento.key); 89 | 90 | // Lista arquivos .CSV da pasta /csv 91 | fs.readdir('./csv', async (err, files) => { 92 | let csvs = files.filter(f => f.indexOf('.csv') >= 0) 93 | if (csvs.length > 0) { 94 | importFile(`./csv/${csvs[0]}`); 95 | } else { 96 | console.log('NENHUM ARQUIVO .CSV ENCONTRADO'); 97 | } 98 | }); 99 | 100 | }; 101 | 102 | init(); -------------------------------------------------------------------------------- /eventos/functions/verifica-sorteio-palavra.protected.js: -------------------------------------------------------------------------------- 1 | const admin = require('firebase-admin'); 2 | process.env.GOOGLE_APPLICATION_CREDENTIALS = Runtime.getAssets()['/firebase-credentials.json'].path; 3 | const { escondeNumero, limpaNumero, getDDD, sendNotification, convertNewLine, fillParams } = require(Runtime.getFunctions()['util'].path); 4 | 5 | if (!admin.apps.length) { 6 | admin.initializeApp({}); 7 | } else { 8 | admin.app(); 9 | } 10 | const firestore = admin.firestore(); 11 | 12 | /* 13 | event: evento, palavra 14 | */ 15 | exports.handler = async function(context, event, callback) { 16 | 17 | // TODO: verificar se palavra-chave existe 18 | // TODO: retornar status e totais 19 | 20 | if (!event.palavra || event.palavra.length == 0) { 21 | let data = { 22 | valida: false, 23 | mensagem: '' 24 | }; 25 | 26 | callback(null, data); 27 | } 28 | event.palavra = event.palavra.toLowerCase(); 29 | 30 | // let participanteId = await md5(limpaNumero(event.from)); 31 | const sorteio = await firestore.collection('events') 32 | .doc(event.evento).collection('sorteios') 33 | .doc(event.palavra) 34 | .get().then(e => { 35 | if (!e.exists) { 36 | return null 37 | } 38 | return e.data(); 39 | }); 40 | 41 | // let participanteGeral = await firestore.collection('participantes') 42 | // .doc(participanteId).get().then(async s => { 43 | // if (s.exists) { 44 | // return s.data(); 45 | // } else { 46 | // return {} 47 | // } 48 | // }); 49 | const valida = sorteio != null; 50 | 51 | let mensagem = []; 52 | mensagem.push(`*${event.palavra.toUpperCase()}*`); 53 | // mensagem.push(``); 54 | 55 | if (sorteio) { 56 | const inscricoes = await firestore.collection('events') 57 | .doc(event.evento).collection('sorteios') 58 | .doc(event.palavra).collection('participantes') 59 | .get().then(s => s.size); 60 | 61 | const sorteados = await firestore.collection('events') 62 | .doc(event.evento).collection('sorteios') 63 | .doc(event.palavra).collection('participantes') 64 | .where('sorteado', '==', true) 65 | .get().then(s => s.size); 66 | 67 | mensagem.push([ 68 | `Status: ${sorteio.status ? sorteio.status.toUpperCase() : 'PENDENTE' }`, 69 | `Inscrições: ${inscricoes}`, 70 | `Pessoas sorteadas: ${sorteados}`, 71 | ].join('\n')); 72 | 73 | mensagem.push([ 74 | `O que deseja fazer?`, 75 | `SORTEAR, ${sorteio.status == 'aberto' ? 'FECHAR' : 'ABRIR'}, LISTA, SAIR` 76 | ].join('\n')); 77 | 78 | } else { 79 | mensagem.push(`ESTA PALAVRA-CHAVE DE SORTEIO NÃO EXISTE!`); 80 | } 81 | 82 | 83 | // *{{widgets.palavra.inbound.Body | upcase}}* 84 | 85 | // Status: SORTEIO ABERTO 86 | // Inscrições: 0 87 | // Pessoas Sorteadas: 0 88 | 89 | // O que deseja fazer? 90 | // SORTEAR, ABRIR/FECHAR, SAIR 91 | 92 | 93 | let data = { 94 | valida, 95 | mensagem: mensagem.join('\n\n') 96 | }; 97 | 98 | callback(null, data); 99 | }; 100 | -------------------------------------------------------------------------------- /eventos/functions/fotografia-atende.js: -------------------------------------------------------------------------------- 1 | const admin = require('firebase-admin'); 2 | process.env.GOOGLE_APPLICATION_CREDENTIALS = Runtime.getAssets()['/firebase-credentials.json'].path; 3 | const { escondeNumero, limpaNumero, getDDD, sendNotification, convertNewLine, fillParams } = require(Runtime.getFunctions()['util'].path); 4 | 5 | if (!admin.apps.length) { 6 | admin.initializeApp({}); 7 | } else { 8 | admin.app(); 9 | } 10 | const firestore = admin.firestore(); 11 | 12 | /* 13 | event: filaId, pasta 14 | */ 15 | exports.handler = async function(context, event, callback) { 16 | 17 | const participante = await firestore.collection('participantes') 18 | .doc(event.filaId).get().then(s => { 19 | if (s.exists) { 20 | return s.data(); 21 | } else { 22 | return null; 23 | } 24 | }); 25 | 26 | if (!participante) { 27 | 28 | return callback(null, { 29 | erro: true, 30 | mensagem: 'Participante não encontrado!\n\nInforme para responsável da Twilio.' 31 | }); 32 | 33 | } 34 | 35 | 36 | // Mudar status para 'atendimento' 37 | await firestore.collection('events') 38 | .doc(participante.ultimoEvento).collection('agendamento') 39 | .doc(event.filaId).set({ 40 | updatedAt: admin.firestore.FieldValue.serverTimestamp(), 41 | status: 'atendimento' 42 | }, { merge: true }); 43 | 44 | 45 | 46 | // TODO: não criar pasta se já existir uma no banco de agendamentos 47 | 48 | 49 | // Criar pasta do drive 50 | // const { GoogleAuth } = require('google-auth-library'); 51 | // const { google } = require('googleapis'); 52 | 53 | // const auth = new GoogleAuth({ scopes: 'https://www.googleapis.com/auth/drive' }); 54 | // const service = google.drive({ version: 'v3', auth }); 55 | // const fileMetadata = { 56 | // 'name': `${escondeNumero(participante.phoneNumber)} ${participante.nome}`, 57 | // 'mimeType': 'application/vnd.google-apps.folder', 58 | // 'parents': [ process.env.FOTOGRAFIA_PASTA_RAIZ ] 59 | // }; 60 | 61 | // // try { 62 | // const file = await service.files.create({ 63 | // resource: fileMetadata, 64 | // fields: 'id', 65 | // }); 66 | 67 | await firestore.collection('events') 68 | .doc(participante.ultimoEvento).collection('agendamento') 69 | .doc(event.filaId).set({ 70 | updatedAt: admin.firestore.FieldValue.serverTimestamp(), 71 | // pasta: event.pasta //file.data.id 72 | }, { merge: true }); 73 | 74 | await firestore.collection('events') 75 | .doc(participante.ultimoEvento).collection('participantes') 76 | .doc(event.filaId).set({ 77 | updatedAt: admin.firestore.FieldValue.serverTimestamp(), 78 | // pasta: file.data.id 79 | }, { merge: true }); 80 | 81 | 82 | return callback(null, { 83 | erro: false, 84 | mensagem: 'Atendimento iniciado com sucesso!', 85 | // pasta: file.data.id 86 | }); 87 | 88 | // } catch (err) { 89 | // return callback(null, { 90 | // erro: true, 91 | // mensagem: `Ocorreu um erro ao criar a pasta!\n\n${JSON.stringify(err)}`, 92 | // }); 93 | // } 94 | 95 | }; 96 | -------------------------------------------------------------------------------- /eventos/assets/javascript/tela.js: -------------------------------------------------------------------------------- 1 | import * as firebase from "https://www.gstatic.com/firebasejs/9.18.0/firebase-app.js"; 2 | import { getFirestore, onSnapshot, collection, query, where, orderBy, limit, doc, getDoc } from "https://www.gstatic.com/firebasejs/9.18.0/firebase-firestore.js"; 3 | 4 | const CURRENT_EVENT = 'tdcbusiness2023'; 5 | 6 | const firebaseConfig = { 7 | apiKey: "AIzaSyC6I8Rwo9YiJgd7zK-U5RpHBZDSPsDt0ME", 8 | authDomain: "br-events.firebaseapp.com", 9 | projectId: "br-events", 10 | storageBucket: "br-events.appspot.com", 11 | messagingSenderId: "603831187236", 12 | appId: "1:603831187236:web:b1b3e97f5c6a38a4eb25b1" 13 | }; 14 | 15 | const app = firebase.initializeApp(firebaseConfig); 16 | const db = getFirestore(app); 17 | 18 | const params = new Proxy(new URLSearchParams(window.location.search), { 19 | get: (searchParams, prop) => searchParams.get(prop), 20 | }); 21 | 22 | 23 | window.display = async function() { 24 | console.log('params.id', params.id); 25 | 26 | const trilhaSnap = await getDoc(doc(db, "events", CURRENT_EVENT, "trilhas", params.id)); 27 | 28 | const trilhaElement = document.getElementById('trilhaNome'); 29 | if (trilhaSnap.exists()) { 30 | trilhaElement.innerText = trilhaSnap.data().trilhaNome; 31 | } else { 32 | trilhaElement.innerText = 'Nenhuma palestra ativa nesta trilha.' 33 | } 34 | 35 | const q = query( 36 | collection(db, "events", CURRENT_EVENT, "palestras"), 37 | where("trilhaId", "==", params.id), 38 | where("status", "==", "Aberto"), 39 | orderBy("updatedAt", "desc"), 40 | limit(1)); 41 | 42 | onSnapshot(q, (querySnapshot) => { 43 | console.log("total de palestras: ", querySnapshot.size); 44 | const mainContainer = document.getElementById("container"); 45 | const emptyContainer = document.getElementById("empty"); 46 | const main = document.getElementById("main-container"); 47 | const brandContainer = document.getElementById("brand-container"); 48 | 49 | if (querySnapshot.size === 0) { 50 | mainContainer.classList.add("hidden"); 51 | emptyContainer.classList.remove("hidden"); 52 | main.classList.remove("hidden"); 53 | brandContainer.classList.add("hidden"); 54 | return; 55 | } 56 | 57 | const palestra = querySnapshot.docs[0].data(); 58 | const idPalestra = querySnapshot.docs[0].id; 59 | 60 | const dynamicCode = document.getElementById("dynamic-code"); 61 | dynamicCode.innerHTML = `${idPalestra}` 62 | 63 | const talkTitle = document.getElementById("talk-title"); 64 | talkTitle.innerHTML = palestra.titulo; 65 | 66 | const trilha = document.getElementById("trilha"); 67 | trilha.innerHTML = `${palestra.trilhaNome}`; 68 | 69 | const url = `https://chart.googleapis.com/chart?chs=500x500&cht=qr&chl=https://wa.me/551150393737?text=${idPalestra}` 70 | const currentQrCode = document.getElementById("current-qrcode"); 71 | if (currentQrCode) { 72 | currentQrCode.remove(); 73 | } 74 | const codeContainer = document.getElementById("image-content-qr"); 75 | codeContainer.insertAdjacentHTML("afterbegin", `whatsapp-logo-with-twilio`) 76 | emptyContainer.classList.add("hidden"); 77 | main.classList.remove("hidden"); 78 | mainContainer.classList.remove("hidden"); 79 | brandContainer.classList.remove("hidden"); 80 | }); 81 | } -------------------------------------------------------------------------------- /eventos/.twilioserverlessrc: -------------------------------------------------------------------------------- 1 | { 2 | "commands": {}, 3 | "environments": {}, 4 | "projects": {}, 5 | // "assets": true /* Upload assets. Can be turned off with --no-assets */, 6 | // "assetsFolder": null /* Specific folder name to be used for static assets */, 7 | // "buildSid": null /* An existing Build SID to deploy to the new environment */, 8 | // "createEnvironment": false /* Creates environment if it couldn't find it. */, 9 | // "cwd": null /* Sets the directory of your existing Serverless project. Defaults to current directory */, 10 | // "detailedLogs": false /* Toggles detailed request logging by showing request body and query params */, 11 | // "edge": null /* Twilio API Region */, 12 | // "env": null /* Path to .env file for environment variables that should be installed */, 13 | // "environment": "dev" /* The environment name (domain suffix) you want to use for your deployment. Alternatively you can specify an environment SID starting with ZE. */, 14 | // "extendedOutput": false /* Show an extended set of properties on the output */, 15 | // "force": false /* Will run deployment in force mode. Can be dangerous. */, 16 | // "forkProcess": true /* Disable forking function processes to emulate production environment */, 17 | // "functionSid": null /* Specific Function SID to retrieve logs for */, 18 | // "functions": true /* Upload functions. Can be turned off with --no-functions */, 19 | // "functionsFolder": null /* Specific folder name to be used for static functions */, 20 | // "inspect": null /* Enables Node.js debugging protocol */, 21 | // "inspectBrk": null /* Enables Node.js debugging protocol, stops execution until debugger is attached */, 22 | // "legacyMode": false /* Enables legacy mode, it will prefix your asset paths with /assets */, 23 | // "live": true /* Always serve from the current functions (no caching) */, 24 | // "loadLocalEnv": false /* Includes the local environment variables */, 25 | // "loadSystemEnv": false /* Uses system environment variables as fallback for variables specified in your .env file. Needs to be used with --env explicitly specified. */, 26 | // "logCacheSize": null /* Tailing the log endpoint will cache previously seen entries to avoid duplicates. The cache is topped at a maximum of 1000 by default. This option can change that. */, 27 | // "logLevel": "info" /* Level of logging messages. */, 28 | // "logs": true /* Toggles request logging */, 29 | // "ngrok": null /* Uses ngrok to create a public url. Pass a string to set the subdomain (requires a paid-for ngrok account). */, 30 | // "outputFormat": "" /* Output the results in a different format */, 31 | // "overrideExistingProject": false /* Deploys Serverless project to existing service if a naming conflict has been found. */, 32 | // "port": "3000" /* Override default port of 3000 */, 33 | // "production": false /* Promote build to the production environment (no domain suffix). Overrides environment flag */, 34 | // "properties": null /* Specify the output properties you want to see. Works best on single types */, 35 | // "region": null /* Twilio API Region */, 36 | "runtime": "node16" /* The version of Node.js to deploy the build to. (node16) */, 37 | // "serviceName": null /* Overrides the name of the Serverless project. Default: the name field in your package.json */, 38 | // "serviceSid": null /* SID of the Twilio Serverless Service to deploy to */, 39 | // "sourceEnvironment": null /* SID or suffix of an existing environment you want to deploy from. */, 40 | // "tail": false /* Continuously stream the logs */, 41 | // "template": null /* undefined */, 42 | } -------------------------------------------------------------------------------- /eventos/assets/javascript/gerenciar.js: -------------------------------------------------------------------------------- 1 | import * as firebase from "https://www.gstatic.com/firebasejs/9.18.0/firebase-app.js"; 2 | import { getFirestore, onSnapshot, collection, query, where, orderBy, doc as docFunc, setDoc, FieldValue } from "https://www.gstatic.com/firebasejs/9.18.0/firebase-firestore.js"; 3 | 4 | const CURRENT_EVENT = 'tdcbusiness2023'; 5 | 6 | const firebaseConfig = { 7 | apiKey: "AIzaSyC6I8Rwo9YiJgd7zK-U5RpHBZDSPsDt0ME", 8 | authDomain: "br-events.firebaseapp.com", 9 | projectId: "br-events", 10 | storageBucket: "br-events.appspot.com", 11 | messagingSenderId: "603831187236", 12 | appId: "1:603831187236:web:b1b3e97f5c6a38a4eb25b1" 13 | }; 14 | 15 | const app = firebase.initializeApp(firebaseConfig); 16 | const db = getFirestore(app); 17 | 18 | const params = new Proxy(new URLSearchParams(window.location.search), { 19 | get: (searchParams, prop) => searchParams.get(prop), 20 | }); 21 | 22 | const trilhaId = params.id; 23 | 24 | const trails = document.getElementById("trail-talks"); 25 | const q = query( 26 | collection(db, "events", CURRENT_EVENT, "palestras"), 27 | where("trilhaId", "==", trilhaId), 28 | orderBy("horario", "asc") 29 | ); 30 | 31 | window.trigger = function() { 32 | onSnapshot(q, (querySnapshot) => { 33 | trails.innerHTML = ""; 34 | if (querySnapshot.docs.length > 0) { 35 | // TODO: adicionar nome da trilha no topo 36 | document.getElementById("trail-name").innerText = querySnapshot.docs[0].data().trilhaNome; 37 | } 38 | querySnapshot.forEach((doc) => { 39 | const palestra = doc.data(); 40 | const li = document.createElement("li"); 41 | // Add button to update database 42 | li.innerHTML = ` 43 | 44 | 45 | ${palestra.feedbacks ? '[' + palestra.feedbacks + ' > ' + (palestra.totalPoints / palestra.feedbacks).toFixed(2) + ']' : ''} ${palestra.horario}: ${palestra.titulo} 46 | (${doc.id}) 47 | `; 48 | li.setAttribute("data-id", doc.id); 49 | trails.appendChild(li); 50 | 51 | const abrirPalestra = document.getElementById(`abrir-palestra-${doc.id}`); 52 | abrirPalestra.addEventListener("click", (e) => { 53 | const palestraRef = docFunc(db, "events", CURRENT_EVENT, "palestras", doc.id); 54 | setDoc(palestraRef, { 55 | status: "Aberto", 56 | // updatedAt: FieldValue.serverTimestamp() 57 | }, { 58 | merge: true 59 | }); 60 | }); 61 | const fecharPalestra = document.getElementById(`fechar-doc-${doc.id}`); 62 | fecharPalestra.addEventListener("click", (e) => { 63 | const palestraRef = docFunc(db, "events", CURRENT_EVENT, "palestras", doc.id); 64 | setDoc(palestraRef, { 65 | status: "Fechado", 66 | // updatedAt: FieldValue.serverTimestamp() 67 | }, { 68 | merge: true 69 | }); 70 | }); 71 | if (palestra.status === "Aberto") { 72 | abrirPalestra.disabled = true; 73 | fecharPalestra.disabled = false; 74 | } 75 | if (palestra.status === "Fechado") { 76 | abrirPalestra.disabled = false; 77 | fecharPalestra.disabled = true; 78 | } 79 | }); 80 | }); 81 | } -------------------------------------------------------------------------------- /eventos/functions/add-sorteio-palavra.protected.js: -------------------------------------------------------------------------------- 1 | const admin = require('firebase-admin'); 2 | process.env.GOOGLE_APPLICATION_CREDENTIALS = Runtime.getAssets()['/firebase-credentials.json'].path; 3 | const { escondeNumero, limpaNumero, getDDD, sendNotification, convertNewLine, fillParams } = require(Runtime.getFunctions()['util'].path); 4 | 5 | if (!admin.apps.length) { 6 | admin.initializeApp({}); 7 | } else { 8 | admin.app(); 9 | } 10 | const firestore = admin.firestore(); 11 | const md5 = require('md5'); 12 | 13 | /* 14 | event: evento, palavra, from 15 | */ 16 | exports.handler = async function(context, event, callback) { 17 | 18 | let participanteId = await md5(limpaNumero(event.from)); 19 | let idPlayerEvent = await md5(`${event.evento}:${limpaNumero(event.from)}`); 20 | 21 | event.palavra = event.palavra.toLowerCase(); 22 | 23 | let mensagem = []; 24 | 25 | const sorteio = await firestore.collection('events') 26 | .doc(event.evento).collection('sorteios') 27 | .doc(event.palavra) 28 | .get().then(p => { 29 | if (p.exists) { 30 | return p.data() 31 | } 32 | return null 33 | }); 34 | 35 | if (sorteio) { 36 | 37 | // Verificar se sorteio esta aberto 38 | if (sorteio.status == 'aberto') { 39 | const inscrito = await firestore.collection('events') 40 | .doc(event.evento).collection('sorteios') 41 | .doc(event.palavra).collection('participantes') 42 | .doc(idPlayerEvent) 43 | .get().then(p => p.exists); 44 | 45 | if (!inscrito) { 46 | // Inscrever 47 | 48 | let participante = await firestore.collection('events') 49 | .doc(event.evento).collection('participantes') 50 | .doc(idPlayerEvent).get().then(async s => { 51 | if (s.exists) { 52 | return s.data(); 53 | } 54 | return { 55 | nome: 'Sem nome informado' 56 | } 57 | }); 58 | 59 | await firestore.collection('events') 60 | .doc(event.evento).collection('sorteios') 61 | .doc(event.palavra).collection('participantes') 62 | .doc(idPlayerEvent) 63 | .set({ 64 | participanteId, 65 | idPlayerEvent, 66 | telefone: escondeNumero(limpaNumero(event.from)), 67 | nome: participante.nome || '', 68 | profileName: participante.profileName, 69 | sorteado: false, 70 | seed: Math.random(), 71 | createdAt: admin.firestore.FieldValue.serverTimestamp(), 72 | }, { merge: true }); 73 | 74 | await firestore.collection('events') 75 | .doc(event.evento).collection('sorteios') 76 | .doc(event.palavra).set({ 77 | inscricoes: admin.firestore.FieldValue.increment(1) 78 | }, { merge: true}); 79 | 80 | const nomeSorteio = sorteio.nome || ''; 81 | mensagem.push(`🥳 🎉 Inscrição realizada com sucesso para o sorteio *${nomeSorteio.split('').join('\n')}*! 🎉`); 82 | 83 | } else { 84 | // Não deixar inscrever 85 | mensagem.push(`Você já fez sua inscrição para este sorteio!`); 86 | } 87 | 88 | } else { 89 | mensagem.push(`O sorteio informado não está aberto.`); 90 | } 91 | 92 | } else { 93 | mensagem.push(`Palavra-chave de sorteio não foi encontrada!`); 94 | } 95 | 96 | let data = { 97 | mensagem: mensagem.join('\n\n') 98 | }; 99 | 100 | callback(null, data); 101 | }; 102 | -------------------------------------------------------------------------------- /eventos/assets/questions_screen.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Twilio: Questions 7 | 51 | 52 | 53 | 54 |
55 | 56 |
57 | 58 |
59 | 60 |
61 | 62 | 63 | 64 | 65 | 66 | 152 | 153 | -------------------------------------------------------------------------------- /eventos/functions/add-fila-fotografia.protected.js: -------------------------------------------------------------------------------- 1 | const admin = require('firebase-admin'); 2 | process.env.GOOGLE_APPLICATION_CREDENTIALS = Runtime.getAssets()['/firebase-credentials.json'].path; 3 | const { escondeNumero, limpaNumero, getDDD, sendNotification, convertNewLine, fillParams } = require(Runtime.getFunctions()['util'].path); 4 | 5 | if (!admin.apps.length) { 6 | admin.initializeApp({}); 7 | } else { 8 | admin.app(); 9 | } 10 | const firestore = admin.firestore(); 11 | const md5 = require('md5'); 12 | 13 | /* 14 | event: evento, token, from 15 | */ 16 | exports.handler = async function(context, event, callback) { 17 | let participanteId = await md5(limpaNumero(event.from)); 18 | let idPlayerEvent = await md5(`${event.evento}:${limpaNumero(event.from)}`); 19 | 20 | let agendamentoTotal = null; 21 | let agendamentoPosicao = null; 22 | 23 | const participante = await firestore.collection('participantes') 24 | .doc(participanteId).get().then(s => { 25 | if (s.exists) { 26 | return s.data(); 27 | } else { 28 | return null; 29 | } 30 | }); 31 | 32 | 33 | 34 | let ultimo = await firestore.collection('events') 35 | .doc(event.evento).collection('agendamento') 36 | .orderBy('posicao', 'desc').limit(1).get().then(s => { 37 | if (s.size == 0) { 38 | return 0; 39 | } 40 | return s.docs[0].data().posicao; 41 | }); 42 | 43 | let posicao = ultimo + 1; 44 | 45 | let agendamento = await firestore.collection('events') 46 | .doc(event.evento).collection('agendamento') 47 | .doc(idPlayerEvent).set({ 48 | idPlayerEvent, 49 | participanteId, 50 | createdAt: admin.firestore.FieldValue.serverTimestamp(), 51 | posicao: posicao, 52 | status: 'fila', 53 | telefone: escondeNumero(limpaNumero(event.from)), 54 | nome: participante.nome || '-' 55 | }).then(async s => { 56 | return firestore.collection('events') 57 | .doc(event.evento).collection('agendamento') 58 | .doc(participanteId).get().then(r => r.data()); 59 | }); 60 | 61 | console.log('NOVO AGENDAMENTO', agendamento); 62 | 63 | agendamentoTotal = await firestore.collection('events') 64 | .doc(event.evento).collection('agendamento') 65 | .where('status', '==', 'fila') 66 | .get().then(s => { 67 | return s.size; 68 | }); 69 | 70 | agendamentoPosicao = await firestore.collection('events') 71 | .doc(event.evento).collection('agendamento') 72 | .where('status', '==', 'fila') 73 | .where('posicao', '<', agendamento.posicao) 74 | .get().then(s => { 75 | return s.size + 1; 76 | }); 77 | 78 | let mensagem = `Seu registro para foto profissional foi realizado com sucesso!\n\nVocê é a ${agendamentoPosicao}ª pessoa na fila num total de ${agendamentoTotal} ${agendamentoTotal > 1 ? 'pessoas' : 'pessoa'}.` 79 | 80 | let data = { 81 | mensagem, 82 | agendamento, 83 | agendamentoPosicao, 84 | agendamentoTotal 85 | }; 86 | 87 | 88 | // TODO: verificar pontuação de networking 89 | // /events/{eventId}/participantes/{participanteId}/ [PontosAcumulados, PontosCorrente] 90 | // como resoler pontuação igual? 91 | 92 | // TODO: montar mensagem padrão para participante com resumo das informações 93 | 94 | 95 | 96 | // console.log('VERIFICA PALAVRA: ', event); 97 | 98 | // const client = await context.getTwilioClient(); 99 | // if (!event.token) { 100 | // console.log('TOKEN VAZIO'); 101 | // return callback(null, { 102 | // type: 'not-found' 103 | // }); 104 | // } 105 | 106 | // let activation = await firestore.collection('events').doc(event.evento).collection('palavras').doc(event.token).get(); 107 | // if (!activation.exists) { 108 | // activation = await firestore.collection('events').doc(event.evento).collection('palavras').doc(event.token.toLowerCase()).get(); 109 | // if (!activation.exists) { 110 | // // token não encontrado 111 | // console.log(event.token, 'Palavra não existe no evento', event.evento); 112 | // return callback(null, { 113 | // type: 'not-found' 114 | // }); 115 | // } 116 | // } 117 | 118 | console.log('DATA', data); 119 | 120 | 121 | callback(null, data); 122 | 123 | }; 124 | -------------------------------------------------------------------------------- /eventos/functions/videomatik-webhook.js: -------------------------------------------------------------------------------- 1 | const admin = require('firebase-admin'); 2 | process.env.GOOGLE_APPLICATION_CREDENTIALS = Runtime.getAssets()['/firebase-credentials.json'].path; 3 | const { escondeNumero, limpaNumero, getDDD, convertNewLine } = require(Runtime.getFunctions()['util'].path); 4 | 5 | 6 | if (!admin.apps.length) { 7 | admin.initializeApp({}); 8 | } else { 9 | admin.app(); 10 | } 11 | 12 | const VideomatikAPI = require('@videomatik/api'); 13 | const videomatik = new VideomatikAPI({ 14 | apiKey: process.env.VIDEOMATIK_API_KEY, 15 | }); 16 | 17 | 18 | const firestore = admin.firestore(); 19 | const md5 = require('md5'); 20 | 21 | // parâmetros: imagem, from 22 | exports.handler = async function(context, event, callback) { 23 | const client = context.getTwilioClient(); 24 | 25 | console.log('VIDEOMATIK EVENT', event); 26 | const { state, id, videoRequestId, participanteId, downloadURL, from } = event; 27 | 28 | await firestore.collection('videomatik').doc(id).set({ 29 | createdAt: admin.firestore.FieldValue.serverTimestamp(), 30 | videoRequestId, 31 | state 32 | }, { merge: true }); 33 | 34 | const participante = await firestore.collection('participantes').doc(participanteId).get().then(p => { 35 | return p.data(); 36 | }) 37 | 38 | 39 | console.log('resultado participante', participante); 40 | 41 | switch(state) { 42 | case 'render': 43 | case 'rendering': 44 | await client.messages.create({ 45 | from: `whatsapp:${from}`, //`whatsapp:${process.env.TWILIO_WHATSAPP_NUMBER_DEFAULT}`, 46 | to: `whatsapp:${participante.phoneNumber}`, 47 | body: 'Seu vídeo está sendo renderizado agora.' 48 | // body: 'Your video is rendering now.' 49 | }).then(m => { 50 | console.log('RENDER', m.sid); 51 | }); 52 | break; 53 | 54 | case 'finished': 55 | 56 | // Carregar participante 57 | await firestore.collection('videomatik').doc(id).set({ 58 | downloadURL 59 | }, { merge: true }); 60 | 61 | 62 | // Carregar dados do vídeo 63 | // const videoRequest = await videomatik.getOneVideoRequest(videoRequestId); 64 | // console.log(videoRequest.renderJob); 65 | 66 | 67 | // let mensagem = []; 68 | // mensagem.push(`Este vídeo foi feito pela API da Videomatik.`); 69 | // // mensagem.push(`Você sabia que a Videomatik está sorteando uma Alexa Echo Dot para quem testar a API de criar vídeos? Passe no estande e saiba mais!`); 70 | // mensagem.push(``); 71 | // mensagem.push(`Confira e favorite o repositório https://github.com/luisleao/twilio_whatsapp_eventos para ter acesso ao código-fonte!`); 72 | 73 | // // mensagem.push(`This video was made by Videomatik API together with Twilio's WhatsApp API.`); 74 | // // mensagem.push(``); 75 | // // mensagem.push(`If you want to see the code, check out and bookmark this repository: https://github.com/luisleao/twilio_whatsapp_eventos`); 76 | 77 | 78 | // // Envio da mensagem de agradecimento 79 | // await client.messages.create({ 80 | // from: `whatsapp:${from}`, //`whatsapp:${process.env.TWILIO_WHATSAPP_NUMBER_DEFAULT}`, 81 | // to: `whatsapp:${participante.phoneNumber}`, 82 | // body: mensagem.join('\n\n') 83 | // }); 84 | 85 | // Envio do video 86 | await client.messages.create({ 87 | from: `whatsapp:${from}`, //`whatsapp:${process.env.TWILIO_WHATSAPP_NUMBER_DEFAULT}`, 88 | to: `whatsapp:${participante.phoneNumber}`, 89 | // body: 'Here is your video', 90 | body: 'Aqui está seu vídeo!', 91 | mediaUrl: downloadURL //videoRequest.renderJob.downloadURL //`https://leao.ngrok.io/gateway?video=${videoRequest.renderJob.downloadURL}` 92 | }).then(m => { 93 | console.log('FINISHED', m.sid); 94 | }); 95 | 96 | 97 | // Contador Videomatik 98 | try { 99 | const axios = require('axios').default; 100 | await axios.get('https://video-counter.vercel.app/api/increment'); 101 | } catch (e) { 102 | console.log('Axios error'); 103 | } 104 | 105 | // Adicionar pontos 106 | // TODO: add points caso não tenha gerado vídeo ainda 107 | 108 | break; 109 | 110 | } 111 | 112 | return callback(null, 'OK'); 113 | }; -------------------------------------------------------------------------------- /eventos/functions/add-registro.protected.js: -------------------------------------------------------------------------------- 1 | const admin = require('firebase-admin'); 2 | process.env.GOOGLE_APPLICATION_CREDENTIALS = Runtime.getAssets()['/firebase-credentials.json'].path; 3 | const { escondeNumero, limpaNumero, getDDD, convertNewLine } = require(Runtime.getFunctions()['util'].path); 4 | 5 | 6 | if (!admin.apps.length) { 7 | admin.initializeApp({}); 8 | } else { 9 | admin.app(); 10 | } 11 | 12 | const firestore = admin.firestore(); 13 | const md5 = require('md5'); 14 | 15 | exports.handler = async function(context, event, callback) { 16 | const client = await context.getTwilioClient(); 17 | 18 | console.log('ADD REGISTRO: ', event); 19 | 20 | let participanteId = await md5(limpaNumero(event.from)); 21 | let idPlayerEvent = await md5(`${event.evento}:${limpaNumero(event.from)}`); 22 | 23 | let participante = { 24 | phoneNumber: limpaNumero(event.from), 25 | impressoes: 0 26 | } 27 | 28 | 29 | switch(event.modo) { 30 | case 'sympla': 31 | // Verificar se existe no Sympla 32 | const sympla = await firestore.collection('events').doc(event.evento).collection('sympla').doc(event.sympla.toUpperCase()).get(); 33 | if (sympla.exists) { 34 | // Erro - participante já utilizou este codigo 35 | return callback(null, { 36 | erro: true, 37 | mensagem: 'Este código do Sympla já foi utilizado.' 38 | }); 39 | } 40 | break; 41 | 42 | case 'email': 43 | // Verificar se existe no Email 44 | const registroEmail = await firestore.collection('events').doc(event.evento).collection('emails').doc(event.email.toLowerCase()).get(); 45 | if (!registroEmail.exists) { 46 | // Erro - Email não encontrado 47 | return callback(null, { 48 | erro: true, 49 | mensagem: `O e-mail *${event.email.toLowerCase()}* não foi encontrado no sistema.` 50 | }); 51 | } 52 | 53 | const emailData = registroEmail.data(); 54 | if (emailData.usado) { 55 | // Erro - participante já utilizou este emai; 56 | return callback(null, { 57 | erro: true, 58 | mensagem: `O e-mail *${event.email.toLowerCase()}* já foi utilizado em um registro.` 59 | }); 60 | } 61 | Object.assign(participante, {...emailData}); 62 | break; 63 | 64 | case 'manual': 65 | console.log('REGISTRO MANUAL'); 66 | break; 67 | } 68 | 69 | 70 | 71 | // Adicionando parâmetros no participante, independente do modelo 72 | const allowedElements = ['nome', 'pronome', 'sympla', 'linkedin', 'email', 'firstName', 'lastName', 'empresa']; 73 | Object.keys(allowedElements).map((e, idx) => { 74 | console.log('registro > ', allowedElements[e], event[allowedElements[e]]); 75 | if (event[allowedElements[e]]) { 76 | participante[allowedElements[e]] = event[allowedElements[e]] 77 | } 78 | }); 79 | 80 | console.log('DADOS PARA INSERIR: ', participante); 81 | 82 | 83 | 84 | // Salvar o registro do participante 85 | await firestore.collection('events').doc(event.evento).collection('participantes') 86 | .doc(idPlayerEvent).set({ 87 | ...participante, 88 | participanteId, 89 | createdAt: admin.firestore.FieldValue.serverTimestamp() 90 | }, { merge: true }); 91 | 92 | await firestore.collection('participantes') 93 | .doc(participanteId).set({ 94 | ...participante, 95 | idPlayerEvent, 96 | ultimoEvento: event.evento, 97 | updatedAt: admin.firestore.FieldValue.serverTimestamp() 98 | }, { merge: true }); 99 | 100 | // Salvar o registro de uso do E-mail/Sympla 101 | 102 | switch(event.modo) { 103 | case 'sympla': 104 | await firestore.collection('events').doc(event.evento).collection('sympla') 105 | .doc(event.sympla.toUpperCase()).set({ 106 | participanteId: participanteId, 107 | createdAt: admin.firestore.FieldValue.serverTimestamp() 108 | }, { merge: true }); 109 | break; 110 | case 'email': 111 | await firestore.collection('events').doc(event.evento).collection('emails') 112 | .doc(event.email.toLowerCase()).set({ 113 | usado: true 114 | }, { merge: true }); 115 | break; 116 | } 117 | 118 | return callback(null, { 119 | erro: true, 120 | mensagem: convertNewLine(event.mensagemSucesso) 121 | }); 122 | 123 | }; -------------------------------------------------------------------------------- /eventos/functions/add-palestra.protected.js: -------------------------------------------------------------------------------- 1 | // Adicionar feedback palestra 2 | 3 | const admin = require('firebase-admin'); 4 | process.env.GOOGLE_APPLICATION_CREDENTIALS = Runtime.getAssets()['/firebase-credentials.json'].path; 5 | const { escondeNumero, limpaNumero, getDDD, sendNotification, convertNewLine, fillParams } = require(Runtime.getFunctions()['util'].path); 6 | 7 | if (!admin.apps.length) { 8 | admin.initializeApp({}); 9 | } else { 10 | admin.app(); 11 | } 12 | const firestore = admin.firestore(); 13 | const md5 = require('md5'); 14 | 15 | const {AIRTABLE_API_KEY, AIRTABLE_BASE_ID} = process.env; 16 | 17 | 18 | /* 19 | event: evento, palestraId, from, voto, 20 | */ 21 | exports.handler = async function(context, event, callback) { 22 | const palestraId = event.palestraId; 23 | let participanteId = await md5(limpaNumero(event.from)); 24 | let idPlayerEvent = await md5(`${event.evento}:${limpaNumero(event.from)}`); 25 | 26 | const voto = parseInt(event.voto); 27 | 28 | console.log('ADD PALESTRA FEEDBACK: ', event, participanteId, idPlayerEvent); 29 | 30 | 31 | // carregar pontuação padrão do evento 32 | const eventoData = await firestore.collection('events').doc(event.evento).get().then(s => { 33 | if (s.exists) { 34 | return s.data(); 35 | } else { 36 | return {} 37 | } 38 | }); 39 | 40 | await firestore.collection('events').doc(event.evento) 41 | .collection('participantes').doc(idPlayerEvent) 42 | .collection('feedbacks').doc(palestraId.toUpperCase()).set({ 43 | idPlayerEvent, 44 | participanteId, 45 | createdAt: admin.firestore.FieldValue.serverTimestamp(), 46 | voto: voto 47 | }); 48 | 49 | await firestore.collection('events').doc(event.evento) 50 | .collection('palestras').doc(palestraId).set({ 51 | feedbacks: admin.firestore.FieldValue.increment(1), 52 | totalPoints: admin.firestore.FieldValue.increment(voto), 53 | updatedAt: admin.firestore.FieldValue.serverTimestamp() 54 | }, { merge: true }); 55 | 56 | await firestore.collection('events').doc(event.evento) 57 | .collection('palestras').doc(palestraId) 58 | .collection('feedbacks').doc(idPlayerEvent).set({ 59 | idPlayerEvent, 60 | participanteId, 61 | palestraId, 62 | createdAt: admin.firestore.FieldValue.serverTimestamp(), 63 | voto: voto 64 | }); 65 | 66 | 67 | // var Airtable = require('airtable'); 68 | // var base = new Airtable({ apiKey: AIRTABLE_API_KEY }).base(AIRTABLE_BASE_ID); 69 | 70 | // // Adicionar registro de feedback 71 | // base('Avaliacoes').create([ 72 | // { 73 | // "fields": { 74 | // "Usuario": idPlayerEvent, 75 | // "Voto": voto, 76 | // "Talks": [ 77 | // palestraId 78 | // ] 79 | // } 80 | // }, 81 | // ], async function(err, records) { 82 | // if (err) { 83 | // console.error(err); 84 | // callback(null, { err }); 85 | // return; 86 | // } 87 | // }); 88 | 89 | 90 | let feedbackPoints = 0; 91 | if (eventoData.feedbackPoints) { 92 | feedbackPoints = eventoData.feedbackPoints; 93 | 94 | // Adicionar ponto por avaliação 95 | // Registrar pontuação no evento 96 | await firestore.collection('score') 97 | .doc(event.evento) 98 | .collection('participantes') 99 | .doc(idPlayerEvent) 100 | .set({ 101 | pontosCorrente: admin.firestore.FieldValue.increment(feedbackPoints), 102 | pontosAcumulados: admin.firestore.FieldValue.increment(feedbackPoints), 103 | updatedAt: admin.firestore.FieldValue.serverTimestamp() 104 | }, { merge: true }); 105 | 106 | 107 | 108 | // Registrar pontuação no SCORE do PARTICIPANTE 109 | await firestore.collection('events') 110 | .doc(event.evento) 111 | .collection('participantes') 112 | .doc(idPlayerEvent) 113 | .set({ 114 | pontosCorrente: admin.firestore.FieldValue.increment(feedbackPoints), 115 | pontosAcumulados: admin.firestore.FieldValue.increment(feedbackPoints), 116 | updatedAt: admin.firestore.FieldValue.serverTimestamp() 117 | }, { merge: true }); 118 | 119 | } 120 | 121 | // Incrementar votacoes em stats 122 | await firestore.collection('events') 123 | .doc(event.evento).set({ 124 | stats: { 125 | feedbacks: admin.firestore.FieldValue.increment(1), 126 | pontos: admin.firestore.FieldValue.increment(feedbackPoints) 127 | } 128 | }, { merge: true }); 129 | 130 | if (feedbackPoints > 0) { 131 | callback(null, { 132 | message: `✅ Agradecemos por enviar seu feedback! ✅\n\nVocê acumulou mais *${feedbackPoints} ponto${ feedbackPoints > 1 ? 's': ''}*!` 133 | }); 134 | 135 | } else { 136 | callback(null, { 137 | message: '' 138 | }); 139 | } 140 | 141 | }; 142 | -------------------------------------------------------------------------------- /scripts/printers/print-canon-selphy.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | require("dotenv").config(); 4 | 5 | // Initialize Firebase 6 | var admin = require("firebase-admin"); 7 | 8 | 9 | if (!admin.apps.length) { 10 | admin.initializeApp(); 11 | }else { 12 | admin.app(); // if already initialized, use that one 13 | } 14 | const firestore = admin.firestore(); 15 | 16 | 17 | 18 | var printer = require("printer"); 19 | const fs = require('fs'); 20 | const tmp_dir = require('os').tmpdir(); 21 | 22 | const PDFDocument = require('pdfkit'); 23 | 24 | 25 | const getSelphyPrinter = async () => { 26 | const printers = (await printer.getPrinters()).filter(p => { 27 | return p.options['printer-make-and-model'].toUpperCase().indexOf('SELPHY') >= 0 28 | && p.isDefault; 29 | }); 30 | // console.log('PRINTERS', printers); 31 | if (printers.length > 0) { 32 | return printers[0]; 33 | } else { 34 | return null; 35 | } 36 | } 37 | 38 | const downloadPicture = async (imageUrl) => { 39 | const axios = require('axios'); 40 | console.log('downloading...', imageUrl); 41 | // Faz a requisição HTTP usando o Axios 42 | return await axios.get(imageUrl, 43 | // { responseType: 'stream' } 44 | { responseType: 'arraybuffer' } 45 | // ).then(r => Buffer.from(r.data, 'binary').toString('base64')); 46 | ).then(r => { 47 | console.log('DOWNLOADED!'); 48 | return Buffer.from(r.data, 'binary') 49 | }); 50 | } 51 | 52 | 53 | const print = async (pictureUrl, evento) => { 54 | if (!fs.existsSync(`impressoes/${evento}/`)) { 55 | console.log(`Criando pasta 'impressoes/${evento}/'`); 56 | fs.mkdirSync(`impressoes/${evento}/`); 57 | } 58 | 59 | let ASSET_BACKGROUND = fs.existsSync(`assets/${evento}/fundo.png`) ? `assets/${evento}/fundo.png` : `assets/fundo.png`; 60 | let ASSET_FOREGROUD = fs.existsSync(`assets/${evento}/frente.png`) ? `assets/${evento}/frente.png` : `assets/frente.png`; 61 | 62 | const fileName = pictureUrl.split('/').reverse()[0] 63 | const pictureBin = await downloadPicture(pictureUrl); 64 | 65 | const selphy = await getSelphyPrinter(); 66 | if (!selphy) return false; 67 | 68 | const width = 100/10; 69 | const height = 148/10; 70 | 71 | const doc = new PDFDocument({ 72 | size: [width * 28.3465, height * 28.3465], 73 | // size: 'A6', 74 | // size: [1748, 2480], 75 | // size: [100, 148], 76 | layout: 'portrait', //'landscape', 77 | margins: { 78 | top: 0, bottom: 0, left: 0, right: 0 79 | } 80 | }); 81 | 82 | const file = `impressoes/${evento}/${fileName}.pdf`; 83 | const stream = doc.pipe(fs.createWriteStream(file)); 84 | 85 | 86 | doc.info['Title'] = pictureUrl; 87 | const offsetX = 40; //44; 88 | const offsetY = 65; //70; 89 | 90 | doc 91 | .moveTo(0, 0) 92 | .image(ASSET_BACKGROUND, 0, 0, {width: doc.page.width, height: doc.page.height}); 93 | 94 | doc 95 | .moveTo(0, 0) 96 | // .image('foto.jpeg', 0, 0, {width: doc.page.width, height: doc.page.height}); 97 | .image(pictureBin, offsetX, 0, { 98 | // width: doc.page.width - (offsetX * 2), 99 | // height: doc.page.height - (offsetY + offsetY), 100 | fit: [doc.page.width - (offsetX * 2), doc.page.height], 101 | align: 'center', 102 | valign: 'center' 103 | }); 104 | 105 | doc 106 | .moveTo(0, 0) 107 | .image(ASSET_FOREGROUD, 0, 0, {width: doc.page.width, height: doc.page.height}); 108 | 109 | doc.end(); 110 | 111 | stream.on('finish', function() { 112 | console.log('COMPLETED'); 113 | 114 | // Print PDF File 115 | printer.printFile({ 116 | filename: file, 117 | docname: fileName, 118 | printer: selphy.name, 119 | 120 | success:function(jobID){ 121 | console.log(`sent to printer with ID: ${jobID}`); 122 | }, 123 | error:function(err){ 124 | console.log(err); 125 | } 126 | }); 127 | 128 | }); 129 | 130 | return true; 131 | 132 | } 133 | 134 | (async () => { 135 | console.log('Inicializado.'); 136 | // console.log(await printer.getPrinters()); 137 | // return; 138 | 139 | let selphyPrinter = await getSelphyPrinter(); 140 | console.log('PRINTER:', selphyPrinter); 141 | if (!selphyPrinter) { 142 | console.log('No Canon SELPHY printer found!'); 143 | return; 144 | } 145 | 146 | firestore.collectionGroup('photoprint') 147 | .where('status', '==', 'pending') 148 | .onSnapshot(async s => { 149 | let batch = firestore.batch(); 150 | 151 | let tempLabels = []; 152 | s.forEach(async doc => { 153 | tempLabels.push({ ...doc.data(), ref: doc.ref}); //{id: doc.id, ...doc.data()}); 154 | }); 155 | for (picture of tempLabels) { 156 | console.log('PRINTING', picture.url); 157 | if (await print(picture.url, picture.evento)) { 158 | batch.set(picture.ref, { 159 | printed: admin.firestore.FieldValue.increment(1), 160 | status: 'printed', 161 | updatedAt: admin.firestore.FieldValue.serverTimestamp() 162 | }, { merge: true }); 163 | console.log('PRINTED'); 164 | } else { 165 | batch.set(picture.ref, { 166 | printed: admin.firestore.FieldValue.increment(1), 167 | status: 'error', 168 | updatedAt: admin.firestore.FieldValue.serverTimestamp() 169 | }, { merge: true }) 170 | console.log('ERROR'); 171 | } 172 | } 173 | await batch.commit(); 174 | 175 | }); 176 | 177 | // let imagem = ''; 178 | // await print(imagem); 179 | 180 | })() 181 | -------------------------------------------------------------------------------- /scripts/divoom.js: -------------------------------------------------------------------------------- 1 | const { Pixoo } = require('pixoo'); 2 | 3 | const PIXOO_IP = '192.168.50.246'; //'172.20.10.6'; // '10.0.1.2'; //'172.20.10.6'; //192.168.15.17 4 | const INTERVALO = 2000; 5 | 6 | const pixoo = new Pixoo(PIXOO_IP, 64); 7 | 8 | function getBase64(file) { 9 | const fs = require('fs'); 10 | return fs.readFileSync(file, {encoding: 'base64'}); 11 | } 12 | const aleatorio = (maximo) => { 13 | return Math.floor(maximo* Math.random()); 14 | } 15 | 16 | 17 | const red = [255, 0, 0]; 18 | const green = [0, 255, 0]; 19 | const blue = [0, 0, 255]; 20 | const white = [255, 255, 255]; 21 | const black = [0, 0, 0]; 22 | const purple = [255, 0, 255]; 23 | 24 | 25 | let pessoas = aleatorio(9999); 26 | let loops = 0; 27 | let videomatik = aleatorio(9999); 28 | let pontos = aleatorio(99999); 29 | let exibe = 'counter'; 30 | 31 | let stats = {} 32 | let ranking = [ 33 | { 34 | numero: '0955', 35 | pontos: 1234 36 | } 37 | ]; 38 | 39 | let timer = null; 40 | let move = 'in'; 41 | let inicio = 2; 42 | let linha = 2; 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | require("dotenv").config(); 53 | 54 | // Initialize Firebase 55 | var admin = require("firebase-admin"); 56 | 57 | let printers = {}; 58 | let eventos = {}; 59 | 60 | 61 | 62 | if (!admin.apps.length) { 63 | admin.initializeApp(); 64 | }else { 65 | admin.app(); // if already initialized, use that one 66 | } 67 | const firestore = admin.firestore(); 68 | 69 | 70 | const { EVENT_ID } = process.env; 71 | 72 | 73 | 74 | 75 | const init = async () => { 76 | 77 | await firestore.collection('events') 78 | .doc(EVENT_ID).onSnapshot(async s => { 79 | const eventoData = s.data(); 80 | stats = eventoData.stats || {}; 81 | console.log('UPDATED STATS', stats); 82 | 83 | 84 | }); 85 | 86 | // Filtrar ordem de participantes que estao com game ativo 87 | ranking = await firestore.collection('events') 88 | .doc(EVENT_ID).collection('participantes') 89 | .where('game', '==', true) 90 | .orderBy('pontosAcumulados', 'desc') 91 | .limit(7) 92 | .onSnapshot(s => { 93 | ranking = s.docs.map(d => { 94 | const participante = d.data(); 95 | return { 96 | numero: participante.phoneNumber.slice(-4), 97 | pontos: participante.pontosAcumulados 98 | } 99 | }); 100 | console.log('NEW RANKING', ranking); 101 | }); 102 | 103 | 104 | // initialize 105 | console.log('iniciado'); 106 | // await pixoo.init(); 107 | await pixoo.fillRGB(0, 0, 0); 108 | play(); 109 | } 110 | 111 | const printGameplay = async () => { 112 | 113 | await pixoo.fillRGB(0, 0, 0); 114 | await pixoo.fillRGB(0, 0, 0); 115 | 116 | await pixoo.drawText('>>> <<<', [2, 1+linha], green); 117 | await pixoo.drawText(' NUMEROS ', [2, 1+linha], red); 118 | 119 | let linhaAtual = 1; 120 | 121 | if (stats.pessoas) { 122 | linhaAtual++; 123 | await pixoo.drawText('PESSOAS ' + (' '+stats.pessoas).slice(-7), [2, contaLinhas(linhaAtual)], white); 124 | } 125 | if (stats.conexoes) { 126 | linhaAtual++; 127 | await pixoo.drawText('CONEXOES ' + (' '+stats.conexoes).slice(-6), [2, contaLinhas(linhaAtual)], white); 128 | } 129 | if (stats.pontos) { 130 | linhaAtual++; 131 | await pixoo.drawText('PONTOS ' + (' '+stats.pontos).slice(-8), [2, contaLinhas(linhaAtual)], white); 132 | } 133 | if (stats.videomatik) { 134 | linhaAtual++; 135 | await pixoo.drawText('VIDEOMATIK ' + (' '+stats.videomatik).slice(-4), [2, contaLinhas(linhaAtual)], white); 136 | // await pixoo.drawText('VIDEOS ' + (' '+stats.videomatik).slice(-8), [2, contaLinhas(linhaAtual)], white); 137 | } 138 | 139 | if (!(stats.pessoas || 140 | stats.conexoes || 141 | stats.pontos || 142 | stats.videomatik) 143 | ) { 144 | await pixoo.drawText('PARTICIPE', [2, contaLinhas(3)], white); 145 | await pixoo.drawText('E VEJA OS', [2, contaLinhas(4)], white); 146 | await pixoo.drawText('DADOS AQUI!', [2, contaLinhas(5)], white); 147 | 148 | } else { 149 | console.log('tem dado'); 150 | } 151 | 152 | await pixoo.drawText('JOGUE NO WHATS', [2, contaLinhas(7)], green); 153 | await pixoo.drawText('11 5039-3737', [2, contaLinhas(8)], purple); 154 | await pixoo.drawText('11 5039-3737', [2, contaLinhas(8)], purple); 155 | // await pixoo.drawText('11 9999-9999', [2, contaLinhas(8)], purple); 156 | // await pixoo.drawText('11 9999-9999', [2, contaLinhas(8)], purple); 157 | 158 | } 159 | 160 | const ALTURA = 7; 161 | 162 | const contaLinhas = (linha) => { 163 | return 5+((linha-1)*ALTURA); 164 | } 165 | 166 | const printRanking = async () => { 167 | 168 | await pixoo.fillRGB(0, 0, 0); 169 | await pixoo.fillRGB(0, 0, 0); 170 | 171 | // pixoo.drawText('===============', [2, 29], white); 172 | await pixoo.drawText('<<< >>>', [2, 1+linha], blue); 173 | await pixoo.drawText(' RANKING ', [2, 1+linha], green); 174 | 175 | if (ranking.length == 0) { 176 | await pixoo.drawText('PARTICIPE', [2, contaLinhas(3)], white); 177 | await pixoo.drawText('E VEJA O', [2, contaLinhas(4)], white); 178 | await pixoo.drawText('RANKING!', [2, contaLinhas(5)], white); 179 | // pixoo.drawBuffer(); 180 | return; 181 | } 182 | 183 | for (p in ranking) { 184 | const cor = p > 0 ? white : green; 185 | await pixoo.drawText(`${parseInt(p)+1}º ${ranking[p].numero}: `+ (' '+ranking[p].pontos).slice(-6), [2, contaLinhas(2 + parseInt(p))], cor); 186 | } 187 | } 188 | 189 | const play = async () => { 190 | // await pixoo.init(); 191 | 192 | // exibe = 'ranking'; 193 | loops++; 194 | // videomatik++; 195 | console.log(`Exibindo loop ${loops}`); 196 | 197 | 198 | switch (exibe) { 199 | case 'counter': 200 | await printGameplay(); 201 | exibe = 'ranking'; 202 | break; 203 | 204 | case 'ranking': 205 | await printRanking(); 206 | exibe = 'counter'; 207 | break; 208 | } 209 | if (exibe == 'ranking' && ranking.length == 0) { 210 | exibe = 'counter'; 211 | } 212 | 213 | setTimeout(play, INTERVALO); 214 | } 215 | 216 | init(); -------------------------------------------------------------------------------- /eventos/functions/verifica-palavrachave.protected.js: -------------------------------------------------------------------------------- 1 | const admin = require('firebase-admin'); 2 | process.env.GOOGLE_APPLICATION_CREDENTIALS = Runtime.getAssets()['/firebase-credentials.json'].path; 3 | const { escondeNumero, limpaNumero, getDDD, sendNotification, convertNewLine, fillParams } = require(Runtime.getFunctions()['util'].path); 4 | 5 | if (!admin.apps.length) { 6 | admin.initializeApp({}); 7 | } else { 8 | admin.app(); 9 | } 10 | const firestore = admin.firestore(); 11 | const md5 = require('md5'); 12 | 13 | /* 14 | event: evento, token, from 15 | */ 16 | exports.handler = async function(context, event, callback) { 17 | let participanteId = await md5(limpaNumero(event.from)); 18 | 19 | console.log('VERIFICA PALAVRA: ', event); 20 | 21 | const client = await context.getTwilioClient(); 22 | if (!event.token) { 23 | console.log('TOKEN VAZIO'); 24 | return callback(null, { 25 | type: 'not-found' 26 | }); 27 | } 28 | 29 | let activation = await firestore.collection('events').doc(event.evento).collection('palavras').doc(event.token).get(); 30 | if (!activation.exists) { 31 | activation = await firestore.collection('events').doc(event.evento).collection('palavras').doc(event.token.toLowerCase()).get(); 32 | if (!activation.exists) { 33 | // token não encontrado 34 | console.log(event.token, 'Palavra não existe no evento', event.evento); 35 | return callback(null, { 36 | type: 'not-found' 37 | }); 38 | } 39 | } 40 | 41 | let activationData = activation.data(); 42 | if (activationData.mensagem) { 43 | activationData.mensagem = convertNewLine(activationData.mensagem); 44 | } 45 | let callbackData = { 46 | ...activationData 47 | // type: activationData.type || 'activation', 48 | } 49 | 50 | switch(activationData.type) { 51 | case 'impressao_manual': 52 | break; 53 | 54 | case 'reimpressao': 55 | break; 56 | 57 | case 'impressao': 58 | // Carregar dados de participante 59 | let participante = await firestore.collection('events').doc(event.evento).collection('participantes') 60 | .doc(participanteId) 61 | .get() 62 | .then( p => p.exists ? p.data() : null ); 63 | 64 | if (!participante) { 65 | // Verificar se tem dado do participante geral e copiar 66 | const participanteGeral = await firestore.collection('participantes') 67 | .doc(participanteId) 68 | .get() 69 | .then( p => p.exists ? p.data() : null ); 70 | 71 | if (!participanteGeral) { 72 | // Erro: participante não encontrato no evento! 73 | return callback(null, { 74 | type: activationData.type, 75 | mensagem: 'Erro!\n\nParticipante não encontrado neste evento.' 76 | }); 77 | } 78 | participante = participanteGeral; 79 | } 80 | 81 | await firestore.collection('labels').add({ 82 | evento: event.evento, 83 | printer: activationData.printer, 84 | participanteId, 85 | ...participante 86 | }); 87 | 88 | await firestore.collection('printers').doc(activationData.printer).set({ 89 | impressoes: admin.firestore.FieldValue.increment(1), 90 | updatedAt: admin.firestore.FieldValue.serverTimestamp() 91 | }, { merge: true }); 92 | 93 | if (activationData.mediaUrl) { 94 | activationData.mediaUrl = fillParams(activationData.mediaUrl, { 95 | participanteId, 96 | ...participante 97 | }); 98 | } 99 | break; 100 | 101 | case 'quiz': 102 | break; 103 | 104 | case 'certificado': 105 | break; 106 | 107 | default: // 'conteudo' 108 | break; 109 | } 110 | 111 | // tipos de palavras-chave 112 | // TODO: impressao 113 | // TODO: quiz 114 | // TODO: certificado 115 | // TODO: conteudo 116 | 117 | // TODO: networking (registro linkedin) 118 | // TODO: voucher 119 | // TODO: score 120 | 121 | // TODO: limite máximo de ativações 122 | // TODO: notificações 123 | 124 | // Uma nova pessoa que acabou de se conectar com você: {{1}} 125 | 126 | 127 | 128 | // event.evento, event.token 129 | 130 | // let participanteId = await md5(limpaNumero(event.from)); 131 | 132 | // let evento = null; 133 | // let mensagem = ''; 134 | // let mediaUrl = ''; 135 | 136 | // console.log('participanteId', participanteId); 137 | 138 | // const participante = await firestore.collection('participantes') 139 | // .doc(participanteId) 140 | // .get() 141 | // .then( p => p.exists ? p.data() : null ); 142 | 143 | // if (participante) { 144 | // evento = participante.evento || null; 145 | // // Verificar se evento estiver ativo 146 | // if (evento != null) { 147 | // const participanteEvento = await firestore.collection('events') 148 | // .doc(evento) 149 | // .get() 150 | // .then( e => e.exists ? e.data() : null ); 151 | // evento = participanteEvento && participanteEvento.active ? evento : null; 152 | // mensagem = participante && participanteEvento.message ? participanteEvento.message : ''; 153 | // mediaUrl = participante && participanteEvento.mediaUrl ? participanteEvento.mediaUrl : ''; 154 | // } 155 | // } 156 | 157 | // // Caso não tenha evento definido ou ativo, carregar evento default ativo 158 | // if (!evento) { 159 | // // Sem evento - definir default 160 | // const eventos = await firestore.collection('events') 161 | // .where('default', '==', true) 162 | // .where('active', '==', true) 163 | // .get() 164 | // .then(snapshot => { 165 | // return snapshot.docs.map(doc => { 166 | // return { key: doc.id, ...doc.data() } 167 | // }); 168 | // }); 169 | 170 | // if (eventos.length > 0) { 171 | // evento = eventos[0].key 172 | // mensagem = eventos[0].message ? eventos[0].message : ''; 173 | // mediaUrl = eventos[0].mediaUrl ? eventos[0].mediaUrl : ''; 174 | // } 175 | // } else { 176 | 177 | // } 178 | // mensagem = mensagem.split('').join('\n'); 179 | 180 | callback(null, { 181 | ...activationData 182 | }); 183 | 184 | }; 185 | -------------------------------------------------------------------------------- /eventos/functions/vendingmachine-resgate.protected.js: -------------------------------------------------------------------------------- 1 | const admin = require('firebase-admin'); 2 | process.env.GOOGLE_APPLICATION_CREDENTIALS = Runtime.getAssets()['/firebase-credentials.json'].path; 3 | const { escondeNumero, limpaNumero, getDDD, sendNotification, convertNewLine, fillParams } = require(Runtime.getFunctions()['util'].path); 4 | 5 | if (!admin.apps.length) { 6 | admin.initializeApp({}); 7 | } else { 8 | admin.app(); 9 | } 10 | const firestore = admin.firestore(); 11 | const md5 = require('md5'); 12 | 13 | /* 14 | event: evento, token, from 15 | */ 16 | exports.handler = async function(context, event, callback) { 17 | 18 | 19 | console.log('EVENT', event); 20 | 21 | let participanteId = await md5(limpaNumero(event.from)); 22 | 23 | // // Registrar participante na base geral 24 | // await firestore.collection('participantes') 25 | // .doc(participanteId).set({ 26 | // phoneNumber: limpaNumero(event.from), 27 | // profileName: event.profileName, 28 | // ultimoEvento: event.evento, 29 | // updatedAt: admin.firestore.FieldValue.serverTimestamp() 30 | // }, { merge: true }); 31 | 32 | let participanteGeral = await firestore.collection('participantes') 33 | .doc(participanteId).get().then(async s => { 34 | if (s.exists) { 35 | return s.data(); 36 | } else { 37 | return null 38 | } 39 | }); 40 | 41 | let participante = await firestore.collection('events') 42 | .doc(event.evento).collection('participantes') 43 | .doc(participanteId).get().then(async s => { 44 | if (s.exists) { 45 | return s.data(); 46 | } else { 47 | return null; 48 | } 49 | }); 50 | 51 | 52 | let produtoVending = await firestore.collection('vendingmachine') 53 | .doc(process.env.VENDINGMACHINE_DEFAULT).collection('estoque') 54 | .doc(event.codigoItem).get().then(s => { 55 | if (s.exists) { 56 | return s.data(); 57 | } else { 58 | return null; 59 | } 60 | }); 61 | 62 | if (!(participanteGeral.isAdmin || participanteGeral.twilion) && participante.resgates > process.env.LIMITE_RESGATES) { 63 | console.log('Limite de resgate alcançado!'); 64 | return callback(null, `Você pode resgatar até ${process.env.LIMITE_RESGATES} itens.`); 65 | } 66 | 67 | if (!participanteGeral || !participante) { 68 | // Erro - produto não encontrado! 69 | console.log('Participante não encontrado!'); 70 | return callback(null, `Ocorreu um erro no seu registro!\n\nInforme ao time da Twilio!`); 71 | } 72 | 73 | if (!produtoVending) { 74 | // Erro - produto não encontrado! 75 | console.log('Produto não encontrado!'); 76 | return callback(null, `O código informado não foi encontrado!`); 77 | } 78 | 79 | // Verificar estoque do produto selecionado 80 | if (produtoVending.estoque <= 0) { 81 | // Erro - produto sem estoque 82 | console.log('Produto SEM ESTOQUE!'); 83 | return callback(null, `O produto selecionado está sem estoque!\n\nSeus pontos não serão descontados.`); 84 | 85 | } 86 | 87 | // Verificar pontos do produto selecionado 88 | // Verificar pontuação de participante 89 | if (!(participanteGeral.isAdmin || participanteGeral.twilion) && participante.pontosCorrente < produtoVending.pontos) { 90 | // Participante sem pontuação mínima e participante não Admin/Twilion 91 | console.log('PARTICIPANTE SEM PONTOS!'); 92 | return callback(null, `Para resgatar este produto, você precisa de ${produtoVending.pontos} pontos.\n\nSua pontuação atual é de ${participante.pontosCorrente} ponto(s).`); 93 | } 94 | 95 | 96 | console.log('RODANDO BATCH'); 97 | // Descontar pontos de participante 98 | const batch = firestore.batch(); 99 | 100 | let participanteRef = firestore.collection('events') 101 | .doc(event.evento).collection('participantes') 102 | .doc(participanteId); 103 | 104 | batch.set(participanteRef, { 105 | updatedAt: admin.firestore.FieldValue.serverTimestamp(), 106 | pontosCorrente: admin.firestore.FieldValue.increment((participanteGeral.isAdmin || participanteGeral.twilion) ? 0 : produtoVending.pontos * -1), 107 | resgates: admin.firestore.FieldValue.increment(1) 108 | }, { merge: true }); 109 | 110 | 111 | // Adicionar log de resgate em participante 112 | let resgateRef = firestore.collection('events') 113 | .doc(event.evento).collection('participantes') 114 | .doc(participanteId).collection('resgates').doc(); 115 | 116 | batch.set(resgateRef, { 117 | codigo: event.codigoItem, 118 | pontos: produtoVending.pontos, 119 | createdAt: admin.firestore.FieldValue.serverTimestamp(), 120 | }); 121 | 122 | 123 | // Descontar estoque do produto selecionado 124 | let vendingRef = firestore.collection('vendingmachine') 125 | .doc(process.env.VENDINGMACHINE_DEFAULT).collection('estoque') 126 | .doc(event.codigoItem); 127 | 128 | batch.set(vendingRef, { 129 | updatedAt: admin.firestore.FieldValue.serverTimestamp(), 130 | estoque: admin.firestore.FieldValue.increment(-1) 131 | }, { merge: true }); 132 | 133 | 134 | // Enviar comando para vending machine 135 | let comandoRef = firestore.collection('vendingmachine') 136 | .doc(process.env.VENDINGMACHINE_DEFAULT).collection('comandos') 137 | .doc() 138 | 139 | batch.set(comandoRef, { 140 | codigo: event.codigoItem, 141 | status: 'pending', 142 | participante: participanteId, 143 | pontos: produtoVending.pontos, 144 | createdAt: admin.firestore.FieldValue.serverTimestamp() 145 | }); 146 | 147 | 148 | // Registrar contador de resgates da vending 149 | let vendingResgateRef = firestore.collection('vendingmachine') 150 | .doc(process.env.VENDINGMACHINE_DEFAULT); 151 | 152 | batch.set(vendingResgateRef, { 153 | updatedAt: admin.firestore.FieldValue.serverTimestamp(), 154 | resgates: admin.firestore.FieldValue.increment(1) 155 | }, { merge: true }); 156 | 157 | 158 | // Registrar contador de resgates do evento 159 | let eventoRef = firestore.collection('events') 160 | .doc(event.evento); 161 | 162 | batch.set(eventoRef, { 163 | updatedAt: admin.firestore.FieldValue.serverTimestamp(), 164 | resgates: admin.firestore.FieldValue.increment(1) 165 | }, { merge: true }); 166 | 167 | 168 | 169 | await batch.commit(); 170 | console.log('BATCH FINALIZADO!'); 171 | 172 | return callback(null, `Seu resgate foi realizado com sucesso!\n\nRetire seu item na vending machine.\n\nVocê pode resgatar até ${process.env.LIMITE_RESGATES} itens.`); 173 | 174 | }; 175 | -------------------------------------------------------------------------------- /eventos/functions/barista-status.js: -------------------------------------------------------------------------------- 1 | const admin = require('firebase-admin'); 2 | process.env.GOOGLE_APPLICATION_CREDENTIALS = Runtime.getAssets()['/firebase-credentials.json'].path; 3 | const { escondeNumero, limpaNumero, getDDD, sendNotification, convertNewLine, fillParams, replaceVariablesTemplateMessage } = require(Runtime.getFunctions()['util'].path); 4 | 5 | if (!admin.apps.length) { 6 | admin.initializeApp({}); 7 | } else { 8 | admin.app(); 9 | } 10 | const firestore = admin.firestore(); 11 | 12 | /* 13 | event: filaId, evento, status, idPlayerEvent 14 | */ 15 | exports.handler = async function(context, event, callback) { 16 | const client = await context.getTwilioClient(); 17 | 18 | 19 | const evento = await firestore.collection('events').doc(event.evento).get().then(s => { 20 | if (s.exists) { 21 | return s.data(); 22 | } else { 23 | return {} 24 | } 25 | }); 26 | 27 | 28 | const barista = await firestore.collection('events').doc(event.evento) 29 | .collection('barista').doc(event.filaId).get().then(async s => { 30 | if (s.exists) { 31 | return s.data(); 32 | } else { 33 | return null; 34 | } 35 | }); 36 | 37 | 38 | if (barista) { 39 | 40 | // Verificar se possui pontuação mínima para pedido 41 | const participante = await firestore.collection('events') 42 | .doc(event.evento).collection('participantes') 43 | .doc(barista.idPlayerEvent).get().then(async s => { 44 | if (s.exists) { 45 | return s.data(); 46 | } else { 47 | return { 48 | pontosAcumulados: 0, 49 | pontosCorrente: 0, 50 | coffeeUnlimited: false, 51 | cafes: 0 52 | }; 53 | } 54 | }); 55 | 56 | if (!participante) { 57 | return callback(null, { 58 | erro: true, 59 | mensagem: evento.barista.messages.attendeeNotFound 60 | }); 61 | } 62 | 63 | const numero = limpaNumero(participante.phoneNumber); 64 | const codigo = numero.substr(numero.length - 4 ) 65 | 66 | switch (event.status) { 67 | case 'chamar': 68 | 69 | // (client, from, to, message, messagingServiceSid) 70 | await sendNotification( 71 | client, null, 72 | `whatsapp:${participante.phoneNumber}`, 73 | replaceVariablesTemplateMessage(evento.barista.messages.call, 74 | { 75 | 'code': codigo 76 | }, 77 | evento.barista.messageServiceSID 78 | ) 79 | 80 | ); 81 | 82 | await firestore.collection('events').doc(event.evento) 83 | .collection('barista').doc(event.filaId).set({ 84 | updatedAt: admin.firestore.FieldValue.serverTimestamp(), 85 | chamados: admin.firestore.FieldValue.increment(1) 86 | }, { merge: true }); 87 | 88 | break; 89 | 90 | case 'cancelado': 91 | 92 | // Verificar se participante não é ilimitado e retornar pontos e total de cafés 93 | // resetando contador de café e voltando pontos 94 | await firestore.collection('events') 95 | .doc(event.evento).collection('participantes') 96 | .doc(barista.idPlayerEvent).set({ 97 | cafes: admin.firestore.FieldValue.increment(-1), 98 | pontosCorrente: admin.firestore.FieldValue.increment(participante.coffeeUnlimited ? 0 : evento.barista.points), 99 | updatedAt: admin.firestore.FieldValue.serverTimestamp() 100 | }, { merge: true }); 101 | 102 | await sendNotification( 103 | client, 104 | null, 105 | `whatsapp:${participante.phoneNumber}`, 106 | evento.barista.messages.cancelled, 107 | evento.barista.messageServiceSID 108 | ); 109 | 110 | await firestore.collection('events').doc(event.evento) 111 | .collection('barista').doc(event.filaId).set({ 112 | status: 'cancelado', 113 | updatedAt: admin.firestore.FieldValue.serverTimestamp(), 114 | }, { merge: true }); 115 | break; 116 | 117 | case 'preparo': 118 | await firestore.collection('events').doc(event.evento) 119 | .collection('barista').doc(event.filaId).set({ 120 | status: 'preparo', 121 | updatedAt: admin.firestore.FieldValue.serverTimestamp(), 122 | }, { merge: true }); 123 | break; 124 | 125 | case 'pronto': 126 | await firestore.collection('events').doc(event.evento) 127 | .collection('barista').doc(event.filaId).set({ 128 | status: 'pronto', 129 | updatedAt: admin.firestore.FieldValue.serverTimestamp(), 130 | }, { merge: true }); 131 | 132 | await firestore.collection('events').doc(event.evento) 133 | .collection('participantes').doc(barista.idPlayerEvent).set({ 134 | ultimoResgateBarista: admin.firestore.FieldValue.serverTimestamp(), 135 | updatedAt: admin.firestore.FieldValue.serverTimestamp(), 136 | }, { merge: true }); 137 | 138 | 139 | 140 | let mensagem = []; 141 | 142 | mensagem.push(replaceVariablesTemplateMessage(evento.barista.messages.ready, 143 | { 144 | 'code': codigo, 145 | 'nextOrderInterval': evento.barista.tempoMinimoResgate 146 | })); 147 | 148 | await sendNotification( 149 | client, 150 | null, 151 | `whatsapp:${participante.phoneNumber}`, 152 | mensagem.join('\n\n'), 153 | evento.barista.messageServiceSID 154 | ); 155 | 156 | // Atualizar stats de café 157 | await firestore.collection('events').doc(event.evento) 158 | .set({ 159 | stats: { 160 | cafe: admin.firestore.FieldValue.increment(1) 161 | }, 162 | updatedAt: admin.firestore.FieldValue.serverTimestamp(), 163 | }, { merge: true }); 164 | 165 | break; 166 | } 167 | 168 | } 169 | 170 | 171 | callback(null, 'OK'); 172 | }; 173 | -------------------------------------------------------------------------------- /eventos/functions/executa-comando-sorteio.protected.js: -------------------------------------------------------------------------------- 1 | const admin = require('firebase-admin'); 2 | process.env.GOOGLE_APPLICATION_CREDENTIALS = Runtime.getAssets()['/firebase-credentials.json'].path; 3 | const { escondeNumero, limpaNumero, getDDD, sendNotification, convertNewLine, fillParams } = require(Runtime.getFunctions()['util'].path); 4 | 5 | if (!admin.apps.length) { 6 | admin.initializeApp({}); 7 | } else { 8 | admin.app(); 9 | } 10 | const firestore = admin.firestore(); 11 | 12 | /* 13 | event: evento, palavra, comando, to 14 | */ 15 | exports.handler = async function(context, event, callback) { 16 | const client = await context.getTwilioClient(); 17 | 18 | event.palavra = event.palavra.toLowerCase(); 19 | 20 | let data = {}; 21 | let mensagem = []; 22 | 23 | // mensagem.push(`comando ${event.comando.toUpperCase()} executado com sucesso!`); 24 | 25 | switch(event.comando.toUpperCase()) { 26 | case 'ABRIR': 27 | case 'FECHAR': 28 | await firestore.collection('events') 29 | .doc(event.evento).collection('sorteios') 30 | .doc(event.palavra) 31 | .set({ 32 | status: event.comando.toUpperCase() == 'ABRIR' ? 'aberto' : 'fechado', 33 | updatedAt: admin.firestore.FieldValue.serverTimestamp() 34 | }, { merge: true}); 35 | 36 | mensagem.push(`*${event.palavra.toUpperCase()}* foi *${event.comando.toUpperCase() == 'ABRIR' ? 'aberta' : 'fechada'}* com sucesso!`); 37 | break; 38 | 39 | case 'SORTEAR': 40 | 41 | const sorteado = await firestore.collection('events') 42 | .doc(event.evento).collection('sorteios') 43 | .doc(event.palavra).collection('participantes') 44 | .where('sorteado', '!=', true) 45 | .orderBy('sorteado', 'asc') 46 | .orderBy('seed', 'desc') 47 | .limit(1) 48 | .get().then(s => { 49 | if (s.size == 0) { 50 | return null 51 | } 52 | return s.docs.map(d => { 53 | return { 54 | id: d.id, 55 | ...d.data() 56 | }; 57 | })[0]; 58 | 59 | }); 60 | 61 | if (sorteado) { 62 | 63 | // Atualizar status de sorteio 64 | await firestore.collection('events') 65 | .doc(event.evento).collection('sorteios') 66 | .doc(event.palavra).collection('participantes') 67 | .doc(sorteado.idPlayerEvent) 68 | .set({ 69 | sorteado: true, 70 | sorteadoAt: admin.firestore.FieldValue.serverTimestamp(), 71 | }, { merge: true }); 72 | 73 | // Carregar participante para obter número do WhatsApp 74 | let idPlayerEvent = sorteado.idPlayerEvent; 75 | let participanteId = sorteado.participanteId; 76 | 77 | let participante = await firestore.collection('events') 78 | .doc(event.evento).collection('participantes') 79 | .doc(idPlayerEvent).get().then(async s => { 80 | if (s.exists) { 81 | return s.data(); 82 | } else { 83 | return {} 84 | } 85 | }); 86 | 87 | if (participante) { 88 | 89 | await firestore.collection('events') 90 | .doc(event.evento).collection('participantes') 91 | .doc(idPlayerEvent).set({ 92 | sorteios: admin.firestore.FieldValue.increment(1), 93 | updatedAt: admin.firestore.FieldValue.serverTimestamp() 94 | }, { merge: true}); 95 | 96 | await firestore.collection('participantes') 97 | .doc(participante.participanteId).set({ 98 | sorteios: admin.firestore.FieldValue.increment(1), 99 | updatedAt: admin.firestore.FieldValue.serverTimestamp() 100 | }, { merge: true}); 101 | 102 | 103 | // Enviar mensagem para pessoa sorteada 104 | let mensagemSorteio = []; 105 | mensagemSorteio.push(`🚨 PARABÉNS 🚨`) 106 | mensagemSorteio.push(`Você foi uma pessoa contemplada no sorteio da palavra-chave *${event.palavra.toUpperCase()}*!`); 107 | mensagemSorteio.push(`Apresente esta mensagem para os organizadores do sorteio ou aguarde mais instruções caso esteja participando online.`); 108 | 109 | await sendNotification( 110 | client, 111 | event.to, 112 | `whatsapp:${participante.phoneNumber}`, 113 | mensagemSorteio.join('\n\n') 114 | ); 115 | 116 | // mensagem.push(`🎉 ${sorteado.telefone.split('*').join('∗')}: ${sorteado.nome} 🎉`); 117 | mensagem.push(`🎉 ${sorteado.telefone.split('*').join('∗')} 🎉\n\n(${participante.phoneNumber}): ${sorteado.nome}\n\nCidade: ${participante.cidade}`); 118 | // mensagem.push([ 119 | // `Você deseja que esta pessoa receba uma ligação?`, 120 | // `Responda com *SIM* ou *NÃO*.` 121 | // ].join('\n')); 122 | 123 | data.idPlayerEvent = idPlayerEvent; 124 | data.participanteId = participanteId; 125 | data.telefone = participante.phoneNumber; 126 | data.retornoSorteio = true; 127 | 128 | } 129 | } else { 130 | mensagem.push(`🚨 Nenhum registro encontrado para sorteio! 🚨`); 131 | data.retornoSorteio = false; 132 | } 133 | 134 | // TODO: retornar opção para ligação 135 | 136 | break; 137 | 138 | case 'LISTA': 139 | const sorteados = await firestore.collection('events') 140 | .doc(event.evento).collection('sorteios') 141 | .doc(event.palavra).collection('participantes') 142 | .where('sorteado', '==', true) 143 | .orderBy('sorteadoAt', 'desc') 144 | .get().then(s => { 145 | return s.docs.map(d => { 146 | return { 147 | id: d.id, 148 | ...d.data() 149 | }; 150 | }); 151 | }); 152 | 153 | const nomeSorteados = sorteados.map(item => 154 | `- ${item.telefone.split('*').join('∗')}: ${item.nome}` 155 | ); 156 | 157 | if (nomeSorteados.length == 0) { 158 | mensagem.push(`Nenhuma pessoa foi sorteada em *${event.palavra.toUpperCase()}*.`) 159 | } else { 160 | mensagem.push(`Lista de pessoas sorteadas em *${event.palavra.toUpperCase()}*:`); 161 | mensagem.push(nomeSorteados.join('\n')); 162 | } 163 | break; 164 | } 165 | 166 | 167 | data.mensagem = mensagem.join('\n\n'); 168 | 169 | callback(null, data); 170 | 171 | }; 172 | -------------------------------------------------------------------------------- /scripts/printers/print-thermal.js: -------------------------------------------------------------------------------- 1 | // Impressão das etiquetas com múltiplas impressoras. 2 | process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; 3 | require("dotenv").config(); 4 | 5 | let labelListenerUnsubscribe = null; 6 | const IP_UPDATE_INTERVAL = 15000; 7 | 8 | 9 | 10 | const PRINTER_NAME = process.env.PRINTER_NAME ?? 'COUPOM'; 11 | let PRINTER_IP = null; //'tcp://192.168.15.71'; 12 | let printer = null; 13 | const { ThermalPrinter, PrinterTypes, CharacterSet, BreakLine, CutOptions } = require('node-thermal-printer'); 14 | 15 | 16 | 17 | // Initialize Firebase 18 | var admin = require("firebase-admin"); 19 | if (!admin.apps.length) { 20 | admin.initializeApp(); 21 | }else { 22 | admin.app(); // if already initialized, use that one 23 | } 24 | const firestore = admin.firestore(); 25 | 26 | 27 | var os = require('os'); 28 | 29 | 30 | const printThermal = async (data) => { 31 | if (!PRINTER_IP) { 32 | console.log(`Error: no printer IP Address defined`); 33 | return false; 34 | } 35 | // try { 36 | console.log('Printing...', data); 37 | const printerConnected = await printer.isPrinterConnected(); 38 | if (!printerConnected) { 39 | console.log('ERROR - COUPOM PRINTER NOT CONNECTED!!!'); 40 | return false; 41 | } else { 42 | 43 | printer.alignCenter(); 44 | await printer.printImage('./assets/twilio-logo.png'); 45 | // .then(() => { 46 | 47 | printer.newLine(); 48 | printer.printQR(data.id, { // https://wa.me/551150393737?text=Teste 49 | cellSize: 8, // 1 - 8 50 | correction: 'M', // L(7%), M(15%), Q(25%), H(30%) 51 | model: 2 // 1 - Model 1 52 | // 2 - Model 2 (standard) 53 | // 3 - Micro QR 54 | }); 55 | printer.newLine(); 56 | printer.newLine(); 57 | printer.setTextQuadArea(); // Set text to quad area 58 | printer.setTypeFontB(); 59 | printer.println('CODIGO'); 60 | printer.setTypeFontA(); 61 | printer.invert(true); 62 | printer.setTextSize(3, 3); 63 | printer.println(` ${data.codigo} `); 64 | printer.invert(false); // Background/text color inversion 65 | // printer.setTextSize(); 66 | 67 | 68 | printer.setTypeFontB(); 69 | printer.setTextQuadArea(); // Set text to quad area 70 | printer.newLine(); 71 | printer.println(` ${data.pedido} `); 72 | if (data.settings) { 73 | printer.alignLeft(); 74 | printer.setTextDoubleHeight(); 75 | printer.newLine(); 76 | printer.println(data.settings.split('・ ').join('').split('\n').join(', ')); 77 | printer.newLine(); 78 | } 79 | printer.setTextNormal(); 80 | printer.alignCenter(); 81 | printer.println(`*** ${data.telefone} ***`); 82 | printer.newLine(); 83 | 84 | printer.cut(); 85 | 86 | // }).then(()=> { 87 | // return printer.execute(err => { 88 | // if (err) { 89 | // console.error('ERROR PRINTER EXECUTION', err); 90 | // return false; 91 | // } 92 | // }); 93 | 94 | // }); 95 | } 96 | 97 | // } catch (error) { 98 | // console.error('ERROR PRINTING', error); 99 | // return false; 100 | // } 101 | } 102 | 103 | let i = 0; 104 | const getLabelsForPrinter = async () => { 105 | 106 | return firestore.collection('labels') 107 | .where('printer', '==', PRINTER_NAME) 108 | .onSnapshot(async query => { 109 | if (query.size > 0) { 110 | console.log(query.size, 'records found!'); 111 | let batch = firestore.batch(); 112 | 113 | let tempLabels = []; 114 | query.forEach(async doc => { 115 | tempLabels.push(doc); 116 | }); 117 | 118 | if (printer && query.size > 0) { 119 | printer.clear(); 120 | } 121 | 122 | for (doc of tempLabels) { 123 | i++; 124 | const labelData = doc.data(); 125 | 126 | await printThermal(labelData); 127 | await batch.delete(doc.ref); 128 | } 129 | if (printer && query.size > 0) { 130 | printer.execute(err => { 131 | if (err) { 132 | console.error('ERROR PRINTER EXECUTION', err); 133 | // return false; 134 | } 135 | }); 136 | 137 | } 138 | await batch.commit(); 139 | // console.log('END OF THE LINE'); 140 | } 141 | }); 142 | } 143 | 144 | 145 | const updateIPAddress = async () => { 146 | 147 | var networkInterfaces = os.networkInterfaces(); 148 | let ips = Object.keys(networkInterfaces).map (interface => { 149 | return networkInterfaces[interface].filter(i => { 150 | return i.family == 'IPv4' && i.address != '127.0.0.1'; 151 | }); 152 | }).reduce((prevValue, currentValue, currentIdx, vector) => { 153 | return prevValue.concat(currentValue) 154 | }, []); 155 | 156 | firestore.collection('printers').doc(PRINTER_NAME).set( { 157 | ips, 158 | updatedAt: admin.firestore.FieldValue.serverTimestamp() 159 | }, { merge: true }) 160 | 161 | 162 | } 163 | 164 | (async () => { 165 | console.log('Initializing printing system...'); 166 | 167 | // Filter labels on Firestore tied to printers 168 | // labelListenerUnsubscribe = getLabelsForPrinter(); 169 | 170 | 171 | // Get printer IP 172 | firestore.collection('printers').doc(PRINTER_NAME).onSnapshot(async s => { 173 | const printerData = s.data(); 174 | 175 | if (printerData.ipAddress) { 176 | if (PRINTER_IP != printerData.ipAddress) { 177 | if (labelListenerUnsubscribe) { 178 | labelListenerUnsubscribe(); 179 | } 180 | 181 | PRINTER_IP = printerData.ipAddress; 182 | 183 | printer = new ThermalPrinter({ 184 | type: PrinterTypes.EPSON, 185 | interface: PRINTER_IP, 186 | characterSet: CharacterSet.PC860_PORTUGUESE 187 | }); 188 | printer.clear(); 189 | 190 | try { 191 | await printThermal({ 192 | id: 'https://wa.me/551150393737?text=BARISTA', 193 | codigo: '****', 194 | telefone: PRINTER_IP, 195 | pedido: `Connected to printer!` 196 | }); 197 | 198 | printer.execute(err => { 199 | if (err) { 200 | console.error('ERROR PRINTER EXECUTION', err); 201 | // return false; 202 | } 203 | }); 204 | 205 | } catch (error) { 206 | console.error('ERROR', error) 207 | } 208 | 209 | labelListenerUnsubscribe = await getLabelsForPrinter(); 210 | } 211 | 212 | } 213 | 214 | }); 215 | 216 | setInterval(updateIPAddress, IP_UPDATE_INTERVAL); 217 | console.log('System initialized!'); 218 | })(); 219 | 220 | 221 | -------------------------------------------------------------------------------- /eventos/functions/verifica-palestra.protected.js: -------------------------------------------------------------------------------- 1 | const admin = require('firebase-admin'); 2 | process.env.GOOGLE_APPLICATION_CREDENTIALS = Runtime.getAssets()['/firebase-credentials.json'].path; 3 | const { escondeNumero, limpaNumero, getDDD, sendNotification, convertNewLine, fillParams } = require(Runtime.getFunctions()['util'].path); 4 | 5 | if (!admin.apps.length) { 6 | admin.initializeApp({}); 7 | } else { 8 | admin.app(); 9 | } 10 | const firestore = admin.firestore(); 11 | const md5 = require('md5'); 12 | 13 | const {AIRTABLE_API_KEY, AIRTABLE_BASE_ID} = process.env; 14 | 15 | 16 | /* 17 | event: evento, token, from 18 | */ 19 | exports.handler = async function(context, event, callback) { 20 | const palestraId = event.token.toUpperCase(); 21 | const participanteId = await md5(limpaNumero(event.from)); 22 | let idPlayerEvent = await md5(`${event.evento}:${limpaNumero(event.from)}`); 23 | 24 | console.log('VERIFICA PALESTRA: ', event, participanteId, idPlayerEvent); 25 | 26 | var Airtable = require('airtable'); 27 | var base = new Airtable({ apiKey: AIRTABLE_API_KEY }).base(AIRTABLE_BASE_ID); 28 | 29 | // TODO: carregar palestra do banco de dados 30 | const palestra = await firestore.collection('events').doc(event.evento) 31 | .collection('palestras').doc(palestraId).get(); 32 | if (!palestra.exists) { 33 | return callback(null, { id: palestraId, palestra: null, err: { 34 | message: 'Esta palestra não foi encontrada em nossos registros!' 35 | }}); 36 | } 37 | 38 | const palestraData = palestra.data(); 39 | 40 | const voto = await firestore.collection('events').doc(event.evento) 41 | .collection('participantes').doc(idPlayerEvent) 42 | .collection('feedbacks').doc(palestraId.toUpperCase()).get(); 43 | 44 | 45 | // Verificar voto também pelo banco de dados 46 | return callback(null, { id: palestraId, palestra: palestraData, votou: voto.exists, err: null}); 47 | 48 | 49 | // // Verificar se palestra está no Airtable 50 | // await base('Palestras').select({ 51 | // filterByFormula:`{ID}="${palestraId}"`, 52 | // fields: [ 53 | // 'ID', 54 | // 'Horario', 55 | // 'Titulo', 56 | // 'Status', 57 | // 'Palestrante_Linkedin', 58 | // 'Palestrante_Nome' 59 | // ] 60 | // }).firstPage(async (err, records) => { 61 | // if (err) { 62 | // console.error(err); 63 | // return callback(null, { id: null, fields: null, votou: false, err }); 64 | // } 65 | 66 | // if (records.length == 0) { 67 | // return callback(null, { id: null, fields: null, votou: false, err: { 68 | // message: 'Nenhum registro encontrado' 69 | // }}); 70 | // } 71 | 72 | // const { id, fields } = records[0]; 73 | // await base('Avaliacoes').select({filterByFormula:`{ID}="${idPlayerEvent}_${palestraId}"`}).firstPage(async (err, avaliacaoRecords) => { 74 | // if (err) { 75 | // return callback(null, { id: null, fields: null, votou: false, err: null}); 76 | // } 77 | 78 | // return callback(null, { id, fields, votou: avaliacaoRecords.length > 0, err: null}); 79 | // }); 80 | 81 | // }); 82 | 83 | 84 | 85 | 86 | // let palestra = await firestore.collection('events').doc(event.evento).collection('palestras').doc(palestraId).get(); 87 | // if (!activation.exists) { 88 | // console.log(`Palestra ${palestraId} NÃO EXISTE!`); 89 | // return callback(null, { 90 | // type: 'not-found' 91 | // }); 92 | // } 93 | // const palestraData = palestra.data(); 94 | 95 | // if (!palestraData.open) { 96 | // return callback(null, { 97 | // type: 'closed' 98 | // }); 99 | // } 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | // TODO: verificar no airtable se existe esta palestra 109 | // TODO: verificar se palestra está aberta 110 | // TODO: verificar se participante já deu feedback 111 | // TODO: retornar sobre 112 | 113 | 114 | // callback(null, {}); 115 | 116 | 117 | // const client = await context.getTwilioClient(); 118 | // if (!event.token) { 119 | // console.log('TOKEN VAZIO'); 120 | // return callback(null, { 121 | // type: 'not-found' 122 | // }); 123 | // } 124 | 125 | // let activation = await firestore.collection('events').doc(event.evento).collection('palavras').doc(event.token).get(); 126 | // if (!activation.exists) { 127 | // activation = await firestore.collection('events').doc(event.evento).collection('palavras').doc(event.token.toLowerCase()).get(); 128 | // if (!activation.exists) { 129 | // // token não encontrado 130 | // console.log(event.token, 'Palavra não existe no evento', event.evento); 131 | // return callback(null, { 132 | // type: 'not-found' 133 | // }); 134 | // } 135 | // } 136 | 137 | // let activationData = activation.data(); 138 | // if (activationData.mensagem) { 139 | // activationData.mensagem = convertNewLine(activationData.mensagem); 140 | // } 141 | // let callbackData = { 142 | // ...activationData 143 | // // type: activationData.type || 'activation', 144 | // } 145 | 146 | // switch(activationData.type) { 147 | // case 'impressao_manual': 148 | // break; 149 | 150 | // case 'reimpressao': 151 | // break; 152 | 153 | // case 'impressao': 154 | // // Carregar dados de participante 155 | // let participante = await firestore.collection('events').doc(event.evento).collection('participantes') 156 | // .doc(participanteId) 157 | // .get() 158 | // .then( p => p.exists ? p.data() : null ); 159 | 160 | // if (!participante) { 161 | // // Verificar se tem dado do participante geral e copiar 162 | // const participanteGeral = await firestore.collection('participantes') 163 | // .doc(participanteId) 164 | // .get() 165 | // .then( p => p.exists ? p.data() : null ); 166 | 167 | // if (!participanteGeral) { 168 | // // Erro: participante não encontrato no evento! 169 | // return callback(null, { 170 | // type: activationData.type, 171 | // mensagem: 'Erro!\n\nParticipante não encontrado neste evento.' 172 | // }); 173 | // } 174 | // participante = participanteGeral; 175 | // } 176 | 177 | // await firestore.collection('labels').add({ 178 | // evento: event.evento, 179 | // printer: activationData.printer, 180 | // participanteId, 181 | // ...participante 182 | // }); 183 | 184 | // await firestore.collection('printers').doc(activationData.printer).set({ 185 | // impressoes: admin.firestore.FieldValue.increment(1), 186 | // updatedAt: admin.firestore.FieldValue.serverTimestamp() 187 | // }, { merge: true }); 188 | 189 | // if (activationData.mediaUrl) { 190 | // activationData.mediaUrl = fillParams(activationData.mediaUrl, { 191 | // participanteId, 192 | // ...participante 193 | // }); 194 | // } 195 | // break; 196 | 197 | // case 'quiz': 198 | // break; 199 | 200 | // case 'certificado': 201 | // break; 202 | 203 | // default: // 'conteudo' 204 | // break; 205 | // } 206 | // callback(null, { 207 | // ...activationData 208 | // }); 209 | 210 | }; 211 | -------------------------------------------------------------------------------- /eventos/assets/coordenador.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Coodenador 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 123 | 124 | 125 | 126 | 127 | 128 | 129 |
130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 |
138 | 139 | 140 | 141 |
142 |
    143 | 144 |
145 |
146 | 229 | 230 | -------------------------------------------------------------------------------- /eventos/functions/fotografia-finaliza.js: -------------------------------------------------------------------------------- 1 | const admin = require('firebase-admin'); 2 | 3 | process.env.GOOGLE_APPLICATION_CREDENTIALS = Runtime.getAssets()['/firebase-credentials.json'].path; 4 | const { escondeNumero, limpaNumero, getDDD, sendNotification, sendNotificationMedia, convertNewLine, fillParams } = require(Runtime.getFunctions()['util'].path); 5 | 6 | if (!admin.apps.length) { 7 | admin.initializeApp({}); 8 | } else { 9 | admin.app(); 10 | } 11 | const firestore = admin.firestore(); 12 | 13 | const TIPOS_ARQUIVOS_PERMITIDOS = [ 14 | 'image/jpeg', 15 | 'image/png' 16 | ]; 17 | 18 | const { GoogleAuth } = require('google-auth-library'); 19 | const { google } = require('googleapis'); 20 | 21 | const auth = new GoogleAuth({ scopes: 'https://www.googleapis.com/auth/drive' }); 22 | const service = google.drive({ version: 'v3', auth }); 23 | 24 | 25 | // let log = []; 26 | let client, participante; 27 | 28 | const batch = firestore.batch(); 29 | 30 | /* 31 | event: filaId, pasta 32 | */ 33 | exports.handler = async function(context, event, callback) { 34 | client = await context.getTwilioClient(); 35 | 36 | participante = await firestore.collection('participantes') 37 | .doc(event.filaId).get().then(s => { 38 | if (s.exists) { 39 | return s.data(); 40 | } else { 41 | return null; 42 | } 43 | }); 44 | 45 | if (!participante) { 46 | 47 | return callback(null, { 48 | erro: true, 49 | mensagem: 'Participante não encontrado!\n\nInforme para responsável da Twilio.' 50 | }); 51 | 52 | } 53 | 54 | const agendamento = await firestore.collection('events') 55 | .doc(participante.ultimoEvento).collection('agendamento') 56 | .doc(event.filaId).get().then(s => { 57 | if (s.exists) { 58 | return s.data(); 59 | } else { 60 | return null; 61 | } 62 | }); 63 | 64 | if (!agendamento) { 65 | return callback(null, { 66 | erro: true, 67 | mensagem: 'Registro de fila não encontrado!\n\nInforme para responsável da Twilio.' 68 | }); 69 | } 70 | 71 | if (!event.pasta) { 72 | return callback(null, { 73 | erro: true, 74 | mensagem: 'Participante não possui pasta do DRIVE!\n\nRetorne para a fila e tente novamente.' 75 | }); 76 | } 77 | 78 | 79 | agendamento.pasta = event.pasta; 80 | 81 | // Carregar arquivos da pasta do Drive 82 | 83 | 84 | const arquivos = await service.files.list({ 85 | q: `'${agendamento.pasta}' in parents` 86 | }).then(r => { 87 | return r.data 88 | }); 89 | 90 | console.log('RESULT', arquivos); 91 | if (!arquivos || !arquivos.files) { 92 | return callback(null, { 93 | erro: true, 94 | mensagem: 'Participante não possui pasta do DRIVE!\n\nRetorne para a fila e tente novamente.' 95 | }); 96 | 97 | } 98 | 99 | 100 | const fotos = arquivos.files.filter( f => TIPOS_ARQUIVOS_PERMITIDOS.includes(f.mimeType)).map( f => { 101 | return { ...f, filaId: event.filaId} 102 | }); 103 | 104 | if (fotos.length == 0) { 105 | return callback(null, { 106 | erro: true, 107 | mensagem: 'Nenhuma foto encontrada no DRIVE!\n\nTente finalizar novamente por favor.' 108 | }); 109 | } 110 | 111 | console.log('FOTOS', fotos); 112 | 113 | let mensagem = []; 114 | if (fotos.length > 1) { 115 | mensagem.push(`🚨 ${participante.nome}, aqui estão as fotos do Connect Foto Iteris! 🚨`); 116 | } else { 117 | mensagem.push(`🚨 ${participante.nome}, aqui está a foto do Connect Foto Iteris! 🚨`); 118 | } 119 | mensagem.push(`Temos um total de ${fotos.length} foto${fotos.length > 1 ? 's' : ''} 🎉`) 120 | mensagem.push(`Você pode acessar os originais pelo link https://drive.google.com/drive/folders/${agendamento.pasta}`); 121 | 122 | 123 | 124 | // console.log(); 125 | // console.log(mensagem.join('\n\n')); 126 | // console.log(); 127 | 128 | try { 129 | 130 | const promises = fotos.map(adicionaFilaEnvio); 131 | await Promise.all(promises); 132 | 133 | await batch.commit(); 134 | 135 | // const promises = fotos.map(processaFotosFirebase); 136 | // await Promise.all(promises); 137 | } catch (x) { 138 | console.log('ERROR', x); 139 | } 140 | 141 | console.log('concluido'); 142 | 143 | 144 | 145 | // Mudar status para 'concluido' 146 | await firestore.collection('events') 147 | .doc(participante.ultimoEvento).collection('agendamento') 148 | .doc(event.filaId).set({ 149 | pasta: event.pasta, 150 | updatedAt: admin.firestore.FieldValue.serverTimestamp(), 151 | status: 'concluido', 152 | totalFotos: fotos.length 153 | }, { merge: true }); 154 | 155 | 156 | await sendNotification( 157 | client, 158 | `whatsapp:${process.env.TWILIO_WHATSAPP_NUMBER_TDC}`, 159 | `whatsapp:${participante.phoneNumber}`, 160 | mensagem.join('\n\n') 161 | ); 162 | 163 | // Adicionar contador de fotos enviadas 164 | await firestore.collection('participantes').doc(event.filaId).set({ 165 | updatedAt: admin.firestore.FieldValue.serverTimestamp(), 166 | fotografiaEnviadas: admin.firestore.FieldValue.increment(fotos.length) 167 | }, { merge: true }); 168 | 169 | await firestore.collection('events').doc(participante.ultimoEvento) 170 | .collection('participantes').doc(event.filaId).set({ 171 | updatedAt: admin.firestore.FieldValue.serverTimestamp(), 172 | fotografiaEnviadas: admin.firestore.FieldValue.increment(fotos.length) 173 | }, { merge: true }); 174 | 175 | 176 | 177 | return callback(null, { 178 | erro: false, 179 | mensagem: 'Atendimento finalizado com sucesso!', 180 | // log 181 | }); 182 | 183 | 184 | }; 185 | 186 | 187 | async function adicionaFilaEnvio(f) { 188 | 189 | let ref = firestore.collection('envioFoto').doc(); 190 | batch.set(ref, { 191 | fileId: f.id, 192 | name: f.name, 193 | contentType: f.mimeType, 194 | participanteId: f.filaId, 195 | telefone: participante.phoneNumber, 196 | destino: `fotos/${f.filaId}/${f.id}`, 197 | createdAt: admin.firestore.FieldValue.serverTimestamp(), 198 | from: `whatsapp:${process.env.TWILIO_WHATSAPP_NUMBER_TDC}`, 199 | to: `whatsapp:${participante.phoneNumber}` 200 | }); 201 | 202 | // await firestore.collection('envioFoto').add({ 203 | // fileId: f.id, 204 | // name: f.name, 205 | // contentType: f.mimeType, 206 | // participanteId: f.filaId, 207 | // telefone: participante.phoneNumber, 208 | // destino: `fotos/${f.filaId}/${f.id}`, 209 | // createdAt: admin.firestore.FieldValue.serverTimestamp(), 210 | // from: `whatsapp:${process.env.TWILIO_WHATSAPP_NUMBER_TDC}`, 211 | // to: `whatsapp:${participante.phoneNumber}` 212 | // }); 213 | } 214 | 215 | async function processaFotosFirebase(f) { 216 | // try { 217 | console.log(`DOWNLOADING ${f.id}, ${f.mimeType}`); 218 | 219 | const urlArquivoAssinada = await service.files.get( 220 | { fileId: f.id, alt: 'media' }, 221 | { responseType: 'stream' } 222 | ).then( async (res) => { 223 | return new Promise((resolve, reject) => { 224 | 225 | const bucket = admin.storage().bucket(process.env.FOTOGRAFIA_BUCKET_PADRAO); 226 | console.log(`fotos/${f.filaId}/${f.id}`); 227 | 228 | const cloudFile = bucket.file(`fotos/${f.filaId}/${f.id}`); 229 | let cloudStream = cloudFile.createWriteStream({ 230 | contentType: f.mimeType, 231 | resumable: false, 232 | // public: true 233 | }); 234 | 235 | res.data.on('end', async () => { 236 | console.log('DATA END'); 237 | const URL_ASSINADA = await cloudFile.getSignedUrl({ 238 | action: 'read', 239 | expires: new Date(Date.now() + (3600 * 1000 * 24)) 240 | }).then(signedUrls => { 241 | console.log(`SIGNED ${signedUrls[0]}`) 242 | return signedUrls[0]; 243 | }); 244 | resolve(URL_ASSINADA); 245 | }).pipe(cloudStream); //, { end: true }); 246 | 247 | }); 248 | }); 249 | 250 | // Envio imagem pelo WhatsApp 251 | await sendNotificationMedia( 252 | client, 253 | `whatsapp:${process.env.TWILIO_WHATSAPP_NUMBER_TDC}`, 254 | `whatsapp:${participante.phoneNumber}`, 255 | '', 256 | urlArquivoAssinada 257 | ); 258 | 259 | } -------------------------------------------------------------------------------- /eventos/functions/verifica-participante.protected.js: -------------------------------------------------------------------------------- 1 | const admin = require('firebase-admin'); 2 | process.env.GOOGLE_APPLICATION_CREDENTIALS = Runtime.getAssets()['/firebase-credentials.json'].path; 3 | const { escondeNumero, limpaNumero, getDDD, sendNotification, convertNewLine, fillParams, adicionaNove } = require(Runtime.getFunctions()['util'].path); 4 | const { checkUser } = require(Runtime.getFunctions()['codecon'].path); 5 | 6 | 7 | if (!admin.apps.length) { 8 | admin.initializeApp({}); 9 | } else { 10 | admin.app(); 11 | } 12 | const firestore = admin.firestore(); 13 | const md5 = require('md5'); 14 | 15 | 16 | 17 | /* 18 | event: evento, token, from 19 | */ 20 | exports.handler = async function(context, event, callback) { 21 | let participanteId = await md5(limpaNumero(event.from)); 22 | let idPlayerEvent = await md5(`${event.evento}:${limpaNumero(event.from)}`); 23 | 24 | console.log('EVENT PARTICIPANTE', event); 25 | 26 | 27 | let evento = await firestore.collection('events') 28 | .doc(event.evento).get().then(async s => { 29 | if (s.exists) { 30 | return s.data(); 31 | } else { 32 | return null; 33 | } 34 | }); 35 | 36 | 37 | // Registrar participante na base geral 38 | await firestore.collection('participantes') 39 | .doc(participanteId).set({ 40 | phoneNumber: limpaNumero(event.from), 41 | idPlayerEvent: idPlayerEvent, 42 | profileName: event.profileName || '', 43 | ultimoEvento: event.evento, 44 | updatedAt: admin.firestore.FieldValue.serverTimestamp() 45 | }, { merge: true }); 46 | 47 | let participanteGeral = await firestore.collection('participantes') 48 | .doc(participanteId).get().then(async s => { 49 | if (s.exists) { 50 | return s.data(); 51 | } else { 52 | return {} 53 | } 54 | }); 55 | 56 | let participante = await firestore.collection('events') 57 | .doc(event.evento).collection('participantes') 58 | .doc(idPlayerEvent).get().then(async s => { 59 | if (s.exists) { 60 | return s.data(); 61 | } else { 62 | await firestore.collection('events') 63 | .doc(event.evento).collection('participantes') 64 | .doc(idPlayerEvent).set({ 65 | idPlayerEvent, 66 | participanteId, 67 | createdAt: admin.firestore.FieldValue.serverTimestamp(), 68 | phoneNumber: limpaNumero(event.from), 69 | profileName: event.profileName || '', 70 | pontosAcumulados: 0, 71 | pontosCorrente: 0, 72 | recebeuOptin: false, 73 | ativouNetworking: false, 74 | possuiRegistro: false, 75 | impressoes: 0, 76 | codeconAtivado: false 77 | }); 78 | return { 79 | pontosAcumulados: 0, 80 | pontosCorrente: 0, 81 | recebeuOptin: false, 82 | ativouNetworking: false, 83 | possuiRegistro: false, 84 | impressoes: 0, 85 | codeconAtivado: false 86 | }; 87 | } 88 | }); 89 | 90 | let agendamentoTotal = null; 91 | let agendamentoPosicao = null; 92 | 93 | 94 | // TODO: verificar opt-in 95 | // /events/{eventId}/participantes/{participanteId}/ [recebeuOptin, aceitouOptin] 96 | // data.recebeuOptin = participante.recebeuOptin || false; 97 | 98 | 99 | // Verificar se tem agendamento de foto profissional, posição na fila e tempo 100 | // /events/{eventId}/agendamento/{participanteId} 101 | 102 | 103 | let agendamento = await firestore.collection('events') 104 | .doc(event.evento).collection('agendamento') 105 | .doc(idPlayerEvent).get().then( s => { 106 | if (s.exists) { 107 | return s.data(); 108 | } else { 109 | return null; 110 | } 111 | }); 112 | 113 | if (agendamento) { 114 | agendamentoTotal = await firestore.collection('events') 115 | .doc(event.evento).collection('agendamento') 116 | .get().then(s => { 117 | return s.size; 118 | }); 119 | 120 | agendamentoPosicao = await firestore.collection('events') 121 | .doc(event.evento).collection('agendamento') 122 | .where('posicao', '<', agendamento.posicao) 123 | .where('status', '==', 'fila') 124 | .get().then(s => { 125 | return s.size + 1; 126 | }); 127 | agendamento.posicao = agendamentoPosicao; 128 | } 129 | 130 | 131 | let podeResgatar = true; 132 | let podeResgatarBarista = true; 133 | let proximoResgate = '' 134 | let proximoResgateBarista = ''; 135 | 136 | 137 | if (participante.ultimoResgate && evento.tempoMinimoResgate) { 138 | 139 | // evento.limiteTempoResgate 140 | const current_timestamp = admin.firestore.Timestamp.fromDate(new Date()); 141 | const tempo = (current_timestamp.toDate().getTime() - participante.ultimoResgate.toDate().getTime())/1000/60; 142 | 143 | podeResgatar = tempo >= evento.tempoMinimoResgate; 144 | proximoResgate = Math.ceil(evento.tempoMinimoResgate - tempo); 145 | 146 | } 147 | 148 | if (evento.barista) { 149 | if (participante.ultimoResgateBarista && evento.barista.tempoMinimoResgate) { 150 | 151 | // evento.limiteTempoResgate 152 | const current_timestamp = admin.firestore.Timestamp.fromDate(new Date()); 153 | const tempo = (current_timestamp.toDate().getTime() - participante.ultimoResgateBarista.toDate().getTime())/1000/60; 154 | 155 | podeResgatarBarista = tempo >= evento.barista.tempoMinimoResgate; 156 | proximoResgateBarista = Math.ceil(evento.barista.tempoMinimoResgate - tempo); 157 | 158 | } 159 | 160 | } 161 | 162 | 163 | let data = { 164 | participante: { 165 | ...participante, 166 | podeResgatar, 167 | proximoResgate, 168 | twilion: participanteGeral.twilion, 169 | isAdmin: participanteGeral.twilion || participanteGeral.isAdmin || participante.twilion || participante.isAdmin, 170 | gerenciaSorteio: participanteGeral.gerenciaSorteio || participante.gerenciaSorteio, 171 | videomatikUnlimited: participanteGeral.videomatikUnlimited, 172 | coffeeUnlimited: participanteGeral.coffeeUnlimited 173 | }, 174 | evento, 175 | agendamento, 176 | agendamentoPosicao, 177 | agendamentoTotal, 178 | videomatik: evento.videomatik 179 | }; 180 | 181 | let mensagem = []; 182 | let vendingmachine = null; 183 | 184 | const IS_ADMIN = participanteGeral.isAdmin || participanteGeral.twilion || participante.isAdmin || participante.twilion; 185 | const GERENCIA_SORTEIO = participanteGeral.gerenciaSorteio || participante.gerenciaSorteio; 186 | 187 | 188 | 189 | switch (event.evento) { 190 | 191 | case 'signalsingapore2023': 192 | mensagem.push(`Welcome to *SIGNAL Singapore*!`); 193 | 194 | if (evento.barista && evento.barista.enabled) { 195 | if (evento.barista && evento.barista.enabled) { 196 | if (participanteGeral.coffeeUnlimited || participante.coffeeUnlimited) { 197 | mensagem.push(`☕️ Café *ILIMITADO!* ☕️ Envie *BARISTA* para pedir o seu.`); 198 | } else { 199 | if (!podeResgatarBarista) { 200 | mensagem.push(`Você pode resgatar seu próximo ☕️ em ${proximoResgateBarista} minuto(s).`) 201 | } else { 202 | mensagem.push(`Quer um café especial? Envie *BARISTA* para pedir o seu.`); 203 | } 204 | 205 | } 206 | 207 | } 208 | } 209 | 210 | break; 211 | } 212 | 213 | 214 | 215 | // Main Admin Settings 216 | if (IS_ADMIN) { 217 | mensagem.push([ 218 | `🚨 *ADMIN* 🚨`, 219 | `Send participant ID to manage points and settings.`, 220 | // `Envie *ESTOQUE* para gerenciar a vending machine.` 221 | ].join('\n')); 222 | 223 | let totalParticipantes = (await firestore.collection('events') 224 | .doc(event.evento).collection('participantes').get()).size; 225 | 226 | mensagem.push(`Total Participants: *${totalParticipantes}*`); 227 | // mensagem.push(`Participantes: *${totalParticipantes}*\nResgates: *${evento.resgates}*\nVideomatik: *${evento.stats.videomatik}*`); 228 | 229 | } 230 | 231 | // Raffle Admin Settings 232 | if (GERENCIA_SORTEIO) { 233 | mensagem.push([ 234 | `🚨 *RAFFLES* 🚨`, 235 | `Send *SORTEIO* to manage a raffle.` 236 | ].join('\n')); 237 | } 238 | 239 | 240 | data.mensagem = mensagem.join('\n\n'); 241 | callback(null, data); 242 | 243 | }; 244 | -------------------------------------------------------------------------------- /eventos/assets/javascript/orders.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | DEPRECATED: remover este arquivo em 7/agosto/2024 4 | */ 5 | 6 | import * as firebase from "https://www.gstatic.com/firebasejs/9.18.0/firebase-app.js"; 7 | import { 8 | getFirestore, 9 | onSnapshot, 10 | collection, 11 | query, 12 | where, 13 | orderBy, 14 | limit, 15 | } from "https://www.gstatic.com/firebasejs/9.18.0/firebase-firestore.js"; 16 | 17 | 18 | 19 | 20 | const urlParams = new URLSearchParams(window.location.search); 21 | const CURRENT_EVENT = urlParams.get('currentEvent'); 22 | if (!CURRENT_EVENT) { 23 | alert('You need to add the query param currentEvent before continue!'); 24 | } else { 25 | 26 | const firebaseConfig = { 27 | apiKey: "AIzaSyC6I8Rwo9YiJgd7zK-U5RpHBZDSPsDt0ME", 28 | authDomain: "br-events.firebaseapp.com", 29 | projectId: "br-events", 30 | storageBucket: "br-events.appspot.com", 31 | messagingSenderId: "603831187236", 32 | appId: "1:603831187236:web:b1b3e97f5c6a38a4eb25b1", 33 | }; 34 | 35 | const app = firebase.initializeApp(firebaseConfig); 36 | const db = getFirestore(app); 37 | 38 | const todo = document.getElementById("to-do"); 39 | const q = query( 40 | collection(db, "events", CURRENT_EVENT, "orders"), 41 | where("status", "in", ["pendente", "preparo"]), 42 | orderBy("createdAt", "asc") 43 | ); 44 | 45 | function toDateTime(secs) { 46 | var t = new Date(1970, 0, 1); // Epoch 47 | t.setSeconds(secs); 48 | return t; 49 | } 50 | 51 | const sendPost = (payload) => { 52 | try { 53 | fetch("https://eventos-2246-dev.twil.io/barista-status", { 54 | method: "POST", 55 | headers: { 56 | "Content-Type": "application/json", 57 | }, 58 | body: JSON.stringify(payload) 59 | }) 60 | } catch (err) { 61 | 62 | } 63 | } 64 | 65 | const cityCheckbox = document.getElementById("city"); 66 | 67 | const check = () => { 68 | // document.body.removeAttribute('class'); 69 | // document.body.classList.add(cityCheckbox.checked ? 'recife' : 'bh'); 70 | 71 | // if (cityCheckbox.checked) { 72 | // todo.querySelectorAll("li").forEach((li) => { 73 | // if (li.getAttribute("data-city") !== "Recife") { 74 | // li.style.display = "none"; 75 | // } 76 | // if (li.getAttribute("data-city") === "Recife") { 77 | // li.style.display = "block"; 78 | // } 79 | // }); 80 | // } else { 81 | // todo.querySelectorAll("li").forEach((li) => { 82 | // if (li.getAttribute("data-city") !== "Belo Horizonte") { 83 | // li.style.display = "none"; 84 | // } 85 | // if (li.getAttribute("data-city") === "Belo Horizonte") { 86 | // li.style.display = "block"; 87 | // } 88 | // }); 89 | // } 90 | } 91 | 92 | // cityCheckbox.addEventListener("change", (e) => { 93 | // check(); 94 | // }); 95 | 96 | const NomeCafe = (tipo) => { 97 | console.log('TIPO', tipo); 98 | switch(tipo) { 99 | // VAMOS LATAM 2023 100 | case 'COFFEE_ESPRESSO': return 'Espresso'; 101 | case 'COFFEE_MACCHIATO': return 'Macchiato'; 102 | case 'COFFEE_CAPPUCCINO': return 'Cappuccino'; 103 | 104 | // // Codecon Summit 2023 105 | // case 'COFFEE_ESPRESSO': return 'Espresso'; 106 | // case 'COFFEE_ESPRESSOLEITE': return 'Espresso com Leite'; 107 | // case 'COFFEE_LATTE': return 'Latte'; 108 | // case 'COFFEE_MACCHIATO': return 'Macchiato'; 109 | // case 'COFFEE_CAPPUCCINO': return 'Cappuccino'; 110 | // case 'COFFEE_CARAMELO': return 'Caramelo Macchiato'; 111 | 112 | // // TDC Connections 2023 113 | // case 'COFFEE_RAFAELOLIVEIRA': return 'Rafael Oliveira'; 114 | // case 'COFFEE_JOAOWALTERAMARAL': return 'João Walter Amaral'; 115 | // case 'COFFEE_JOSEALDO': return 'José Aldo'; 116 | 117 | // // TDC Business 2023 118 | // case 'COFFEE_JOSEALDO': return 'José Aldo'; 119 | // case 'COFFEE_JOAOPAULO': return 'João Paulo Capobianco'; 120 | // case 'COFFEE_ALESSANDRAMICHEL': return 'Alessandra e Michel'; 121 | default: return tipo.replace('COFFEE_', '') 122 | 123 | } 124 | } 125 | 126 | window.load = function () { 127 | onSnapshot(q, (querySnapshot) => { 128 | todo.innerHTML = ""; 129 | 130 | if (querySnapshot.docs.length == 0) { 131 | // TODO: adicionar nome da trilha no topo 132 | document.getElementById("resumo").innerText = "Nenhum pedido encontrado!"; 133 | } else { 134 | document.getElementById("resumo").innerText = ""; 135 | 136 | } 137 | 138 | 139 | querySnapshot.forEach((doc) => { 140 | const cafe = doc.data(); 141 | const li = document.createElement("li"); 142 | 143 | // li.classList.add(cafe.cidade == 'Recife' ? cafe.cidade.toLowerCase() : 'bh'); 144 | li.classList.add(cafe.status); 145 | // console.log('NomeCafe(cafe.pedido)', NomeCafe(cafe.pedido)) 146 | 147 | li.innerHTML = `${cafe.telefone}: ${typeof cafe.pedido == 'string' ? cafe.pedido : cafe.pedido.name || cafe.pedido.product_retailer_id} 148 | 149 | 150 | 151 | 152 | 153 | 154 | `; 155 | 156 | li.setAttribute("data-id", doc.id); 157 | li.setAttribute("data-city", cafe.cidade); 158 | 159 | todo.appendChild(li); 160 | const chamarButton = document.getElementById(`chamar-${doc.id}`); 161 | chamarButton.addEventListener("click", () => { 162 | console.log("chamar"); 163 | sendPost({ 164 | filaId: doc.id, 165 | status: "chamar", 166 | evento: cafe.evento, 167 | idPlayerEvent: cafe.idPlayerEvent, 168 | }) 169 | }); 170 | 171 | const prepararButton = document.getElementById(`preparar-${doc.id}`); 172 | prepararButton.addEventListener("click", () => { 173 | console.log("preparar"); 174 | sendPost({ 175 | filaId: doc.id, 176 | status: "preparo", 177 | evento: cafe.evento, 178 | idPlayerEvent: cafe.idPlayerEvent, 179 | }) 180 | }); 181 | 182 | const cancelarButton = document.getElementById(`cancelar-${doc.id}`); 183 | cancelarButton.addEventListener("click", () => { 184 | console.log("cancelar"); 185 | if (confirm('Deseja cancelar este pedido?')) { 186 | sendPost({ 187 | filaId: doc.id, 188 | status: "cancelado", 189 | evento: cafe.evento, 190 | idPlayerEvent: cafe.idPlayerEvent, 191 | }) 192 | } 193 | }); 194 | 195 | const prontoButton = document.getElementById(`pronto-${doc.id}`); 196 | prontoButton.addEventListener("click", () => { 197 | console.log("pronto"); 198 | sendPost({ 199 | filaId: doc.id, 200 | status: "pronto", 201 | evento: cafe.evento, 202 | idPlayerEvent: cafe.idPlayerEvent, 203 | }) 204 | }); 205 | 206 | 207 | const reprintButton = document.getElementById(`reprint-${doc.id}`); 208 | reprintButton.addEventListener("click", () => { 209 | console.log("reprint"); 210 | sendPost({ 211 | filaId: doc.id, 212 | status: "reprint", 213 | evento: cafe.evento, 214 | idPlayerEvent: cafe.idPlayerEvent, 215 | }) 216 | }); 217 | 218 | check(); 219 | }); 220 | }); 221 | }; 222 | 223 | } 224 | 225 | --------------------------------------------------------------------------------