├── .env.example ├── .gitignore ├── LICENSE ├── README.md ├── package-lock.json ├── package.json └── src ├── config ├── commands.js └── greetings.js ├── data └── copies.json ├── index.js ├── lib ├── commands.js └── customCommands.js └── utils ├── fetchData.js ├── random.js └── randomMsg.js /.env.example: -------------------------------------------------------------------------------- 1 | BOT_USERNAME= 2 | OAUTH_TOKEN= 3 | CHANNELS_NAME= 4 | USERNAME= 5 | CONSUMER_KEY= 6 | CONSUMER_SECRET= 7 | TOKEN_KEY= 8 | TOKEN_SECRET= -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | #firebase 107 | src/serviceAccountKey.json -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Oscar Barajas Tavares 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gndxdev-bot 2 | 3 | gndxdev-bot send messages to Twitch. 4 | 5 | ### Develop 6 | 7 | ```bash 8 | npm run dev 9 | ``` 10 | 11 | ### Use 12 | 13 | - Configure your environment variables 14 | - Set your greetings ands commands 15 | 16 | ```bash 17 | npm run start 18 | ``` 19 | 20 | ### Contributing 21 | If someone wants to add or improve something, I invite you to collaborate directly in this repository: [gndxdev-bot](https://github.com/gndx/gndxdev-bot/) 22 | 23 | ### Contributors 24 | 25 | - IOSamuel : [https://github.com/iosamuel](https://github.com/iosamuel) 26 | - Manuel Galindez : [https://github.com/jgalianoz](https://github.com/jgalianoz) 27 | 28 | ### License 29 | gndxdev-bot is released under the [MIT License](https://opensource.org/licenses/MIT). 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gndxdev-bot", 3 | "version": "1.0.1", 4 | "description": "gndxdev-bot for twitch", 5 | "main": "src/index.js", 6 | "dependencies": { 7 | "dotenv": "8.2.0", 8 | "firebase-admin": "11.5.0", 9 | "node-cron": "2.0.3", 10 | "node-fetch": "2.6.0", 11 | "tmi.js": "1.5.0", 12 | "twitter": "1.7.1" 13 | }, 14 | "devDependencies": { 15 | "nodemon": "2.0.4" 16 | }, 17 | "scripts": { 18 | "start": "node .", 19 | "dev": "nodemon ." 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git+https://github.com/gndx/gndxdev-bot.git" 24 | }, 25 | "keywords": [ 26 | "javascript", 27 | "twitch" 28 | ], 29 | "author": "Oscar Barajas ", 30 | "license": "MIT", 31 | "bugs": { 32 | "url": "https://github.com/gndx/gndxdev-bot/issues" 33 | }, 34 | "homepage": "https://github.com/gndx/gndxdev-bot#readme" 35 | } 36 | -------------------------------------------------------------------------------- /src/config/commands.js: -------------------------------------------------------------------------------- 1 | const copies = require("../data/copies.json"); 2 | 3 | const commands = { 4 | private: { 5 | merch: copies.commands.merch, 6 | blog: copies.commands.blog, 7 | social: copies.commands.social, 8 | courses: copies.commands.courses, 9 | twitch: copies.commands.twitch, 10 | youtube: copies.commands.youtube 11 | }, 12 | public: { 13 | discord: copies.commands.discord 14 | } 15 | }; 16 | 17 | module.exports = commands; 18 | -------------------------------------------------------------------------------- /src/config/greetings.js: -------------------------------------------------------------------------------- 1 | const copies = require('../data/copies.json'); 2 | 3 | const greetings = { 4 | welcome: copies.greetings.welcome, 5 | hello: copies.greetings.hello, 6 | subs: copies.greetings.subs 7 | }; 8 | 9 | module.exports = greetings; 10 | -------------------------------------------------------------------------------- /src/data/copies.json: -------------------------------------------------------------------------------- 1 | { 2 | "commands": { 3 | "merch": "HolidayPresent El nuevo merchandising está aquí.... Compruébalo: https://merch.streamelements.com/gndxdev", 4 | "blog": "TakeNRG Te comparto mi blog: https://gndx.dev y mi canal de YouTube donde comparto mis conferencias: https://youtube.com/c/oscarbarajas : Quieres apoyar mi contenido educativo visita mi Patreon: https://www.patreon.com/gndx", 5 | "social": "Mis Redes sociales: \n Instagram: https://instagram.com/gndx \n Twitter: https://twitter.com/gndx \n GitHub: https://github.com/gndx", 6 | "courses": "🎓📗 Mis cursos en Platzi: https://platzi.com/teachers/gndx/", 7 | "twitch": "imGlitch Recuerda todos los domingos a las 6:00 P.M. Hablo de tecnología en vivo en este canal, ¡Sígueme!", 8 | "youtube": "EarthDay Suscríbete a mi canal de YouTube donde comparto mis conferencias https://youtube.com/c/oscarbarajas", 9 | "discord": "Comunidad de GnDx en Discord: https://discord.gg/brutEYf VoHiYo!" 10 | }, 11 | "greetings": { 12 | "welcome": "¡Bienvenidos! ;)", 13 | "hello": "imGlitch Hola", 14 | "subs": "¡Gracias por suscribirte! BloodTrail" 15 | } 16 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const tmi = require("tmi.js"); 2 | const cron = require("node-cron"); 3 | const randomMsg = require("./utils/randomMsg"); 4 | const greetings = require("./config/greetings"); 5 | const Commands = require("./lib/commands"); 6 | require("dotenv").config(); 7 | 8 | const welcomeList = ["hola", "buenas", "saludos"]; 9 | const BOTUSERNAME = process.env.BOT_USERNAME; 10 | const CHANNEL = process.env.CHANNELS_NAME; 11 | const PASSWORD = process.env.OAUTH_TOKEN; 12 | 13 | const client = new tmi.client({ 14 | identity: { 15 | username: BOTUSERNAME, 16 | password: PASSWORD, 17 | }, 18 | channels: CHANNEL.split(","), 19 | }); 20 | 21 | const comm = new Commands(client); 22 | 23 | const sendMessage = (target, text, list, message) => { 24 | list.some((t) => { 25 | const includes = text.replace(/ /g, "").includes(t); 26 | if (includes) client.say(target, message); 27 | return text.includes(t); 28 | }); 29 | }; 30 | 31 | cron.schedule("*/7 * * * *", () => { 32 | client.say(`#${CHANNEL}`, randomMsg()); 33 | }); 34 | 35 | client.on("message", async (target, context, msg, self) => { 36 | if (self) return; 37 | const text = msg.toLowerCase(); 38 | 39 | comm.resolve(context, target, text); 40 | 41 | sendMessage( 42 | target, 43 | text, 44 | welcomeList, 45 | `@${context.username} ${greetings.hello}` 46 | ); 47 | }); 48 | 49 | client.on("subscription", (channel, username) => { 50 | client.say(channel, `${username}, ${greetings.subs}`); 51 | }); 52 | 53 | client.on("raided", (channel, username, viewers) => { 54 | client.say( 55 | channel, 56 | `TombRaid ¡Raid!, Gracias a ${username} se han unido ${viewers} espectadores, ${greetings.welcome} PogChamp` 57 | ); 58 | }); 59 | 60 | client.on("connected", (addr, port) => { 61 | console.log(`* Connected to ${addr}:${port}`); 62 | }); 63 | 64 | client.connect(); 65 | -------------------------------------------------------------------------------- /src/lib/commands.js: -------------------------------------------------------------------------------- 1 | const commands = require("../config/commands"); 2 | const CustomCommands = require("./customCommands"); 3 | 4 | const STREAMER = "gndxdev"; 5 | 6 | class Commands extends CustomCommands { 7 | constructor(client) { 8 | super(client); 9 | } 10 | 11 | async resolve(context, target, msg) { 12 | const commandMessage = msg.replace("!", ""); 13 | const privateCommands = context.username === STREAMER; 14 | const allCommands = { 15 | ...(privateCommands && this.commands), 16 | ...(privateCommands && commands.private), 17 | ...commands.public 18 | }; 19 | 20 | const commandKey = Object.keys(allCommands).find(k => 21 | commandMessage.includes(k) 22 | ); 23 | 24 | if (commandKey && commandKey in allCommands) { 25 | const command = allCommands[commandKey]; 26 | if (typeof command.fn === "function") { 27 | await command.fn.apply(this, [context, target, msg]); 28 | } else if (typeof command === "string") { 29 | this.client.say(target, command); 30 | } 31 | } 32 | } 33 | } 34 | 35 | module.exports = Commands; 36 | -------------------------------------------------------------------------------- /src/lib/customCommands.js: -------------------------------------------------------------------------------- 1 | const Twitter = require("twitter"); 2 | const random = require("../utils/random"); 3 | const fetchData = require("../utils/fetchData"); 4 | const serviceAccount = require("../serviceAccountKey.json"); 5 | const admin = require("firebase-admin"); 6 | require('dotenv').config(); 7 | 8 | const DB = process.env.FIRESTORE; 9 | 10 | const clientTwitter = new Twitter({ 11 | consumer_key: process.env.CONSUMER_KEY, 12 | consumer_secret: process.env.CONSUMER_SECRECT, 13 | access_token_key: process.env.TOKEN_KEY, 14 | access_token_secret: process.env.TOKEN_SECRET 15 | }); 16 | 17 | admin.initializeApp({ 18 | credential: admin.credential.cert(serviceAccount), 19 | databaseURL: "https://gndxtwitchbot.firebaseio.com" 20 | }); 21 | 22 | class CustomCommands { 23 | constructor(client) { 24 | this.client = client; 25 | this.commands = { 26 | winner: { fn: this.winner, type: "private" }, 27 | twbot: { fn: this.twbot, type: "private" }, 28 | rifa: { fn: this.rifa, type: "public" }, 29 | song: { fn: this.song, type: "public" } 30 | }; 31 | this.db = admin.firestore(); 32 | this.userList = []; 33 | } 34 | 35 | twbot(_, target, msg) { 36 | let msgTwitter = msg.substr(6); 37 | const tweet = `${msgTwitter} en vivo: https://twitch.tv/gndxdev #EStreamerCoders`; 38 | clientTwitter.post("statuses/update", { status: tweet }, (error, tweet) => { 39 | if (error) throw error; 40 | const tweetUrl = `https://twitter.com/i/web/status/${tweet.id_str}`; 41 | this.client.say( 42 | target, 43 | `¡Nuevo Tweet, dale RT! MrDestructoid ${tweetUrl}` 44 | ); 45 | }); 46 | } 47 | 48 | async song(context, target) { 49 | let pretzel = "https://www.pretzel.rocks/api/v1/playing/twitch/gndxdev/"; 50 | let song = await fetchData(pretzel); 51 | this.client.say(target, `@${context.username}, ${song}`); 52 | } 53 | 54 | async rifa(context, target) { 55 | if (this.userList.includes(context.username)) { 56 | this.client.say( 57 | target, 58 | `@${context.username}, ¡Ya estas particiando BibleThump!` 59 | ); 60 | } else { 61 | this.userList.push(context.username); 62 | await this.db.collection(DB).add({ username: context.username }); 63 | this.client.say( 64 | target, 65 | `@${context.username}, ¡Registro exitoso! VoHiYo!` 66 | ); 67 | } 68 | } 69 | 70 | async winner(context, target) { 71 | let winner = []; 72 | let twitch = this.db.collection(DB); 73 | await twitch 74 | .orderBy("username", "asc") 75 | .get() 76 | .then(snapshot => { 77 | snapshot.forEach(doc => { 78 | winner.push(doc.data()); 79 | }); 80 | }) 81 | .catch(err => { 82 | console.log("Error", err); 83 | }); 84 | let randomUser = random(winner); 85 | this.client.say( 86 | target, 87 | `BloodTrail El Ganador es @${randomUser.username} HolidayPresent` 88 | ); 89 | } 90 | } 91 | 92 | module.exports = CustomCommands; 93 | -------------------------------------------------------------------------------- /src/utils/fetchData.js: -------------------------------------------------------------------------------- 1 | const fetch = require('node-fetch'); 2 | 3 | const fetchData = async (api) => { 4 | const response = await fetch(api); 5 | const text = await response.text(); 6 | return text; 7 | }; 8 | 9 | module.exports = fetchData; -------------------------------------------------------------------------------- /src/utils/random.js: -------------------------------------------------------------------------------- 1 | const random = (array) => { 2 | return array[Math.floor(Math.random() * array.length)]; 3 | } 4 | 5 | module.exports = random; -------------------------------------------------------------------------------- /src/utils/randomMsg.js: -------------------------------------------------------------------------------- 1 | const commands = require("../config/commands"); 2 | 3 | const messages = [...Object.values(commands.private)]; 4 | let messagesRandom = shuffleArray(messages); 5 | 6 | const randomMsg = () => { 7 | if (!messagesRandom.length) messagesRandom = shuffleArray(messages); 8 | return messagesRandom.shift(); 9 | }; 10 | 11 | // Durstenfeld shuffle algorithm 12 | function shuffleArray(array) { 13 | const arr = [...array]; 14 | for (let i = arr.length - 1; i > 0; i--) { 15 | let j = Math.floor(Math.random() * (i + 1)); 16 | [arr[i], arr[j]] = [arr[j], arr[i]]; 17 | } 18 | return arr; 19 | } 20 | 21 | module.exports = randomMsg; 22 | --------------------------------------------------------------------------------