├── .all-contributorsrc ├── .env.development ├── .github └── pull_request_template.md ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.md ├── Procfile ├── README.md ├── build ├── app.js ├── commands │ └── open.js ├── config.js ├── database │ └── index.js ├── useCases │ ├── handleCommands │ │ └── handleCommands.js │ ├── initTemplates │ │ └── initTemplates.js │ └── openCommands.js ├── useCommands │ ├── count │ │ └── count.js │ ├── help │ │ └── help.js │ └── index.js └── utils │ ├── findCommandByAction.js │ ├── getSanitizedRender.js │ ├── sendErrorCommand.js │ ├── turnOnAutomaticMessages.js │ └── utils.js ├── commands.json ├── commands.rahmaidev.json ├── package.json ├── src ├── app.ts ├── commands │ └── open.ts ├── config.ts ├── database │ ├── db.json │ └── index.ts ├── useCases │ ├── handleCommands │ │ └── handleCommands.ts │ ├── initTemplates │ │ └── initTemplates.ts │ └── openCommands.ts ├── useCommands │ ├── count │ │ └── count.js │ ├── help │ │ └── help.ts │ └── index.ts └── utils │ ├── findCommandByAction.ts │ ├── getSanitizedRender.ts │ ├── sendErrorCommand.ts │ ├── turnOnAutomaticMessages.ts │ └── utils.ts └── tsconfig.json /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "README.md" 4 | ], 5 | "imageSize": 100, 6 | "commit": false, 7 | "contributorsSortAlphabetically": true, 8 | "contributors": [ 9 | { 10 | "login": "DevRadhy", 11 | "name": "Lucas Jantsch Guedes", 12 | "avatar_url": "https://avatars.githubusercontent.com/u/50425715?v=4", 13 | "profile": "https://github.com/DevRadhy", 14 | "contributions": [ 15 | "developer" 16 | ] 17 | }, 18 | { 19 | "login": "jakeliny", 20 | "name": "Jakeliny Gracielly", 21 | "avatar_url": "https://avatars.githubusercontent.com/u/17316392?v=4", 22 | "profile": "http://jakeliny.com.br", 23 | "contributions": [ 24 | "developer" 25 | ] 26 | }, 27 | { 28 | "login": "jorge-lba", 29 | "name": "Jorge Alegretti", 30 | "avatar_url": "https://avatars.githubusercontent.com/u/56704254?v=4", 31 | "profile": "https://github.com/jorge-lba", 32 | "contributions": [ 33 | "developer" 34 | ] 35 | }, 36 | { 37 | "login": "diegosparente", 38 | "name": "Diego Parente", 39 | "avatar_url": "https://avatars.githubusercontent.com/u/7936432?v=4", 40 | "profile": "http://www.linkedin.com/in/diego-parente-56358998", 41 | "contributions": [ 42 | "contributor" 43 | ] 44 | }, 45 | { 46 | "login": "rafaelnq", 47 | "name": "Rafael Nobre", 48 | "avatar_url": "https://avatars.githubusercontent.com/u/80119382?v=4", 49 | "profile": "https://github.com/rafaelnq", 50 | "contributions": [ 51 | "contributor" 52 | ] 53 | }, 54 | { 55 | "login": "geovanipfranca", 56 | "name": "Geovani França", 57 | "avatar_url": "https://avatars.githubusercontent.com/u/31046316?v=4", 58 | "profile": "http://geovani.dev", 59 | "contributions": [ 60 | "contributor" 61 | ] 62 | } 63 | ], 64 | "types": { 65 | "contributor": { 66 | "symbol": "🚀", 67 | "description": "A custom contribution type.", 68 | "link": "[<%= symbol %>](<%= url %> \"<%= description %>\")," 69 | }, 70 | "developer": { 71 | "symbol": "🧑‍🚀", 72 | "description": "A custom contribution type.", 73 | "link": "[<%= symbol %>](<%= url %> \"<%= description %>\")," 74 | } 75 | }, 76 | "contributorsPerLine": 7, 77 | "projectName": "ThasfinBot", 78 | "projectOwner": "dev-house-community", 79 | "repoType": "github", 80 | "repoHost": "https://github.com", 81 | "skipCi": true 82 | } 83 | -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- 1 | USER="" 2 | TOKEN="" 3 | TWITCH_CLIENT_ID="" 4 | TWITCH_CLIENT_SECRET="" 5 | SESSION_SECRET="" 6 | CALLBACK_URL="" -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 15 | Descreva o seu PR aqui... 16 | 17 | --- 18 | Esse PR é referente a issue: https://github.com/dev-house-community/ThasfinBot/issues/#NUMEROISSUE# 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | node_modules/ 3 | package-lock.json 4 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | DEV HOUSE Discord. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # CONTRIBUINDO 2 | 3 | Aqui você irá encontrar algumas práticas recomendadas que devem ser levadas em consideração na hora de criar **Issues** e **Pull Requests**, e que pode aumentar as chances da sua alteração ser adicionada ao projeto. 4 | 5 | Você verá a importância de descrever de forma correta as alterações feitas, e como isso pode ajudar as pessoas que mantém o projeto e que revisam as **Issues** e **Pull Requests**. 6 | 7 | É muito importante e recomendado que você leia o **[CODE_OF_CONDUCT](CODE_OF_CONDUCT.md)** do projeto antes de qualquer coisa, lá está descrito o que não é permitido de maneira alguma dentro do alcance da comunidade. 8 | 9 | ## TÍTULO E DESCRIÇÃO 10 | 11 | Essa é somente uma de várias das práticas recomendadas que devem ser seguidas para criar **Issues** e **Pull Requests** de forma correta, e que pode aumentar as chances de sua modificação ser adicionada ao projeto, além de seguir as recomendações propostas para fazer contribuições. 12 | 13 | Os títulos e descrições precisam ser coerentes e descrever o que realmente está acontecendo. O título deve descrever a intenção da modificação, enquanto a descrição deve conter detalhadamente o que foi mudado e a pretensão, isso ajuda com que os revisores possam ter uma visão mais ampla do que você pretende fazer, antes de revisar o código. 14 | 15 | ## COMMITS SEMÂNTICOS 16 | 17 | Fazer commits de forma correta também é muito importante em todos os projetos, seja em contribuições **Open Souce**, projetos privados, ou até mesmo em projetos pessoais, é muito importante ter mensagens de commits que descrevam realmente o que a alteração no código está fazendo. 18 | 19 | Em contribuições **Open Souce** uma boa maneira de revisar um **PR** é vendo quais arquivos foram modificados, mas isso muda a partir do momento em que vários arquivos estão sendo modificados, entrando em contextos diferentes, nesses casos navegar através de commits se torna uma melhor opção, podendo analisar o código por contextos de modificação, fica mais compreensível ver o que está acontecendo no código, por isso é muito importante sempre escrever as mensagens de commits de forma clara e legível, além é claro de adicionar somente os arquivos que façam parte do contexto da modificação no commit. 20 | 21 | Caso você precise alterar uma mensagem de commit, o Github mostra como fazer isso na documentação no seguinte link. [Alterando mensagens de commits][changing-a-commit-message]. 22 | 23 | Depois dessa breve descrição sobre a importância de ter commits declarativos, podemos entrar no caso em que se possa fazer uma modificação no projeto em grupos ou times, e dar o crédito para todos é muito importante, e para isso você pode adicionar Co-autores aos commits, o Github mostra com o fazer isso. Adicionado Co-autores aos commits - [Criando commits com múltiplos autores][commit-with-multiple-authors]. 24 | 25 | Se preferir, Aqui está uma sugestão de **Code/drops** produzido no canal da **[Rocketseat][rocketseat-youtube]** que pode ser útil na hora de fazer commits no projeto. [Padronizando seus commits][code-drops-video]. 26 | 27 | [changing-a-commit-message]: https://docs.github.com/pt/github/committing-changes-to-your-project/changing-a-commit-message 28 | [commit-with-multiple-authors]: https://docs.github.com/pt/github/committing-changes-to-your-project/creating-a-commit-with-multiple-authors 29 | [code-drops-video]: https://www.youtube.com/watch?v=erInHkjxkL8 30 | [rocketseat-youtube]: https://www.youtube.com/channel/UCSfwM5u0Kce6Cce8_S72olg 31 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 |
2 |

Licença Pública Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional

3 |

Ao exercer os Direitos Licenciados (definidos abaixo), Você aceita e concorda estar sujeito aos termos e condições desta Licença Pública Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional ("Licença Pública"). Na medida em que esta Licença Pública possa ser interpretada como um contrato, Você recebe os Direitos Licenciados em contrapartida pela Sua aceitação destes termos e condições, e o Licenciante concede-Lhe tais direitos em contrapartida pelos benefícios que o Licenciante recebe por disponibilizar o Material Licenciado sob estes termos e condições.

4 |

Cláusula 1 – Definições.

5 |
    6 |
  1. Material Adaptado significa material sujeito a Direito de Autor e Direitos Similares que é derivado de ou baseado no Material Licenciado e no qual o Material Licenciado é traduzido, alterado, arranjado, transformado, ou de outra forma modificado de uma maneira que requeira permissão com base no Direito de Autor e Direitos Similares detidos pelo Licenciante. Para os fins desta Licença Pública, quando o Material Licenciado seja uma obra musical, performance, ou fonograma, é sempre produzido Material Adaptado quando o Material Licenciado é sincronizado em relação temporal com uma imagem em movimento.
  2. 7 |
  3. Licença do Adaptador significa a licença que Você aplica ao Seu Direito de Autor e Direitos Similares nas Suas contribuições ao Material Adaptado de acordo com os termos e condições desta Licença Pública.
  4. 8 |
  5. Licença Compatível com a BY-SA significa uma licença listada em creativecommons.org/compatiblelicenses, aprovada pela Creative Commons como sendo essencialmente equivalente a esta Licença Pública.
  6. 9 |
  7. Direito de Autor e Direitos Similares significa direito de autor e/ou direitos similares estreitamente relacionados com o direito de autor, incluindo, mas não se limitando a, direitos de execução, radiodifusão, fixação de sons, e Direitos Sui Generis sobre Bases de Dados, independentemente de como sejam classificados ou categorizados. Para os fins desta Licença Pública, os direitos especificados na Cláusula 2(b)(1)-(2) não são Direito de Autor e Direitos Similares.
  8. 10 |
  9. Medidas Eficazes de Caráter Tecnológico significam aquelas medidas que, na ausência de direito para tanto, não podem ser contornadas em jurisdições cumprindo obrigações sob o Artigo 11 do Tratado da OMPI de Direito de Autor adotado em 20 de dezembro de 1996, e/ou acordos internacionais similares.
  10. 11 |
  11. Exceções e Limitações significam utilização justa (“fair use”), tratamento justo (“fair dealing”), e/ou qualquer outra exceção ou limitação ao Direito de Autor e Direitos Similares que se aplique à Sua utilização do Material Licenciado.
  12. 12 |
  13. Elementos da Licença significam os atributos da licença listados no nome de uma Licença Pública Creative Commons. Os Elementos da Licença desta Licença Pública são Atribuição e CompartilhaIgual.
  14. 13 |
  15. Material Licenciado significa o trabalho artístico ou literário, base de dados, ou outro material ao qual o Licenciante aplicou esta Licença Pública.
  16. 14 |
  17. Direitos Licenciados significam os direitos concedidos a Você sujeitos aos termos e condições desta Licença Pública, que são limitados a todos os Direitos de Autor e Direitos Similares que se apliquem à Sua utilização do Material Licenciado e que o Licenciante tem o direito de licenciar.
  18. 15 |
  19. Licenciante significa o(s) indivíduo(s) ou entidade(s) concedendo direitos sob esta Licença Pública.
  20. 16 |
  21. Compartilhar significa fornecer material ao público por qualquer meio ou processo que requeira permissão sob os Direitos Licenciados, como reprodução, exibição pública, execução pública, distribuição, disseminação, comunicação ou importação, e disponibilizar material ao público, incluindo por vias pelas quais os membros do público possam ter acesso ao material a partir de um local e no momento individualmente escolhidos por eles.
  22. 17 |
  23. Direitos Sui Generis sobre Bases de Dados significam outros direitos, que não o direito de autor e direitos conexos, resultantes da Diretiva 96/9/EC do Parlamento Europeu e do Conselho de 11 de Março de 1996 sobre a proteção legal de bases de dados, conforme emendada e/ou sucedida, bem como outros direitos essencialmente equivalentes em qualquer lugar do mundo.
  24. 18 |
  25. Você significa o indivíduo ou entidade que exerce os Direitos Licenciados sob esta Licença Pública. Lhe, Seu, Sua e Suas têm um significado correspondente.
  26. 19 |
20 |

Cláusula 2 – Âmbito.

21 |
    22 |
  1. Concessão da licença. 23 |
      24 |
    1. De acordo com os termos e condições desta Licença Pública, o Licenciante concede-Lhe, pelo presente, uma licença mundial, isenta de royalties, não sublicenciável, não exclusiva, e irrevogável para exercer os Direitos Licenciados sobre o Material Licenciado para: 25 |
        26 |
      1. reproduzir e Compartilhar o Material Licenciado, no todo ou em parte; e
      2. 27 |
      3. produzir, reproduzir, e Compartilhar Material Adaptado.
      4. 28 |
      29 |
    2. Exceções e Limitações. Para evitar dúvidas, quando Exceções e Limitações sejam aplicáveis à Sua utilização, esta Licença Pública não se aplica, e Você não precisa de cumprir com os seus termos e condições.
    3. 30 |
    4. Termo. O termo desta Licença Pública está especificado na Cláusula 6(a).
    5. 31 |
    6. Meios/suportes e formatos; modificações técnicas permitidas. O Licenciante autoriza Você a exercer os Direitos Licenciados em todos os meios/suportes e formatos conhecidos agora ou criados posteriormente, e a fazer as modificações técnicas necessárias para tanto. O Licenciante cede e/ou concorda em não reivindicar nenhum direito que proíba Você de fazer modificações técnicas necessárias ao exercício dos Direitos Licenciados, incluindo modificações técnicas necessárias para contornar Medidas Eficazes de Caráter Tecnológico. Para os fins desta Licença Pública, fazer simplesmente modificações autorizadas por esta Cláusula 2(a)(4) nunca produz Material Adaptado.
    7. 32 |
    8. Receptores subsequentes. 33 |
      34 |
        35 |
      1. Oferta pelo Licenciante – Material Licenciado. Cada receptor do Material Licenciado recebe automaticamente uma oferta do Licenciante para exercer os Direitos Licenciados sob os termos e condições desta Licença Pública.
      2. 36 |
      3. Oferta adicional pelo Licenciante – Material Adaptado. Cada receptor do Material Adaptado por Você recebe automaticamente uma oferta do Licenciante para exercer os Direitos Licenciados no Material Adaptado sob as condições da Licença do Adaptador que Você aplicar.
      4. 37 |
      5. Sem restrições subsequentes. Você não pode propor ou impor quaisquer termos ou condições, adicionais ou diferentes, ou aplicar quaisquer Medidas Eficazes de Caráter Tecnológico, sobre o Material Licenciado, se tal restringir o exercício dos Direitos Licenciados por qualquer receptor do Material Licenciado.
      6. 38 |
      39 |
      40 |
    9. Sem endosso. Nada nesta Licença Pública constitui ou pode ser entendido como uma permissão para afirmar ou sugerir que Você, ou que a Sua utilização do Material Licenciado, é conectado ao, patrocinado ou endossado pelo, ou tem status oficial concedido pelo, Licenciante ou terceiros designados para receber atribuição como previsto na Cláusula 3(a)(1)(A)(i).
    10. 41 |
    42 |
  2. Outros direitos.

    43 |
      44 |
    1. Direitos morais, como o direito à integridade, não são licenciados por esta Licença Pública, nem o são os direitos de imagem, privacidade, e/ou outros direitos de personalidade similares; contudo, na medida do possível, o Licenciante renuncia e/ou concorda não exercer quaisquer desses direitos detidos pelo Licenciante, na medida necessária para permitir que Você exerça os Direitos Licenciados, mas não de outra forma.
    2. 45 |
    3. Direitos de patente e marcas não se encontram licenciados sob esta Licença Pública.
    4. 46 |
    5. Na medida do possível, o Licenciante renuncia a qualquer direito de cobrar-Lhe royalties pelo exercício dos Direitos Licenciados, quer diretamente quer por meio de uma entidade de gestão coletiva, sob qualquer regime de licenciamento voluntário ou legal, disponível ou compulsório. Em todos os outros casos, o Licenciante reserva expressamente o direito de arrecadar tais royalties.
    6. 47 |
    48 |
  3. 49 |
50 |

Cláusula 3 – Condições da Licença.

51 |

O Seu exercício dos Direitos Licenciados fica expressamente sujeito às condições seguintes.

52 |
    53 |
  1. Atribuição.

    54 |
      55 |
    1. Se Você Compartilhar o Material Licenciado (incluindo sob uma forma modificada), Você deve:

      56 |
        57 |
      1. manter o seguinte, se for fornecido pelo Licenciante com o Material Licenciado: 58 |
          59 |
        1. identificação do(s) criador(es) do Material Licenciado e quaisquer outros designados para receber atribuição, de qualquer forma razoável solicitada pelo Licenciante (incluindo por pseudónimo, se designado);
        2. 60 |
        3. um aviso de direito de autor e direitos conexos;
        4. 61 |
        5. um aviso que se refere a esta Licença Pública;
        6. 62 |
        7. um aviso que se refere à exclusão de garantias;
        8. 63 |
        9. um URI ou um hyperlink para o Material Licenciado na medida razoavelmente exequível;
        10. 64 |
        65 |
      2. indicar se Você modificou o Material Licenciado e manter uma indicação de quaisquer modificações prévias; e
      3. 66 |
      4. indicar que o Material Licenciado é licenciado com esta Licença Pública, e incluir o texto de, ou o URI ou o hyperlink para, esta Licença Pública.
      5. 67 |
      68 |
    2. 69 |
    3. Você pode satisfazer as condições da Cláusula 3(a)(1) de qualquer forma razoável, tendo em conta o suporte, os meios e o contexto no qual Você Compartilhar o Material Licenciado. Por exemplo, pode ser razoável satisfazer as condições por via do fornecimento de um URI ou de um hyperlink para um recurso que inclui a informação exigida.
    4. 70 |
    5. Se solicitado pelo Licenciante, Você deve remover qualquer parte da informação exigida pela Cláusula 3(a)(1)(A) na medida razoavelmente exequível.
    6. 71 |
    72 |
  2. 73 |
  3. CompartilhaIgual. 74 |

    Para além das condições da Cláusula 3(a), se Você Compartilhar Material Adaptado que Você produzir, as condições seguintes também se aplicam.

    75 |
      76 |
    1. A Licença do Adaptador que Você aplicar deve ser uma licença Creative Commons com os mesmos Elementos da Licença, esta versão ou uma posterior, ou uma Licença Compatível com a BY-SA.
    2. 77 |
    3. Você deve incluir o texto da, ou o URI ou o hyperlink para, a Licença do Adaptador que Você aplicar. Você pode satisfazer esta condição de qualquer forma razoável, tendo em conta o suporte, os meios e o contexto no qual Você Compartilhar o Material Adaptado.
    4. 78 |
    5. Você não pode propor ou impor quaisquer termos ou condições adicionais ou diferentes, ou aplicar quaisquer Medidas Eficazes de Caráter Tecnológico, sobre o Material Adaptado que restrinjam o exercício dos direitos concedidos sob a Licença do Adaptador que Você aplicar.
    6. 79 |
    80 |
  4. 81 |
82 |

Cláusula 4 – Direitos Sui Generis sobre Bases de Dados.

83 |

Quando os Direitos Licenciados incluam Direitos Sui Generis sobre Bases de Dados que se apliquem à Sua utilização do Material Licenciado:

84 |
    85 |
  1. para evitar dúvidas, a Cláusula 2(a)(1) concede-Lhe o direito de extrair, reutilizar, reproduzir e Compartilhar a totalidade ou uma parte substancial dos conteúdos da base de dados;
  2. 86 |
  3. se Você incluir a totalidade ou uma parte substancial dos conteúdos da base de dados numa base de dados em relação à qual Você tenha Direitos Sui Generis sobre Bases de Dados, então a base de dados em relação à qual Você tenha Direitos Sui Generis sobre Bases de Dados (mas não os seus conteúdos individuais) é Material Adaptado, incluindo para os fins da Cláusula 3(b); e
  4. 87 |
  5. Você deve cumprir com as condições da Cláusula 3(a) se Você Compartilhar a totalidade ou uma parte substancial dos conteúdos da base de dados.
  6. 88 |
89 | Para evitar dúvidas, esta Cláusula 4 suplementa e não substitui as Suas obrigações sob esta Licença Pública, quando os Direitos Licenciados incluam outro Direito de Autor e Direitos Similares. 90 |

Cláusula 5 – Exclusão de Garantias e Limitação de Responsabilidade.

91 |
    92 |
  1. Salvo se o Licenciante fizer separadamente uma assunção em sentido contrário, na medida do possível, o Licenciante disponibiliza o Material Licenciado “no estado em que se encontra” (“as-is”) e “como disponível” (“as-available”), e não faz representações ou presta garantias de qualquer tipo relativamente ao Material Licenciado, quer sejam expressas, implícitas, legais ou outras. Isto inclui, mas não se limita a, garantias quanto à titularidade de direitos, potencial de comercialização, adequação a um fim específico, não violação de direitos, ausência de defeitos latentes ou outros defeitos, exatidão, ou existência ou ausência de erros, quer sejam ou não conhecidos ou detetáveis. Quando as exclusões de garantias não sejam permitidas, na íntegra ou em parte, esta exclusão poderá não aplicar-se a Você.
  2. 93 |
  3. Na medida do possível, em nenhum caso será o Licenciante responsável para com Você, com base em nenhum argumento jurídico (incluindo, mas não se limitando a, negligência) ou a outro título, por quaisquer perdas, custos, despesas ou danos, diretos, especiais, indiretos, incidentais, consequenciais, punitivos, exemplares ou outros, resultantes desta Licença Pública ou da utilização do Material Licenciado, ainda que o Licenciante tenha sido advertido da possibilidade dessas perdas, custos, despesas ou danos. Quando a limitação de responsabilidade não seja permitida, na íntegra ou em parte, esta limitação poderá não aplicar-se a Você.
  4. 94 |
95 |
    96 |
  1. A exclusão de garantias e a limitação de responsabilidade acima previstas devem ser interpretadas de uma forma que, na medida do possível, mais se aproxime de uma absoluta exclusão de, e renúncia a, toda e qualquer responsabilidade.
  2. 97 |
98 |

Cláusula 6 – Termo e Cessação.

99 |
    100 |
  1. Esta Licença Pública aplica-se durante o termo do Direito de Autor e Direitos Similares aqui licenciados. No entanto, se Você não cumprir com esta Licença Pública, então os Seus direitos sob esta Licença Pública cessarão automaticamente.
  2. 101 |
  3. 102 |

    Quando o Seu direito de utilizar o Material Licenciado tenha cessado nos termos da Cláusula 6(a), será restabelecido:

    103 |
      104 |
    1. automaticamente a partir da data em que a violação seja sanada, desde que seja sanada dentro de 30 dias a contar da Sua descoberta da violação; ou
    2. 105 |
    3. com o expresso restabelecimento pelo Licenciante.
    4. 106 |
    107 | Para evitar dúvidas, esta Cláusula 6(b) não afeta qualquer direito que o Licenciante possa ter de obter reparação e medidas legais cabíveis pelas Suas violações desta Licença Pública.
  4. 108 |
  5. Para evitar dúvidas, o Licenciante também poderá disponibilizar o Material Licenciado sob termos ou condições separados ou parar a distribuição do Material Licenciado a qualquer momento; no entanto, tal não cessará esta Licença Pública.
  6. 109 |
  7. As Cláusulas 1, 5, 6, 7, e 8 continuarão em vigor após a cessação desta Licença Pública.
  8. 110 |
111 |

Cláusula 7 – Outros Termos e Condições.

112 |
    113 |
  1. O Licenciante não estará vinculado a quaisquer termos ou condições, adicionais ou diferentes, comunicados por Você, salvo se expressamente acordado.
  2. 114 |
  3. Quaisquer pactos, entendimentos ou acordos relativamente ao Material Licenciado não indicados aqui são separados e independentes dos termos e condições desta Licença Pública.
  4. 115 |
116 |

Cláusula 8 – Interpretação.

117 |
    118 |
  1. Para evitar dúvidas, esta Licença Pública não reduz, limita, restringe ou impõe condições sobre qualquer utilização do Material Licenciado que possa ser legalmente feita sem a permissão concedida por esta Licença Pública, e não deve ser interpretada nesse sentido.
  2. 119 |
  3. Na medida do possível, se alguma disposição desta Licença Pública for considerada inexequível, será automaticamente reformada na medida estritamente necessária para que se torne exequível. Se a disposição não puder ser alterada, deverá ser removida desta Licença Pública sem afetar a exequibilidade dos restantes termos e condições.
  4. 120 |
  5. Nenhum termo ou condição desta Licença Pública será renunciado e nenhuma falha no seu cumprimento consentida, salvo se tal for expressamente acordado pelo Licenciante.
  6. 121 |
  7. Nada nesta Licença Pública constitui ou pode ser interpretado como uma limitação de, ou renúncia a, quaisquer privilégios e imunidades aplicáveis ao Licenciante ou a Você, incluindo os resultantes dos processos legais de qualquer jurisdição ou autoridade.
  8. 122 |
123 | 124 |

A Creative Commons não é parte das suas licenças públicas. Não obstante, a Creative Commons pode eleger aplicar uma das suas licenças públicas a material por si publicado e, nesses casos, será considerada um “Licenciante". O texto das licenças públicas Creative Commons é dedicado ao domínio público sob a CC0 Dedicação ao Domínio Público. Exceto para o fim limitado de indicar que o material é compartilhado sob uma licença pública Creative Commons ou de outra forma permitida pelas politicas da Creative Commons publicadas em creativecommons.org/policies, a Creative Commons não autoriza a utilização da marca "Creative Commons" ou de qualquer outra marca ou logo da Creative Commons sem o seu prévio consentimento escrito, incluindo, mas não se limitando a, em conexão com qualquer modificação não autorizada de qualquer uma das suas licenças públicas ou de quaisquer outros pactos, entendimentos ou acordos relativos à utilização de material licenciado. Para evitar dúvidas, este parágrafo não faz parte das licenças públicas.

Para comunicar com a Creative Commons, visite creativecommons.org.

125 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | worker: npm start 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ThasfinBot 2 | 3 |
4 | ThasfinBot 5 |
6 | 7 |

8 | PR Open 9 | Licence MIT 10 |

11 | 12 |
13 | Twitch live 14 |
15 | 16 |
17 |
18 | 19 |

20 | Se preferir você pode navegar por tópicos.
21 | Introdução • 22 | Tecnologias • 23 | Comandos 24 |

25 | 26 | ## 🎉 Introdução 27 | 28 | A **ThasfinBot** é um bot criado para iteragir com o pessoal durante a live na Twitch, ajudando com links e informações. 29 | 30 | ## 🙆‍♀️ Como surgi 31 | 32 | O nome **Thasfin** surgiu da primeira vez em que a @jakeliny jogou RPG, e precisou de um nickname, foi ai que o nome **Thasfin Aedin** nasceu, e que foi usando por algum tempo como nickname pela jake e que agora virou um bot em que todos podem interagir. 33 | 34 | ## 🚀 Tecnologias 35 | 36 | A ThasfinBot foi criada usando **Node.js** e **Javascript**. 37 | 38 | ## 📚 Comandos 39 | 40 | Você pode se divertir e usar alguns comandos em live, usando o **prefixo:** `!`. 41 | 42 | ``` 43 | !discord 44 | 45 | !discover 46 | 47 | !help 48 | 49 | !githouse 50 | 51 | !social 52 | 53 | !setup 54 | ``` 55 | ## Para rodar localmente: 56 | - Duplique o arquivo .env.development com o nome .env 57 | - Gere um token com a sua conta da Twitch no (site do TMI.JS)[https://twitchapps.com/tmi/] 58 | - No arquivo .env coloque seu username da twitch no campo USER e o token gerado no campo TOKEN 59 | - No arquivo config na propriedade channels coloque o username das twitchs que o bot irá ler os comandos 60 | 61 | 62 | 💜 Logo criada por [@geovanipfranca](https://github.com/geovanipfranca). 63 | 64 | ## Contributors ✨ 65 | 66 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 |

Lucas Jantsch Guedes

🚧

Jakeliny Gracielly

🚧

Jorge Alegretti

🚧

Diego Parente

🚧

Rafael Nobre

🚧

Geovani França

🚧
81 | 82 | 83 | 84 | 85 | 86 | 87 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! 88 | 89 | ## License 90 | 91 |

ThasfinBot by Jakeliny Gracielly is licensed under CC BY-SA 4.0

92 | -------------------------------------------------------------------------------- /build/app.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | var express_1 = __importDefault(require("express")); 7 | var tmi_js_1 = __importDefault(require("tmi.js")); 8 | var handleCommands_1 = __importDefault(require("./useCases/handleCommands/handleCommands")); 9 | var utils_1 = __importDefault(require("./utils/utils")); 10 | var config_1 = __importDefault(require("./config")); 11 | var app = express_1.default(); 12 | app.get('/', function (req, res) { return res.send('Thasfin Bot is running'); }); 13 | app.listen(3000, function () { 14 | console.log('⚡️Server is running'); 15 | }); 16 | var client = new tmi_js_1.default.client(config_1.default); 17 | client.on('message', handleCommands_1.default(client)); 18 | function handleConnected(address, port) { 19 | var _a; 20 | console.log("*** Bot conectado com sucesso no IRC (" + address + ":" + port + "} nos canais [" + config_1.default.channels + "]"); 21 | (_a = config_1.default.channels) === null || _a === void 0 ? void 0 : _a.forEach(function (channel) { 22 | //TODO refactor this 23 | utils_1.default.turnOnAutomaticMessages(channel, client); 24 | //TODO: So rodar o startup quando o streamer acaba de abrir live, se a live 25 | // está rolando a mais de 10 minutos ou algo assim esse startup não joga o texto no chat 26 | var startup = [ 27 | "/color yellowgreen", 28 | // "/me A Thasfin tá na área HeyGuys" 29 | ]; 30 | startup.forEach(function (msg) { return client.say(channel, msg); }); 31 | }); 32 | } 33 | client.on('connected', handleConnected); 34 | client.connect(); 35 | -------------------------------------------------------------------------------- /build/commands/open.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | var commands = [ 4 | { 5 | name: '!discord', 6 | message: 'Entra no discord da devHouse!! https://discord.gg/hUjdjevDCz', 7 | }, 8 | { 9 | name: '!maratona', 10 | message: 'Se inscreva na maratona discover https://maratonadiscover.rocketseat.com.br/inscricao', 11 | }, 12 | { 13 | name: '!discover', 14 | message: 'Se inscreva no discover https://app.rocketseat.com.br/discover', 15 | }, 16 | { 17 | name: '!githouse', 18 | message: 'Contribua no GitHub da DEVHOUSE https://github.com/dev-house-community', 19 | }, 20 | { 21 | name: '!devhouse', 22 | message: 'Inscreva no YouTube da DevHouse https://www.youtube.com/channel/UCFohroq6iQ6N4U4w3w4NXSw', 23 | }, 24 | { 25 | name: '!social', 26 | message: 'Siga as minhas redes https://www.instagram.com/jakeliny.gracielly/ e https://www.linkedin.com/in/jakelinygracielly/' 27 | }, 28 | { 29 | name: '!setup', 30 | message: 'Processador AMD Ryzen 5 3600X | Placa de vídeo RTX 2060 super | Placa Mãe B450M Steel Legend | Mem RAM 4x8gb 3200MHz XPG d41 | Fonte CX550W Corsair | Gabinete Gamdias Talos M1B | Teclado Mars Hyper X | Fone Zeus 2 Sakura Editon Redragon | Webcam logitech C925e' 31 | } 32 | ]; 33 | exports.default = commands; 34 | -------------------------------------------------------------------------------- /build/config.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | var config = { 4 | identity: { 5 | username: process.env.USER, 6 | password: process.env.TOKEN, 7 | }, 8 | channels: ['jakeliny', 'maykbrito', 'rahmaidev'] 9 | }; 10 | exports.default = config; 11 | -------------------------------------------------------------------------------- /build/database/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __generator = (this && this.__generator) || function (thisArg, body) { 12 | var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; 13 | return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; 14 | function verb(n) { return function (v) { return step([n, v]); }; } 15 | function step(op) { 16 | if (f) throw new TypeError("Generator is already executing."); 17 | while (_) try { 18 | if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; 19 | if (y = 0, t) op = [op[0] & 2, t.value]; 20 | switch (op[0]) { 21 | case 0: case 1: t = op; break; 22 | case 4: _.label++; return { value: op[1], done: false }; 23 | case 5: _.label++; y = op[1]; op = [0]; continue; 24 | case 7: op = _.ops.pop(); _.trys.pop(); continue; 25 | default: 26 | if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } 27 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } 28 | if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } 29 | if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } 30 | if (t[2]) _.ops.pop(); 31 | _.trys.pop(); continue; 32 | } 33 | op = body.call(thisArg, _); 34 | } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } 35 | if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; 36 | } 37 | }; 38 | var __importDefault = (this && this.__importDefault) || function (mod) { 39 | return (mod && mod.__esModule) ? mod : { "default": mod }; 40 | }; 41 | Object.defineProperty(exports, "__esModule", { value: true }); 42 | var path_1 = __importDefault(require("path")); 43 | var lowdb_1 = __importDefault(require("lowdb")); 44 | var FileSync = require('lowdb/adapters/FileSync'); 45 | /** 46 | * Este método inicia um database lowdb utilizando o arquivo db.json como memory 47 | * @returns {db} 48 | */ 49 | var database = function () { return __awaiter(void 0, void 0, void 0, function () { 50 | var adapter; 51 | return __generator(this, function (_a) { 52 | adapter = new FileSync(path_1.default.resolve('src', 'database', 'db.json')); 53 | return [2 /*return*/, lowdb_1.default(adapter)]; 54 | }); 55 | }); }; 56 | exports.default = database(); 57 | -------------------------------------------------------------------------------- /build/useCases/handleCommands/handleCommands.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __generator = (this && this.__generator) || function (thisArg, body) { 12 | var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; 13 | return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; 14 | function verb(n) { return function (v) { return step([n, v]); }; } 15 | function step(op) { 16 | if (f) throw new TypeError("Generator is already executing."); 17 | while (_) try { 18 | if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; 19 | if (y = 0, t) op = [op[0] & 2, t.value]; 20 | switch (op[0]) { 21 | case 0: case 1: t = op; break; 22 | case 4: _.label++; return { value: op[1], done: false }; 23 | case 5: _.label++; y = op[1]; op = [0]; continue; 24 | case 7: op = _.ops.pop(); _.trys.pop(); continue; 25 | default: 26 | if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } 27 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } 28 | if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } 29 | if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } 30 | if (t[2]) _.ops.pop(); 31 | _.trys.pop(); continue; 32 | } 33 | op = body.call(thisArg, _); 34 | } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } 35 | if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; 36 | } 37 | }; 38 | var __importDefault = (this && this.__importDefault) || function (mod) { 39 | return (mod && mod.__esModule) ? mod : { "default": mod }; 40 | }; 41 | Object.defineProperty(exports, "__esModule", { value: true }); 42 | var utils_1 = __importDefault(require("../../utils/utils")); 43 | var initTemplates_1 = __importDefault(require("../../useCases/initTemplates/initTemplates")); 44 | /** 45 | * Método que trata todos os recebimentos de mensagens e processa comandos 46 | * @param client Client IRC da Twitch conectado com canal 47 | * @returns {(function(*=, *, *, *=): Promise)|*} Função para tratamento das mensagens 48 | */ 49 | var handleCommands = function (client) { return function (target, context, receivedMessage, isBot) { return __awaiter(void 0, void 0, void 0, function () { 50 | var req, action, template, ignored, commands, cmd, args, partials, rendered, parsed, shouldPrintMessages, e_1; 51 | return __generator(this, function (_a) { 52 | switch (_a.label) { 53 | case 0: 54 | // Varifica se quem mandou a mensagem não é bot e se a mensagem é um comando aceitável 55 | if (isBot) 56 | return [2 /*return*/]; 57 | if (!receivedMessage.startsWith("!")) 58 | return [2 /*return*/]; 59 | req = receivedMessage.split(" "); 60 | action = req.shift(); 61 | return [4 /*yield*/, initTemplates_1.default(target)]; 62 | case 1: 63 | template = _a.sent(); 64 | ignored = template["ignored-commands"]; 65 | commands = template["commands"]; 66 | cmd = utils_1.default.findCommandByAction(action, ignored, commands); 67 | args = { 68 | twitch: { target: target, context: context, receivedMessage: receivedMessage, isBot: isBot }, 69 | context: { cmd: cmd, action: action, req: req, commands: commands, ignored: ignored, template: template } 70 | }; 71 | _a.label = 2; 72 | case 2: 73 | _a.trys.push([2, 5, , 6]); 74 | return [4 /*yield*/, require('../../useCommands')(args)]; 75 | case 3: 76 | partials = _a.sent(); 77 | return [4 /*yield*/, JSON.stringify(cmd.command)]; 78 | case 4: 79 | rendered = _a.sent(); 80 | parsed = utils_1.default.getSanitizedRender(args, rendered); 81 | shouldPrintMessages = parsed != null && Array.isArray(parsed.messages); 82 | if (shouldPrintMessages) 83 | parsed["messages"].forEach(function (message) { return client.say(target, message); }); 84 | return [3 /*break*/, 6]; 85 | case 5: 86 | e_1 = _a.sent(); 87 | utils_1.default.sendErrorCommand(client, args, e_1); 88 | return [3 /*break*/, 6]; 89 | case 6: return [2 /*return*/]; 90 | } 91 | }); 92 | }); }; }; 93 | exports.default = handleCommands; 94 | -------------------------------------------------------------------------------- /build/useCases/initTemplates/initTemplates.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __generator = (this && this.__generator) || function (thisArg, body) { 12 | var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; 13 | return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; 14 | function verb(n) { return function (v) { return step([n, v]); }; } 15 | function step(op) { 16 | if (f) throw new TypeError("Generator is already executing."); 17 | while (_) try { 18 | if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; 19 | if (y = 0, t) op = [op[0] & 2, t.value]; 20 | switch (op[0]) { 21 | case 0: case 1: t = op; break; 22 | case 4: _.label++; return { value: op[1], done: false }; 23 | case 5: _.label++; y = op[1]; op = [0]; continue; 24 | case 7: op = _.ops.pop(); _.trys.pop(); continue; 25 | default: 26 | if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } 27 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } 28 | if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } 29 | if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } 30 | if (t[2]) _.ops.pop(); 31 | _.trys.pop(); continue; 32 | } 33 | op = body.call(thisArg, _); 34 | } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } 35 | if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; 36 | } 37 | }; 38 | var __importDefault = (this && this.__importDefault) || function (mod) { 39 | return (mod && mod.__esModule) ? mod : { "default": mod }; 40 | }; 41 | Object.defineProperty(exports, "__esModule", { value: true }); 42 | //TODO ver o que é LRU 43 | var lru_cache_1 = __importDefault(require("lru-cache")); 44 | var path_1 = __importDefault(require("path")); 45 | var fs_1 = require("fs"); 46 | var cache = new lru_cache_1.default({ maxAge: 500 * 60 * 60 }); 47 | /** 48 | * Método que retorna o template a ser utilizado pelo canal pegando o valor ou cache (cache de 1/2h) 49 | * @param channel canal de onde o bot será executado 50 | * @returns {object|null} Retorna o template do canal ou o template padrão do bot 51 | */ 52 | var getTemplateChannel = function (channel) { 53 | if (channel === void 0) { channel = ""; } 54 | return __awaiter(void 0, void 0, void 0, function () { 55 | var owner, _a, channelCommands, commonCommands; 56 | return __generator(this, function (_b) { 57 | switch (_b.label) { 58 | case 0: 59 | owner = channel.substring(1, channel.length); 60 | // Retorna o valor em cache do template caso já tenha sido salvo anteriormente 61 | if (cache.get("commands." + owner + ".json")) { 62 | return [2 /*return*/, JSON.parse("" + cache.get("commands." + owner + ".json"))]; 63 | } 64 | return [4 /*yield*/, Promise.all([ 65 | // promises.readFile(path.resolve(`commands.${owner}.json`), "UTF-8").catch(() => null), 66 | // promises.readFile(path.resolve(`commands.json`), "UTF-8").catch(() => null), 67 | fs_1.promises.readFile(path_1.default.resolve("commands." + owner + ".json")).catch(function () { return null; }), 68 | fs_1.promises.readFile(path_1.default.resolve("commands.json")).catch(function () { return null; }), 69 | ]) 70 | //TODO ver esse buffer do channelCommands 71 | // Se encontrar o template do canal, prioriza o retorno e salva no cache 72 | ]; 73 | case 1: 74 | _a = _b.sent(), channelCommands = _a[0], commonCommands = _a[1]; 75 | //TODO ver esse buffer do channelCommands 76 | // Se encontrar o template do canal, prioriza o retorno e salva no cache 77 | if (channelCommands != null) { 78 | cache.set("commands." + owner + ".json", channelCommands); 79 | return [2 /*return*/, JSON.parse("" + channelCommands)]; 80 | } 81 | // Salva para esse canal o template de comandos default e retorna ele 82 | cache.set("commands." + owner + ".json", commonCommands); 83 | return [2 /*return*/, JSON.parse("" + commonCommands)]; 84 | } 85 | }); 86 | }); 87 | }; 88 | exports.default = getTemplateChannel; 89 | -------------------------------------------------------------------------------- /build/useCases/openCommands.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | var open_1 = __importDefault(require("../commands/open")); 7 | var openCommands = function (client) { return function (target, context, receivedMessage, isBot) { 8 | var message = receivedMessage.trim(); 9 | if (isBot) 10 | return; 11 | if (message === '!help') { 12 | var sendMessage = open_1.default.reduce(function (acc, command) { 13 | return acc + (" " + command.name); 14 | }, ''); 15 | return client.say(target, sendMessage + " "); 16 | } 17 | var command = open_1.default.find(function (command) { return command.name === message; }); 18 | if (!command) 19 | return 'Error'; 20 | return client.say(target, "@" + context.username + " " + command.message); 21 | }; }; 22 | exports.default = openCommands; 23 | -------------------------------------------------------------------------------- /build/useCommands/count/count.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __generator = (this && this.__generator) || function (thisArg, body) { 12 | var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; 13 | return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; 14 | function verb(n) { return function (v) { return step([n, v]); }; } 15 | function step(op) { 16 | if (f) throw new TypeError("Generator is already executing."); 17 | while (_) try { 18 | if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; 19 | if (y = 0, t) op = [op[0] & 2, t.value]; 20 | switch (op[0]) { 21 | case 0: case 1: t = op; break; 22 | case 4: _.label++; return { value: op[1], done: false }; 23 | case 5: _.label++; y = op[1]; op = [0]; continue; 24 | case 7: op = _.ops.pop(); _.trys.pop(); continue; 25 | default: 26 | if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } 27 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } 28 | if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } 29 | if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } 30 | if (t[2]) _.ops.pop(); 31 | _.trys.pop(); continue; 32 | } 33 | op = body.call(thisArg, _); 34 | } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } 35 | if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; 36 | } 37 | }; 38 | var database = require("../../database"); 39 | var MAX_INCREMENT = 10; 40 | /** 41 | * Esta método pega a requisição e retorna um valor entre 1 e MAX_INCREMENT parseado da requisição 42 | * @param req Array com a requisição do comando, o que foi digitado após ele separado por espaços 43 | * @returns {number} Entre 0 e MAX_INCREMENT 44 | */ 45 | var getIncrementValueReq = function (req) { 46 | if (!req.length) 47 | return 1; 48 | var increment = parseInt(req) || 1; 49 | if (increment < MAX_INCREMENT) 50 | return increment; 51 | return 1; 52 | }; 53 | /** 54 | * Este método atualiza o db e retorna o dado salvo do count 55 | * @param key Chave salva no db para o count 56 | * @param value Valor para ser incrementado 57 | * @returns {number} Valor salvo no db 58 | */ 59 | var getIncrementValueDatabase = function (key, value) { return __awaiter(void 0, void 0, void 0, function () { 60 | var saved; 61 | return __generator(this, function (_a) { 62 | switch (_a.label) { 63 | case 0: return [4 /*yield*/, database.get("counts").find({ key: key }).value()]; 64 | case 1: 65 | saved = (_a.sent()) || { key: key, value: 0 }; 66 | // Incrementa o valor em database/inicial com o valor de incremento 67 | saved.value += value; 68 | // Remove o estado atual (JSON Db - se salvar de novo sem apagar cria outro objeto) 69 | return [4 /*yield*/, database.get('counts').remove({ key: key }).write()]; 70 | case 2: 71 | // Remove o estado atual (JSON Db - se salvar de novo sem apagar cria outro objeto) 72 | _a.sent(); 73 | // Salva no banco o novo valor de counts e retorna esse valor 74 | return [4 /*yield*/, database.get("counts").push(saved).write()]; 75 | case 3: 76 | // Salva no banco o novo valor de counts e retorna esse valor 77 | _a.sent(); 78 | return [2 /*return*/, saved.value]; 79 | } 80 | }); 81 | }); }; 82 | /** 83 | * Método que ao ser executado pega o id da action e dá um count de quantas vezes foi chamado, retornando o valor 84 | * @param command Comando solicitado para count 85 | * @param req Requisição do comando, caso venha vazio nada foi passado após o comando (é um array) 86 | * @returns {number} Valor salvo no db 87 | */ 88 | module.exports = function (_a) { 89 | var _b = _a.context, command = _b.cmd.command, req = _b.req; 90 | return __awaiter(void 0, void 0, void 0, function () { 91 | var action, key_db, increment, count; 92 | return __generator(this, function (_c) { 93 | switch (_c.label) { 94 | case 0: 95 | action = command.actions[0]; 96 | key_db = "count_" + action; 97 | increment = getIncrementValueReq(req); 98 | return [4 /*yield*/, getIncrementValueDatabase(key_db, increment)]; 99 | case 1: 100 | count = _c.sent(); 101 | // Retorna o valor atual incrementado no banco 102 | return [2 /*return*/, count]; 103 | } 104 | }); 105 | }); 106 | }; 107 | -------------------------------------------------------------------------------- /build/useCommands/help/help.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | var utils_1 = __importDefault(require("../../utils/utils")); 7 | /** 8 | * Método faz o join da mensagem pelo prefixo do break 9 | * @param messages Array de mensagens que será feito o join 10 | * @returns {string} String unica com todas as posições do array separados por ~break~ 11 | */ 12 | var JoinMessagesToSanitizeResponse = function (messages) { 13 | return messages.join("~break~"); 14 | }; 15 | /** 16 | * Método que busca o help de um comando, sendo ele restrito ou não, caso o comando não exista ele dá uma mensagem amigável 17 | * @param username Usuário do @ quem pediu ajuda 18 | * @param req Requisição solicitada após o ajuda, caso seja um Array Vazio o comando solicitado a ajuda é o próprio 19 | * @param commands Comandos cadastrados para o canal 20 | * @param ignored Lista de comandos ignorados para o canal 21 | * @returns {string} Retorna uma array de string com o join de ~break~ para saber quando tem que separar (Mustache não gera um array ele coloca erroneamente um array dentro da string) 22 | */ 23 | var help = function (_a) { 24 | var username = _a.twitch.context.username, _b = _a.context, req = _b.req, commands = _b.commands, ignored = _b.ignored; 25 | var action = "help"; 26 | if (req.length != 0) { 27 | action = req.shift(); 28 | } 29 | var _c = utils_1.default.findCommandByAction(action, ignored, commands), find = _c.find, reserved = _c.reserved, command = _c.command; 30 | var isReservedCommandSearch = reserved == true; 31 | var isAnUnknowCommandOrWithoutHelp = find == false || find == true && typeof (command === null || command === void 0 ? void 0 : command.help) != "string"; 32 | if (isReservedCommandSearch) { 33 | return JoinMessagesToSanitizeResponse(["/color HotPink", "/me @" + username + " Esse comando " + action + " \u00E9 reservado (S\u00F3 tenho permiss\u00E3o para falar isso) Kappa"]); 34 | } 35 | if (isAnUnknowCommandOrWithoutHelp) { 36 | return JoinMessagesToSanitizeResponse(["/color HotPink", "/me @" + username + " Olha eu nem sei como te ajudar com o commando " + action + " se nem sei o que \u00E9 WutFace"]); 37 | } 38 | return JoinMessagesToSanitizeResponse(["/color YellowGreen", "/me Olha @" + username + " achei isso aqui, ajuda? " + command.help]); 39 | }; 40 | exports.default = help; 41 | -------------------------------------------------------------------------------- /build/useCommands/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | var count_1 = __importDefault(require("./count/count")); 7 | var help_1 = __importDefault(require("./help/help")); 8 | /** 9 | * Funlções que podem ser executadas pelo Mustache na hora de ler o commands (Executa a função e retorna valor ou não) 10 | * @param args todos os argumentos recolhidos durante a execução no comando 11 | * @returns {object} todos as funções disponíveis para serem utilizadas no template 12 | */ 13 | module.exports = function (args) { 14 | return { 15 | count: function () { return count_1.default(args); }, 16 | help: function () { return help_1.default(args); }, 17 | funcaoComandoConsole: function () { console.log("Oie"); } 18 | }; 19 | }; 20 | -------------------------------------------------------------------------------- /build/utils/findCommandByAction.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | /** 4 | * Esse método busca um comando removendo o ! dentro do arquivo commands.json 5 | * @param action Comando a ser buscado com ou sem o prefixo (!) 6 | * @param ignored Lista de comandos a serem ignorados pelo bot 7 | * @param commands Lista de comandos reconhecidos 8 | * @returns {{reserved: boolean, find: boolean, command: null}|{reserved: boolean, find: boolean, command}} 9 | */ 10 | var findCommandByAction = function (action, ignored, commands) { 11 | var _a; 12 | if (action.startsWith("!")) { 13 | action = action.substring(1, action.length); 14 | } 15 | if (ignored.includes(action)) 16 | return { find: true, reserved: true, command: null }; 17 | // Iteração para buscar o commando e retornar 18 | for (var index in commands) { 19 | if ((_a = commands[index].actions) === null || _a === void 0 ? void 0 : _a.includes(action)) { 20 | return { find: true, reserved: false, command: commands[index] }; 21 | } 22 | else { 23 | continue; 24 | } 25 | } 26 | return { find: false, reserved: false, command: null }; 27 | }; 28 | exports.default = findCommandByAction; 29 | -------------------------------------------------------------------------------- /build/utils/getSanitizedRender.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | /** 4 | * Este método sanitiza o retorno depois de ter passado pelo render (que chama as funções do useCommands, pois como o 5 | * comando ajuda para retornar um array dentro das mensagens é necessário salvar como string no render e pós render 6 | * quebrar as linhas novamente 7 | * @param find Verifica se o comando foi ou não encontrado 8 | * @param action O comando que foi solicitado 9 | * @param username O username de quem requisitou o comando 10 | * @param rendered O render do commands.json após os useCommands terem sidos acionados caso sejam necessários (json string) 11 | * @returns {{messages: string[]}|null|command} 12 | */ 13 | var getSanitizedRender = function (_a, rendered) { 14 | var _b = _a.context.cmd, find = _b.find, action = _b.action, username = _a.twitch.context; 15 | var parsed = JSON.parse(rendered); 16 | var isExistentCommand = find == true; 17 | if (!isExistentCommand) 18 | return { messages: ["/color Firebrick", "/me Ops @" + username + " acho que n\u00E3o existe esse comando " + action + " n\u00E3o hein Keepo"] }; 19 | else if (parsed == null) 20 | return null; 21 | // Função que caso a mensagem for string transforma ela em Array separando por \n 22 | var sanitizeMessagesFromStringToArray = function (_a) { 23 | var messages = _a.messages; 24 | if (typeof messages == "string") 25 | return messages.split("~break~"); 26 | return messages; 27 | }; 28 | parsed.messages = sanitizeMessagesFromStringToArray(parsed); 29 | return parsed; 30 | }; 31 | exports.default = getSanitizedRender; 32 | -------------------------------------------------------------------------------- /build/utils/sendErrorCommand.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | /** 4 | * Este método concentra os erros na hora de usar um comando, ou executar uma função do partials, ele manda uma 5 | * mensagem no chat alertando e registra no console o erro gerado com trace! 6 | * @param client Client conectado com o IRC da Twitch 7 | * @param target Canal para onde deve ser enviado a mensagem 8 | * @param username Username do usuário que enviou o comando 9 | * @param action Comando solicitado pelo @ 10 | * @param error Erro com trace do ocorrido e mensagem 11 | */ 12 | var sendErrorCommand = function (client, _a, error) { 13 | var _b = _a.twitch, target = _b.target, username = _b.context.username, action = _a.context.action; 14 | // Ternário que pega o usuário do dono do canal para utilizar na mensagem 15 | var owner = target.startsWith("#") ? target.substring(1, target.length) : target; 16 | var messages = [ 17 | "/color Coral", 18 | "/me Nossa @" + username + " voc\u00EA me quebrou legal com esse comando a\u00ED " + action + " .... @" + owner + " eu deixei um log pra te ajudar a resolver essa bucha a\u00ED se vira!" 19 | ]; 20 | messages.forEach(function (message) { return client.say(target, message); }); 21 | console.error(error); 22 | }; 23 | exports.default = sendErrorCommand; 24 | -------------------------------------------------------------------------------- /build/utils/turnOnAutomaticMessages.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __generator = (this && this.__generator) || function (thisArg, body) { 12 | var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; 13 | return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; 14 | function verb(n) { return function (v) { return step([n, v]); }; } 15 | function step(op) { 16 | if (f) throw new TypeError("Generator is already executing."); 17 | while (_) try { 18 | if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; 19 | if (y = 0, t) op = [op[0] & 2, t.value]; 20 | switch (op[0]) { 21 | case 0: case 1: t = op; break; 22 | case 4: _.label++; return { value: op[1], done: false }; 23 | case 5: _.label++; y = op[1]; op = [0]; continue; 24 | case 7: op = _.ops.pop(); _.trys.pop(); continue; 25 | default: 26 | if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } 27 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } 28 | if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } 29 | if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } 30 | if (t[2]) _.ops.pop(); 31 | _.trys.pop(); continue; 32 | } 33 | op = body.call(thisArg, _); 34 | } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } 35 | if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; 36 | } 37 | }; 38 | var __importDefault = (this && this.__importDefault) || function (mod) { 39 | return (mod && mod.__esModule) ? mod : { "default": mod }; 40 | }; 41 | Object.defineProperty(exports, "__esModule", { value: true }); 42 | var node_cron_1 = __importDefault(require("node-cron")); 43 | var initTemplates_1 = __importDefault(require("../useCases/initTemplates/initTemplates")); 44 | /** 45 | * Este método inicia mensagens automáticas disparadas por cron, sejam elas a cada minuto ou um dia/hora específica 46 | * pode se utilizar o site https://crontab.guru para criar as crontabs personalizadas 47 | * @param channel Nome do canal que será usado como target para as mensagens 48 | * @param client Client conectado com o IRC da Twitch 49 | */ 50 | var turnOnAutomaticMessages = function (channel, client) { 51 | // Chamando a função em async 52 | (function () { return __awaiter(void 0, void 0, void 0, function () { 53 | var automatic, autoCron; 54 | return __generator(this, function (_a) { 55 | switch (_a.label) { 56 | case 0: return [4 /*yield*/, initTemplates_1.default(channel)]; 57 | case 1: 58 | automatic = (_a.sent()).automatic; 59 | autoCron = []; 60 | // Cria um novo schedule com o cron para cada mensagem automática 61 | return [4 /*yield*/, automatic.forEach(function (_a) { 62 | var schedule = _a.schedule, messages = _a.messages; 63 | autoCron.push(node_cron_1.default.schedule(schedule, function () { 64 | messages.forEach(function (message) { return client.say(channel, message); }); 65 | })); 66 | })]; 67 | case 2: 68 | // Cria um novo schedule com o cron para cada mensagem automática 69 | _a.sent(); 70 | // Executa cada uma das mensagens automáticas depois de criadas 71 | autoCron.forEach(function (auto) { return auto.start(); }); 72 | return [2 /*return*/]; 73 | } 74 | }); 75 | }); })(); 76 | }; 77 | exports.default = turnOnAutomaticMessages; 78 | -------------------------------------------------------------------------------- /build/utils/utils.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | //TODO: refatorar os imports para cada função 7 | var initTemplates_1 = __importDefault(require("../useCases/initTemplates/initTemplates")); 8 | var findCommandByAction_1 = __importDefault(require("./findCommandByAction")); 9 | var getSanitizedRender_1 = __importDefault(require("./getSanitizedRender")); 10 | var sendErrorCommand_1 = __importDefault(require("./sendErrorCommand")); 11 | var turnOnAutomaticMessages_1 = __importDefault(require("./turnOnAutomaticMessages")); 12 | exports.default = { 13 | findCommandByAction: findCommandByAction_1.default, 14 | getTemplateChannel: initTemplates_1.default, 15 | getSanitizedRender: getSanitizedRender_1.default, 16 | sendErrorCommand: sendErrorCommand_1.default, 17 | turnOnAutomaticMessages: turnOnAutomaticMessages_1.default 18 | }; 19 | -------------------------------------------------------------------------------- /commands.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignored-commands": [], 3 | "commands": [ 4 | { 5 | "help": "Você recebe um convite para o discord da Dev House", 6 | "actions": [ 7 | "discord", 8 | "devhouse" 9 | ], 10 | "messages": [ 11 | "/color yellowgreen", 12 | "Entre no discord da devHouse!!: https://discord.gg/hUjdjevDCz" 13 | ] 14 | }, 15 | { 16 | "help": "Se inscreva na maratona Live Code Girls dia 29/05", 17 | "actions": [ 18 | "maratona" 19 | ], 20 | "messages": [ 21 | "/color yellowgreen", 22 | "Se inscreve na maratona Live Code Girls que vai acontecer no dia 29/05: https://maratona3.live/" 23 | ] 24 | }, 25 | { 26 | "help": "Fique sabendo mais sobre a Maratona da Discover", 27 | "actions": [ 28 | "maratona-discover" 29 | ], 30 | "messages": [ 31 | "/color yellowgreen", 32 | "Se inscreva na maratona discover https://maratonadiscover.rocketseat.com.br/inscricao" 33 | ] 34 | }, 35 | { 36 | "help": "Twitch dos convidados", 37 | "actions": [ 38 | "convidados" 39 | ], 40 | "messages": [ 41 | "/color yellowgreen", 42 | "Rahmai: https://www.twitch.tv/RahmaiDev || xTecna: https://www.twitch.tv/xtecna" 43 | ] 44 | }, 45 | 46 | { 47 | "help": "Fique sabendo mais sobre a Discover", 48 | "actions": [ 49 | "discover" 50 | ], 51 | "messages": [ 52 | "/color yellowgreen", 53 | "Se inscreva no discover https://app.rocketseat.com.br/discover" 54 | ] 55 | }, 56 | { 57 | "help": "Fique sabendo mais sobre o Github da DevHouse", 58 | "actions": [ 59 | "githouse" 60 | ], 61 | "messages": [ 62 | "/color yellowgreen", 63 | "Contribua no GitHub da DEVHOUSE https://github.com/dev-house-community" 64 | ] 65 | }, 66 | { 67 | "help": "As redes sociais da Jake", 68 | "actions": [ 69 | "social" 70 | ], 71 | "messages": [ 72 | "/color yellowgreen", 73 | "Siga as minhas redes: Instagram >> https://www.instagram.com/jakeliny.gracielly/ | Linkedin >> https://www.linkedin.com/in/jakelinygracielly/" 74 | ] 75 | }, 76 | { 77 | "help": "Veja a configuração do meu PC", 78 | "actions": [ 79 | "setup", 80 | "pc", 81 | "config" 82 | ], 83 | "messages": [ 84 | "/color yellowgreen", 85 | "Processador AMD Ryzen 5 3600X", 86 | "Placa de vídeo RTX 2060 super", 87 | "Placa Mãe B450M Steel Legend", 88 | "Mem RAM 4x8gb 3200MHz XPG d41", 89 | "Fonte CX550W Corsair", 90 | "Gabinete Gamdias Talos M1B", 91 | "Teclado Mars Hyper X", 92 | "Fone Zeus 2 Sakura Editon Redragon", 93 | "Webcam logitech C925e", 94 | "Microfone HyperX Quadcast" 95 | ] 96 | }, 97 | { 98 | "help": "Mostra todos os comandos disponíveis para uso no Bot", 99 | "actions": [ 100 | "comandos", 101 | "commands", 102 | "cmds" 103 | ], 104 | "messages": [ 105 | "/color HotPink", 106 | "{{ #context.commands }}{{ actions.0 }} {{ /context.commands }}" 107 | ] 108 | }, 109 | { 110 | "help": "Mostra a descrição do projeto ThasfinBot", 111 | "actions": [ 112 | "thasfin", 113 | "thasfinBot" 114 | ], 115 | "messages": [ 116 | "/color HotPink", 117 | "Estamos trabalhando em um bot para a Twitch, esses comandos no chat que são enviados pela ThasfinBot já é o nosso Bot em ação. Estamos no momento refatorando e melhorando para ser um serviço que outros streamers possam usar" 118 | ] 119 | }, 120 | { 121 | "help": "Hackathon da Dev House", 122 | "actions": [ 123 | "hackathon" 124 | ], 125 | "messages": [ 126 | "/color HotPink", 127 | "Se inscreva no hackathon da Dev House: https://dev-house-community.github.io/HACKA-HOUSE/" 128 | ] 129 | }, 130 | { 131 | "help": "GitHub da Jakeliny", 132 | "actions": [ 133 | "github", 134 | "git" 135 | ], 136 | "messages": [ 137 | "/color HotPink", 138 | "Meu github: https://github.com/jakeliny" 139 | ] 140 | }, 141 | { 142 | "help": "Chat unificado da Dev House", 143 | "actions": [ 144 | "chat", 145 | "chatdevhouse" 146 | ], 147 | "messages": [ 148 | "/color HotPink", 149 | "Link do chat unificado: https://maykbrito.github.io/youtube-twitch-live-chat/" 150 | ] 151 | }, 152 | { 153 | "help": "Digite '!ajuda ' para ver a descrição do ", 154 | "actions": [ 155 | "ajuda", 156 | "help", 157 | "?" 158 | ], 159 | "messages": "{{{ help }}}" 160 | } 161 | ], 162 | "automatic": [ 163 | { 164 | "schedule": "*/20 * * * *", 165 | "messages": [ 166 | "/color DodgerBlue", 167 | "Você já ouviu falar do discord da DevHouse? Então pega o link https://discord.gg/hUjdjevDCz GlitchCat" 168 | ] 169 | }, 170 | { 171 | "schedule": "*/30 * * * *", 172 | "messages": [ 173 | "/color DodgerBlue", 174 | "Está curtindo a live? Deixa seu follow ai pra ajudar!! <3" 175 | ] 176 | } 177 | ] 178 | } 179 | -------------------------------------------------------------------------------- /commands.rahmaidev.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignored-commands": [], 3 | "commands": [ 4 | { 5 | "help": "Você recebe um convite para o discord da Dev House", 6 | "actions": [ 7 | "discord", 8 | "devhouse" 9 | ], 10 | "messages": [ 11 | "/color yellowgreen", 12 | "Entre no discord da devHouse!!: https://discord.gg/hUjdjevDCz" 13 | ] 14 | }, 15 | { 16 | "help": "Fique sabendo mais sobre a Maratona da Discover", 17 | "actions": [ 18 | "maratona-discover" 19 | ], 20 | "messages": [ 21 | "/color yellowgreen", 22 | "Se inscreva na maratona discover https://maratonadiscover.rocketseat.com.br/inscricao" 23 | ] 24 | }, 25 | { 26 | "help": "Fique sabendo mais sobre a Discover", 27 | "actions": [ 28 | "discover" 29 | ], 30 | "messages": [ 31 | "/color yellowgreen", 32 | "Se inscreva no discover https://app.rocketseat.com.br/discover" 33 | ] 34 | }, 35 | { 36 | "help": "Fique sabendo mais sobre o Github da DevHouse", 37 | "actions": [ 38 | "githouse" 39 | ], 40 | "messages": [ 41 | "/color yellowgreen", 42 | "Contribua no GitHub da DEVHOUSE https://github.com/dev-house-community" 43 | ] 44 | }, 45 | { 46 | "help": "As redes sociais da Jake", 47 | "actions": [ 48 | "social" 49 | ], 50 | "messages": [ 51 | "/color yellowgreen", 52 | "Siga as minhas redes: Instagram >> https://www.instagram.com/jorge.alegretti/ | Linkedin >> https://www.linkedin.com/in/jorge-alegretti/" 53 | ] 54 | }, 55 | { 56 | "help": "Mostra todos os comandos disponíveis para uso no Bot", 57 | "actions": [ 58 | "comandos", 59 | "commands", 60 | "cmds" 61 | ], 62 | "messages": [ 63 | "/color HotPink", 64 | "{{ #context.commands }}{{ actions.0 }} {{ /context.commands }}" 65 | ] 66 | }, 67 | { 68 | "help": "GitHub do Rahmai", 69 | "actions": [ 70 | "github", 71 | "git" 72 | ], 73 | "messages": [ 74 | "/color HotPink", 75 | "Meu github: https://github.com/jorge-lba" 76 | ] 77 | }, 78 | { 79 | "help": "Chat unificado da Dev House", 80 | "actions": [ 81 | "chat", 82 | "chatdevhouse" 83 | ], 84 | "messages": [ 85 | "/color HotPink", 86 | "Link do chat unificado: https://maykbrito.github.io/youtube-twitch-live-chat/" 87 | ] 88 | }, 89 | { 90 | "help": "Digite '!ajuda ' para ver a descrição do ", 91 | "actions": [ 92 | "ajuda", 93 | "help", 94 | "?" 95 | ], 96 | "messages": "{{{ help }}}" 97 | } 98 | ], 99 | "automatic": [ 100 | { 101 | "schedule": "*/20 * * * *", 102 | "messages": [ 103 | "/color DodgerBlue", 104 | "Você já ouviu falar do discord da DevHouse? Então pega o link https://discord.gg/hUjdjevDCz GlitchCat" 105 | ] 106 | }, 107 | { 108 | "schedule": "*/30 * * * *", 109 | "messages": [ 110 | "/color DodgerBlue", 111 | "Está curtindo a live? Deixa seu follow ai pra ajudar!! <3" 112 | ] 113 | } 114 | ] 115 | } 116 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "thasfinbot", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.ts", 6 | "scripts": { 7 | "start": "node ./build/app.js", 8 | "dev": "nodemon -i 'src/database/db.json' -r dotenv/config src/app.ts" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "@types/lru-cache": "^5.1.0", 15 | "dotenv": "^8.2.0", 16 | "express": "^4.17.1", 17 | "lowdb": "^1.0.0", 18 | "lru-cache": "^6.0.0", 19 | "mustache-async": "^0.1.3", 20 | "node-cron": "^3.0.0", 21 | "tmi.js": "^1.7.5", 22 | "twitch": "^4.5.5", 23 | "twitch-eventsub": "^4.5.5", 24 | "typescript": "^4.2.4" 25 | }, 26 | "devDependencies": { 27 | "@types/express": "^4.17.12", 28 | "@types/lowdb": "^1.0.10", 29 | "@types/node-cron": "^2.0.3", 30 | "@types/tmi.js": "^1.7.1", 31 | "jest": "^26.6.3", 32 | "nodemon": "^2.0.7", 33 | "ts-node": "^9.1.1" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/app.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import tmi from 'tmi.js'; 3 | import handleCommands from './useCases/handleCommands/handleCommands'; 4 | import utils from './utils/utils'; 5 | import config from './config'; 6 | 7 | const app = express(); 8 | app.get('/', (req, res) => res.send('Thasfin Bot is running')); 9 | app.listen(process.env.PORT, () => {console.log('⚡️Server is running'); 10 | }); 11 | 12 | const client = new tmi.client(config); 13 | client.on('message', handleCommands(client)); 14 | 15 | function handleConnected(address: any, port: any) { 16 | console.log(`*** Bot conectado com sucesso no IRC (${address}:${port}} nos canais [${config.channels}]`); 17 | config.channels?.forEach(channel => { 18 | //TODO refactor this 19 | utils.turnOnAutomaticMessages(channel, client); 20 | 21 | //TODO: So rodar o startup quando o streamer acaba de abrir live, se a live 22 | // está rolando a mais de 10 minutos ou algo assim esse startup não joga o texto no chat 23 | const startup = [ 24 | "/color yellowgreen", 25 | // "/me A Thasfin tá na área HeyGuys" 26 | ]; 27 | 28 | startup.forEach((msg : any) => client.say(channel, msg)); 29 | }) 30 | } 31 | 32 | client.on('connected', handleConnected); 33 | client.connect(); 34 | -------------------------------------------------------------------------------- /src/commands/open.ts: -------------------------------------------------------------------------------- 1 | const commands : any = [ 2 | { 3 | name: '!discord', 4 | message: 'Entra no discord da devHouse!! https://discord.gg/hUjdjevDCz', 5 | }, 6 | { 7 | name: '!maratona', 8 | message: 9 | 'Se inscreva na maratona discover https://maratonadiscover.rocketseat.com.br/inscricao', 10 | }, 11 | { 12 | name: '!discover', 13 | message: 'Se inscreva no discover https://app.rocketseat.com.br/discover', 14 | }, 15 | { 16 | name: '!githouse', 17 | message: 'Contribua no GitHub da DEVHOUSE https://github.com/dev-house-community', 18 | }, 19 | { 20 | name: '!devhouse', 21 | message: 'Inscreva no YouTube da DevHouse https://www.youtube.com/channel/UCFohroq6iQ6N4U4w3w4NXSw', 22 | }, 23 | { 24 | name: '!social', 25 | message: 'Siga as minhas redes https://www.instagram.com/jakeliny.gracielly/ e https://www.linkedin.com/in/jakelinygracielly/' 26 | }, 27 | { 28 | name: '!setup', 29 | message: 'Processador AMD Ryzen 5 3600X | Placa de vídeo RTX 2060 super | Placa Mãe B450M Steel Legend | Mem RAM 4x8gb 3200MHz XPG d41 | Fonte CX550W Corsair | Gabinete Gamdias Talos M1B | Teclado Mars Hyper X | Fone Zeus 2 Sakura Editon Redragon | Webcam logitech C925e' 30 | } 31 | 32 | ]; 33 | 34 | export default commands; 35 | -------------------------------------------------------------------------------- /src/config.ts: -------------------------------------------------------------------------------- 1 | import { Options } from 'tmi.js'; 2 | 3 | const config : Options = { 4 | identity: { 5 | username: process.env.USER, 6 | password: process.env.TOKEN, 7 | }, 8 | channels: ['jakeliny', 'maykbrito', 'rahmaidev'] 9 | }; 10 | 11 | export default config; -------------------------------------------------------------------------------- /src/database/db.json: -------------------------------------------------------------------------------- 1 | { 2 | "counts": [] 3 | } -------------------------------------------------------------------------------- /src/database/index.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import low from "lowdb"; 3 | 4 | const FileSync = require('lowdb/adapters/FileSync') 5 | 6 | /** 7 | * Este método inicia um database lowdb utilizando o arquivo db.json como memory 8 | * @returns {db} 9 | */ 10 | const database = async () => { 11 | const adapter = new FileSync(path.resolve('src','database','db.json')); 12 | return low(adapter); 13 | } 14 | 15 | export default database(); -------------------------------------------------------------------------------- /src/useCases/handleCommands/handleCommands.ts: -------------------------------------------------------------------------------- 1 | import utils from '../../utils/utils'; 2 | import getTemplateChannel from '../../useCases/initTemplates/initTemplates'; 3 | 4 | /** 5 | * Método que trata todos os recebimentos de mensagens e processa comandos 6 | * @param client Client IRC da Twitch conectado com canal 7 | * @returns {(function(*=, *, *, *=): Promise)|*} Função para tratamento das mensagens 8 | */ 9 | const handleCommands = (client: any) => async (target: any, context: any, receivedMessage: any, isBot: any) => { 10 | // Varifica se quem mandou a mensagem não é bot e se a mensagem é um comando aceitável 11 | if (isBot) return; 12 | if (!receivedMessage.startsWith("!")) return; 13 | 14 | // Inicializa as constantes para processo de tratamento do comando (action) 15 | const req = receivedMessage.split(" "); 16 | const action = req.shift(); 17 | const template = await getTemplateChannel(target); 18 | const ignored = template["ignored-commands"]; 19 | const commands = template["commands"]; 20 | 21 | // Busca o comando no template do canal e define os argumentos e contexto para uso no comando 22 | const cmd = utils.findCommandByAction(action, ignored, commands); 23 | const args = { 24 | twitch: { target, context, receivedMessage, isBot }, 25 | context: { cmd, action, req, commands, ignored, template } 26 | }; 27 | 28 | // Try / Catch para processamento do comando solicitado 29 | try { 30 | // Partials se refe aos useCommands que podem ser chamados a partir 31 | const partials = await require('../../useCommands')(args); 32 | const rendered = await JSON.stringify(cmd.command); 33 | const parsed = utils.getSanitizedRender(args, rendered); 34 | const shouldPrintMessages = parsed != null && Array.isArray(parsed.messages); 35 | 36 | if (shouldPrintMessages) parsed["messages"].forEach((message: any) => client.say(target, message)); 37 | } 38 | catch (e) { utils.sendErrorCommand(client, args, e); } 39 | } 40 | 41 | export default handleCommands -------------------------------------------------------------------------------- /src/useCases/initTemplates/initTemplates.ts: -------------------------------------------------------------------------------- 1 | //TODO ver o que é LRU 2 | import LRU from "lru-cache"; 3 | import path from "path"; 4 | import {promises} from 'fs'; 5 | 6 | const cache = new LRU({ maxAge: 500 * 60 * 60 }); 7 | 8 | /** 9 | * Método que retorna o template a ser utilizado pelo canal pegando o valor ou cache (cache de 1/2h) 10 | * @param channel canal de onde o bot será executado 11 | * @returns {object|null} Retorna o template do canal ou o template padrão do bot 12 | */ 13 | const getTemplateChannel: any = async (channel = "") => { 14 | // Remove o primeiro valor do channel "#" 15 | const owner = channel.substring(1,channel.length); 16 | 17 | // Retorna o valor em cache do template caso já tenha sido salvo anteriormente 18 | if (cache.get(`commands.${owner}.json`)) { return JSON.parse(`${cache.get(`commands.${owner}.json`)}`)} 19 | 20 | // Busca os templates (default / channel) para retornar 21 | const [channelCommands, commonCommands] = await Promise.all([ 22 | // promises.readFile(path.resolve(`commands.${owner}.json`), "UTF-8").catch(() => null), 23 | // promises.readFile(path.resolve(`commands.json`), "UTF-8").catch(() => null), 24 | 25 | promises.readFile(path.resolve(`commands.${owner}.json`)).catch(() => null), 26 | promises.readFile(path.resolve(`commands.json`)).catch(() => null), 27 | ]) 28 | //TODO ver esse buffer do channelCommands 29 | // Se encontrar o template do canal, prioriza o retorno e salva no cache 30 | if (channelCommands != null) { 31 | cache.set(`commands.${owner}.json`, channelCommands); 32 | return JSON.parse(`${channelCommands}`); 33 | } 34 | 35 | // Salva para esse canal o template de comandos default e retorna ele 36 | cache.set(`commands.${owner}.json`, commonCommands); 37 | return JSON.parse(`${commonCommands}`); 38 | } 39 | 40 | export default getTemplateChannel -------------------------------------------------------------------------------- /src/useCases/openCommands.ts: -------------------------------------------------------------------------------- 1 | import commands from '../commands/open'; 2 | 3 | const openCommands = (client : any) => ( 4 | target: any, 5 | context: any, 6 | receivedMessage: any, 7 | isBot: any 8 | ) => { 9 | const message = receivedMessage.trim(); 10 | 11 | if (isBot) return; 12 | 13 | if( message === '!help'){ 14 | const sendMessage = commands.reduce((acc: any, command: any) => { 15 | return acc + ` ${command.name}`; 16 | }, ''); 17 | return client.say(target, `${sendMessage} `); 18 | 19 | } 20 | 21 | const command : any = commands.find((command: any) => command.name === message); 22 | if (!command) return 'Error'; 23 | 24 | return client.say(target, `@${context.username} ${command.message}`); 25 | 26 | } 27 | 28 | export default openCommands -------------------------------------------------------------------------------- /src/useCommands/count/count.js: -------------------------------------------------------------------------------- 1 | const database = require("../../database"); 2 | const MAX_INCREMENT = 10; 3 | 4 | /** 5 | * Esta método pega a requisição e retorna um valor entre 1 e MAX_INCREMENT parseado da requisição 6 | * @param req Array com a requisição do comando, o que foi digitado após ele separado por espaços 7 | * @returns {number} Entre 0 e MAX_INCREMENT 8 | */ 9 | const getIncrementValueReq = (req) => { 10 | if(!req.length) return 1; 11 | const increment = parseInt(req) || 1; 12 | if(increment < MAX_INCREMENT) return increment; 13 | return 1; 14 | } 15 | 16 | /** 17 | * Este método atualiza o db e retorna o dado salvo do count 18 | * @param key Chave salva no db para o count 19 | * @param value Valor para ser incrementado 20 | * @returns {number} Valor salvo no db 21 | */ 22 | const getIncrementValueDatabase = async(key, value) => { 23 | // Busca o valor atual na database caso não tenha cria um objeto inicial 24 | const saved = await database.get("counts").find({ key }).value() || { key, value: 0 }; 25 | 26 | // Incrementa o valor em database/inicial com o valor de incremento 27 | saved.value += value; 28 | 29 | // Remove o estado atual (JSON Db - se salvar de novo sem apagar cria outro objeto) 30 | await database.get('counts').remove({ key }).write(); 31 | 32 | // Salva no banco o novo valor de counts e retorna esse valor 33 | await database.get("counts").push(saved).write(); 34 | return saved.value; 35 | } 36 | 37 | /** 38 | * Método que ao ser executado pega o id da action e dá um count de quantas vezes foi chamado, retornando o valor 39 | * @param command Comando solicitado para count 40 | * @param req Requisição do comando, caso venha vazio nada foi passado após o comando (é um array) 41 | * @returns {number} Valor salvo no db 42 | */ 43 | module.exports = async ({ context: { cmd: { command }, req } }) => { 44 | // Pega o nome da primeira action do comando pra definir como key no db 45 | const action = command.actions[0]; 46 | const key_db = `count_${action}`; 47 | 48 | // Pega o valor passado na chamada ou define o valor como 1 caso não tenha valor ou utrapasse o MAX_INCREMENT 49 | const increment = getIncrementValueReq(req); 50 | const count = await getIncrementValueDatabase(key_db, increment); 51 | 52 | // Retorna o valor atual incrementado no banco 53 | return count; 54 | } -------------------------------------------------------------------------------- /src/useCommands/help/help.ts: -------------------------------------------------------------------------------- 1 | import utils from "../../utils/utils"; 2 | 3 | /** 4 | * Método faz o join da mensagem pelo prefixo do break 5 | * @param messages Array de mensagens que será feito o join 6 | * @returns {string} String unica com todas as posições do array separados por ~break~ 7 | */ 8 | const JoinMessagesToSanitizeResponse = (messages: any) => { 9 | return messages.join("~break~"); 10 | } 11 | 12 | interface Twitch{ 13 | twitch: { 14 | context: { 15 | username: String 16 | } }, 17 | context: { 18 | req: any, 19 | commands: any, 20 | ignored: any 21 | } } 22 | 23 | /** 24 | * Método que busca o help de um comando, sendo ele restrito ou não, caso o comando não exista ele dá uma mensagem amigável 25 | * @param username Usuário do @ quem pediu ajuda 26 | * @param req Requisição solicitada após o ajuda, caso seja um Array Vazio o comando solicitado a ajuda é o próprio 27 | * @param commands Comandos cadastrados para o canal 28 | * @param ignored Lista de comandos ignorados para o canal 29 | * @returns {string} Retorna uma array de string com o join de ~break~ para saber quando tem que separar (Mustache não gera um array ele coloca erroneamente um array dentro da string) 30 | */ 31 | const help = ({ twitch: { context: { username } }, context: { req, commands, ignored} } : Twitch) => { 32 | let action = "help"; 33 | if (req.length != 0) { action = req.shift(); } 34 | 35 | const { find, reserved, command } = utils.findCommandByAction(action, ignored, commands); 36 | const isReservedCommandSearch = reserved == true; 37 | const isAnUnknowCommandOrWithoutHelp = find == false || find == true && typeof command?.help != "string"; 38 | 39 | if (isReservedCommandSearch) { return JoinMessagesToSanitizeResponse(["/color HotPink", `/me @${username} Esse comando ${action} é reservado (Só tenho permissão para falar isso) Kappa`]); } 40 | if (isAnUnknowCommandOrWithoutHelp) { return JoinMessagesToSanitizeResponse(["/color HotPink", `/me @${username} Olha eu nem sei como te ajudar com o commando ${action} se nem sei o que é WutFace`]); } 41 | 42 | return JoinMessagesToSanitizeResponse(["/color YellowGreen", `/me Olha @${username} achei isso aqui, ajuda? ${command.help}`]); 43 | } 44 | 45 | export default help -------------------------------------------------------------------------------- /src/useCommands/index.ts: -------------------------------------------------------------------------------- 1 | import count from "./count/count"; 2 | import help from "./help/help"; 3 | 4 | /** 5 | * Funlções que podem ser executadas pelo Mustache na hora de ler o commands (Executa a função e retorna valor ou não) 6 | * @param args todos os argumentos recolhidos durante a execução no comando 7 | * @returns {object} todos as funções disponíveis para serem utilizadas no template 8 | */ 9 | module.exports = (args: any) => { 10 | return { 11 | count: () => { return count(args) }, 12 | help: () => { return help(args) }, 13 | funcaoComandoConsole: () => { console.log("Oie") } 14 | } 15 | } -------------------------------------------------------------------------------- /src/utils/findCommandByAction.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Esse método busca um comando removendo o ! dentro do arquivo commands.json 3 | * @param action Comando a ser buscado com ou sem o prefixo (!) 4 | * @param ignored Lista de comandos a serem ignorados pelo bot 5 | * @param commands Lista de comandos reconhecidos 6 | * @returns {{reserved: boolean, find: boolean, command: null}|{reserved: boolean, find: boolean, command}} 7 | */ 8 | const findCommandByAction = (action: any, ignored: any, commands: any) => { 9 | if (action.startsWith("!")) { action = action.substring(1,action.length); } 10 | if (ignored.includes(action)) return { find: true, reserved: true, command: null }; 11 | 12 | // Iteração para buscar o commando e retornar 13 | for (const index in commands) { 14 | if(commands[index].actions?.includes(action)) { return { find: true, reserved: false, command: commands[index] }; } 15 | else { continue; } 16 | } 17 | 18 | return { find: false, reserved: false, command: null }; 19 | } 20 | 21 | export default findCommandByAction -------------------------------------------------------------------------------- /src/utils/getSanitizedRender.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Este método sanitiza o retorno depois de ter passado pelo render (que chama as funções do useCommands, pois como o 3 | * comando ajuda para retornar um array dentro das mensagens é necessário salvar como string no render e pós render 4 | * quebrar as linhas novamente 5 | * @param find Verifica se o comando foi ou não encontrado 6 | * @param action O comando que foi solicitado 7 | * @param username O username de quem requisitou o comando 8 | * @param rendered O render do commands.json após os useCommands terem sidos acionados caso sejam necessários (json string) 9 | * @returns {{messages: string[]}|null|command} 10 | */ 11 | const getSanitizedRender = ({ context: { cmd: { find, action } }, twitch: { context: username } }: any, rendered: any) => { 12 | const parsed = JSON.parse(rendered); 13 | const isExistentCommand = find == true; 14 | if(!isExistentCommand) return {messages: ["/color Firebrick", `/me Ops @${username} acho que não existe esse comando ${action} não hein Keepo`]}; 15 | else if(parsed == null) return null; 16 | 17 | // Função que caso a mensagem for string transforma ela em Array separando por \n 18 | const sanitizeMessagesFromStringToArray = ({ messages }: any) => { 19 | if(typeof messages == "string") return messages.split("~break~"); 20 | return messages; 21 | } 22 | 23 | parsed.messages = sanitizeMessagesFromStringToArray(parsed); 24 | return parsed; 25 | } 26 | 27 | export default getSanitizedRender -------------------------------------------------------------------------------- /src/utils/sendErrorCommand.ts: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Este método concentra os erros na hora de usar um comando, ou executar uma função do partials, ele manda uma 4 | * mensagem no chat alertando e registra no console o erro gerado com trace! 5 | * @param client Client conectado com o IRC da Twitch 6 | * @param target Canal para onde deve ser enviado a mensagem 7 | * @param username Username do usuário que enviou o comando 8 | * @param action Comando solicitado pelo @ 9 | * @param error Erro com trace do ocorrido e mensagem 10 | */ 11 | const sendErrorCommand = (client: any, { twitch: { target, context: { username } }, context: { action } }: any, error: any) => { 12 | // Ternário que pega o usuário do dono do canal para utilizar na mensagem 13 | const owner = target.startsWith("#") ? target.substring(1, target.length) : target; 14 | 15 | const messages = [ 16 | `/color Coral`, 17 | `/me Nossa @${username} você me quebrou legal com esse comando aí ${action} .... @${owner} eu deixei um log pra te ajudar a resolver essa bucha aí se vira!` 18 | ]; 19 | 20 | messages.forEach(message => client.say(target, message)); 21 | console.error(error); 22 | } 23 | 24 | export default sendErrorCommand -------------------------------------------------------------------------------- /src/utils/turnOnAutomaticMessages.ts: -------------------------------------------------------------------------------- 1 | import cron from 'node-cron'; 2 | import getTemplateChannel from '../useCases/initTemplates/initTemplates'; 3 | /** 4 | * Este método inicia mensagens automáticas disparadas por cron, sejam elas a cada minuto ou um dia/hora específica 5 | * pode se utilizar o site https://crontab.guru para criar as crontabs personalizadas 6 | * @param channel Nome do canal que será usado como target para as mensagens 7 | * @param client Client conectado com o IRC da Twitch 8 | */ 9 | const turnOnAutomaticMessages = (channel: any, client: any) => { 10 | // Chamando a função em async 11 | (async () => { 12 | // Pega todos os comandos que rodam um determinado tempo configurado pelo cron no template 13 | const { automatic } = await getTemplateChannel(channel); 14 | const autoCron: any = []; 15 | 16 | // Cria um novo schedule com o cron para cada mensagem automática 17 | await automatic.forEach(( 18 | {schedule, messages}: 19 | {schedule: any, messages: any} 20 | ) => { 21 | autoCron.push(cron.schedule(schedule, () => { 22 | messages.forEach((message: any) => client.say(channel, message)); 23 | })); 24 | }); 25 | 26 | // Executa cada uma das mensagens automáticas depois de criadas 27 | autoCron.forEach((auto: any) => auto.start()); 28 | })() 29 | } 30 | 31 | export default turnOnAutomaticMessages -------------------------------------------------------------------------------- /src/utils/utils.ts: -------------------------------------------------------------------------------- 1 | //TODO: refatorar os imports para cada função 2 | import getTemplateChannel from '../useCases/initTemplates/initTemplates'; 3 | import findCommandByAction from './findCommandByAction'; 4 | import getSanitizedRender from './getSanitizedRender'; 5 | import sendErrorCommand from './sendErrorCommand'; 6 | import turnOnAutomaticMessages from './turnOnAutomaticMessages'; 7 | 8 | export default { 9 | findCommandByAction, 10 | getTemplateChannel, 11 | getSanitizedRender, 12 | sendErrorCommand, 13 | turnOnAutomaticMessages 14 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Basic Options */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ 8 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ 9 | // "lib": [], /* Specify library files to be included in the compilation. */ 10 | // "allowJs": true, /* Allow javascript files to be compiled. */ 11 | // "checkJs": true, /* Report errors in .js files. */ 12 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */ 13 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 14 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 15 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 16 | // "outFile": "./", /* Concatenate and emit output to single file. */ 17 | // "outDir": "./", /* Redirect output structure to the directory. */ 18 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 19 | // "composite": true, /* Enable project compilation */ 20 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 21 | // "removeComments": true, /* Do not emit comments to output. */ 22 | // "noEmit": true, /* Do not emit outputs. */ 23 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 24 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 25 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 26 | 27 | /* Strict Type-Checking Options */ 28 | "strict": true, /* Enable all strict type-checking options. */ 29 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 30 | // "strictNullChecks": true, /* Enable strict null checks. */ 31 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 32 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 33 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 34 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 35 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 36 | 37 | /* Additional Checks */ 38 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 39 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 40 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 41 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 42 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ 43 | // "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */ 44 | 45 | /* Module Resolution Options */ 46 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 47 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 48 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 49 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 50 | // "typeRoots": [], /* List of folders to include type definitions from. */ 51 | // "types": [], /* Type declaration files to be included in compilation. */ 52 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 53 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 54 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 55 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 56 | 57 | /* Source Map Options */ 58 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 59 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 60 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 61 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 62 | 63 | /* Experimental Options */ 64 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 65 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 66 | 67 | /* Advanced Options */ 68 | "skipLibCheck": true, /* Skip type checking of declaration files. */ 69 | "forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */ 70 | "allowJs": true, 71 | "outDir": "build" 72 | } 73 | } 74 | --------------------------------------------------------------------------------