├── bot ├── data │ ├── project-exmaple.txt │ ├── faq-example.json │ ├── commands-example.json │ └── quotes-example.json ├── .env-example ├── initializer.py ├── logger.py ├── deepthonk.py └── commands.py ├── .github └── FUNDING.yml ├── .gitignore ├── Pipfile ├── LICENSE └── README.md /bot/data/project-exmaple.txt: -------------------------------------------------------------------------------- 1 | Dummy project info data™. -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | ko_fi: ninjabunny9000 2 | custom: https://streamlabs.com/ninjabunny9000/tip 3 | -------------------------------------------------------------------------------- /bot/data/faq-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor": "teh streamer is using VSCode because it's the bestest!", 3 | "keyboard": "browns." 4 | } -------------------------------------------------------------------------------- /bot/.env-example: -------------------------------------------------------------------------------- 1 | # Secrets go here 2 | TWITCH_BOT_NICK= 3 | TWITCH_TOKEN=oauth: 4 | TWITCH_CLIENT_ID= 5 | TWITCH_PREFIX=! # default is ! 6 | TWITCH_CHANNEL= 7 | TWITCH_TEAM= 8 | STREAMLABS_KEY= 9 | BOT_SERVER_KEY= # More info in /server/README.md 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | *.env 3 | *secret*.* 4 | **/node_modules/** 5 | server/static/sfx/hooks/** 6 | server/error_log.txt 7 | 8 | bot/error_log.txt 9 | 10 | bot/__pycache__/ 11 | 12 | .idea/ 13 | 14 | *.lock 15 | 16 | bot/OLDBOTSTUFF/ 17 | 18 | serverOLD/ 19 | 20 | bot/data/commands.json 21 | 22 | bot/data/faq.json 23 | 24 | bot/data/project.txt 25 | 26 | bot/data/quotes.json 27 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | twitchio = "*" 8 | pygame = "*" 9 | python-socketio = "*" 10 | phue = "*" 11 | rgbxy = "*" 12 | requests = "*" 13 | flask = "*" 14 | pyaml = "*" 15 | flask-socketio = "*" 16 | 17 | [dev-packages] 18 | pylama = "*" 19 | rope = "*" 20 | 21 | [requires] 22 | python_version = "3.6" 23 | -------------------------------------------------------------------------------- /bot/data/commands-example.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "command1", 4 | "level": 0, 5 | "aliases": [ 6 | "cmd1" 7 | ], 8 | "response": "cmd 1 test passed!" 9 | }, 10 | { 11 | "name": "command2", 12 | "level": 0, 13 | "aliases": [ 14 | "cmd2" 15 | ], 16 | "response": "cmd 2 test passed!" 17 | } 18 | ] -------------------------------------------------------------------------------- /bot/initializer.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | class Config: 4 | 5 | def __init__(self): 6 | self.bot_nick = os.environ['TWITCH_BOT_NICK'] 7 | self.token = os.environ['TWITCH_TOKEN'] 8 | self.client_id = os.environ['TWITCH_CLIENT_ID'] 9 | self.prefix = os.environ['TWITCH_PREFIX'] 10 | self.channel = os.environ['TWITCH_CHANNEL'] 11 | self.team = os.environ['TWITCH_TEAM'] 12 | 13 | 14 | class Initializer: 15 | def __init__(self): 16 | self.cfg = Config() 17 | 18 | 19 | -------------------------------------------------------------------------------- /bot/data/quotes-example.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "author": "Jigokuniku", 4 | "quote": "TBH, some best practices are really only ever learned by getting annoyed at the bad practice" 5 | }, 6 | { 7 | "author": "NinjaBunny9000", 8 | "quote": "JUST 👏 BECAUSE 👏 IT'S 👏 EATABLE 👏 DOESN'T 👏 MEAN 👏 IT'S 👏 EDIBLE 👏" 9 | }, 10 | { 11 | "author": "J0nnyclueless", 12 | "quote": "I sometimes get kernel panic attacks" 13 | }, 14 | { 15 | "author": "Sockelo", 16 | "quote": "Forgive them, for they know not what they compile." 17 | } 18 | ] -------------------------------------------------------------------------------- /bot/logger.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | # create logger objectw 4 | log = logging.getLogger('deepthonk') 5 | log.setLevel(logging.DEBUG) 6 | 7 | # create file handler 8 | fh = logging.FileHandler('error_log.txt') 9 | fh.setLevel(logging.WARNING) 10 | 11 | # create console handler 12 | ch = logging.StreamHandler() 13 | ch.setLevel(logging.DEBUG) 14 | 15 | # format 16 | fh_formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(message)s') 17 | ch_formatter = logging.Formatter('[%(levelname)s] %(message)s') 18 | fh.setFormatter(fh_formatter) 19 | ch.setFormatter(ch_formatter) 20 | 21 | # add/register them with the logger obj 22 | log.addHandler(fh) 23 | log.addHandler(ch) 24 | -------------------------------------------------------------------------------- /bot/deepthonk.py: -------------------------------------------------------------------------------- 1 | 2 | # bot modules 3 | # import utils 4 | # import integrations 5 | # import commandsBUTNOT 6 | # import events 7 | # import server_interface 8 | 9 | from twitchio.ext import commands 10 | 11 | # local modules 12 | from initializer import Initializer 13 | from commands import CommandManager 14 | from logger import log 15 | 16 | init = Initializer() 17 | bot = commands.Bot( 18 | irc_token=init.cfg.token, 19 | client_id=init.cfg.client_id, 20 | nick=init.cfg.bot_nick, 21 | prefix=init.cfg.prefix, 22 | initial_channels=[init.cfg.channel] 23 | ) 24 | 25 | # generate and register all the commands from the json file 26 | cmd = CommandManager(bot) 27 | cmd.importCommands('data/commands.json') 28 | cmd.importFAQ('data/faq.json') 29 | log.debug('Starting the bot..') 30 | bot.run() 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 NinjaBunny9000 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. -------------------------------------------------------------------------------- /bot/commands.py: -------------------------------------------------------------------------------- 1 | import json 2 | from logger import log 3 | 4 | 5 | class SimpleCommand: 6 | 7 | def __init__(self, bot, cmd): 8 | @bot.command(name=cmd['name'], aliases=cmd['aliases']) 9 | async def importedCommand(ctx): 10 | if CommandManager.checkPermissions(ctx): 11 | pass 12 | response = cmd['response'] 13 | await ctx.send(response) 14 | 15 | class FAQ: 16 | 17 | class CommandManager: 18 | 19 | def __init__(self, bot): 20 | self.bot = bot 21 | self.list = [] 22 | 23 | def importCommands(self, file): 24 | self.registerCommands(self.getCommands(file)) 25 | 26 | def importFAQ(self, file): 27 | self.registerFAQ(self.getFAQ(file)) 28 | 29 | @staticmethod 30 | def getFAQ(file): 31 | with open(file) as json_file: 32 | return json.load(json_file) 33 | 34 | def registerFAQ(self, faqs): 35 | for faq in faqs: 36 | SimpleCommand(self.bot, faq) 37 | 38 | @staticmethod 39 | def getCommands(file): 40 | with open(file) as json_file: 41 | return json.load(json_file)['commands'] 42 | 43 | def registerCommands(self, cmds): 44 | for cmd in cmds: 45 | SimpleCommand(self.bot, cmd) 46 | self.list.append(cmd['name']) 47 | log.debug(f"Commands imported from JSON data: {self.list}") 48 | 49 | # todo 50 | def checkPermissions(ctx): 51 | pass 52 | 53 | # todo 54 | def generateCmdList(self): 55 | pass 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # DeepThonk: A Python bot for Twitch 3 | 4 | An open-source Python bot framework for Twitch - *currently in development*. In it's current version, it's a rewrite of the "OG" version we build on-stream in 2018. Some features are missing until we can port them over to the rewrite. 5 | 6 | Deepthonk was [Live Coded on Twitch](http://bit.ly/nb9kTwitch), with the help of an amazing community! 7 | 8 | ## Feature Overview 9 | 10 | ### In Production 11 | - A simple call & response command system 12 | - Text-to-speech (TTS) system 13 | - SFX/hooks commands system (using browser source in OBS) 14 | - Shoutouts to subscribers & team members (chat & tts) 15 | - Basic event reactions (raids) 16 | 17 | ### In Development 18 | - Random sound effects based on trigger words in chat 19 | - OBS scene & source control (done but not ported) 20 | - Philips HUE integration (done but not ported) 21 | 22 | ## Integrations 23 | 24 | - Twitch IRC chat & API 25 | - Streamlabs alerts _(partial functionality, still working on remaining alerts)_ 26 | 27 | ## App Organization & Structure 28 | 29 | 30 |
31 | deepthonk/ 32 | ├─ bot/ ..................... App that integrates with Twitch chat 33 | │ ├─ commands.py ........... Chat commands live here 34 | │ ├─ content.py ............ (moving to JSON soon) 35 | │ ├─ debug-example.py ...... Rename to debug.py for command testing 36 | │ ├─ deepthonk.py .......... Starts the chat bot 37 | │ ├─ events.py ............. Handles chat & bot events 38 | │ ├─ server_interface.py ... Interfaces with API/WS server 39 | │ ├─ config/ ............... Imports secrets from env vars 40 | │ ├─ integrations/ 41 | │ │ ├─ streamlabs/ ........ WS wrapper 42 | │ │ └─ twitch/ ............ API wrapper 43 | │ ├─ utils/ ................ Logger, etc 44 | │ └─ .env-example/ .......... Rename to .env & add secrets 45 | └─ server/ .................. App that creates a local API/WS server for the bot 46 | ├─ conf.py ............... Imports secrets 47 | ├─ server.py ............. Starts the local server 48 | ├─ sfx.py ................ Generates sfx commands from files 49 | ├─ static/ 50 | │ ├─ sfx/ 51 | │ │ ├─ hooks/ .......... Put SFX files here! 52 | │ │ └─ other/ .......... 53 | │ │ ├─ raid.mp3 ..... Plays when a raid occurs 54 | │ │ └─ chirp.mp3 .... Plays before tts messages 55 | ├─ templates/ 56 | │ ├─ home.html 57 | │ └─ obs-source.html .... Browser source html template 58 | ├─ utils/ ................ Logger, etc. 59 | └─ .env-example/ .......... Rename to .env & add secrets 60 |61 | 62 | 63 | ## Configure the bot 64 | 65 | To run the bot, you need to run both the local server and the bot application simultaniously. The bot app handles integration with the chat, and the server handles generating the OBS Browser Source for the sound effects and other critical functionality. 66 | 67 | 68 | ### Prerequisites 69 | - Python 3.6 or 3.7 -- [Windows](https://www.python.org/downloads/release/python-375/) // [Linux](https://www.python.org/downloads/release/python-375/) // [OS X](https://www.python.org/downloads/release/python-375/) 70 | - PIPENV ->`pip install pipenv` or `python -m pip install pipenv` (sometimes python3) 71 | - An `oauth token` & `client-id` for a Twitch account for your bot 72 | - This [Twitch Bot Tutorial](https://dev.to/ninjabunny9000/let-s-make-a-twitch-bot-with-python-2nd8) I made on [dev.to](https://dev.to/ninjabunny9000) explains how to obtain your tokens and ID 73 | 74 | 75 | ### Create a cozy home for the bot to live in 76 | 77 | Virtual environments require a couple extra steps to set up but make developing Python apps a breeze. I suggest using [PIPENV](https://github.com/pypa/pipenv) which marries pip and venv into a single package. 78 | 79 | >You'll need to do the following steps for both the bot and the server directories 80 | 81 | - In the console, navigate to your working directory 82 | - Then run ⇒ `pipenv install` 83 | 84 | **Repeat this for both `/bot/` and `/server/` directories** 85 | 86 | ## Adding secrets & settings 87 | 88 | Rename the `.env-example` files to `.env` in the `/bot/` and `/server/` directories. 89 | 90 | Your secrets will live inside these files and imported into the venv when you start the apps. Add the oauth token and client-id from above go after the `=` in the files. Fill in the other variables as well. 91 | 92 | ```bash 93 | # /server/ 94 | SECRET_KEY= # make up your own key here 95 | ``` 96 | 97 | ```bash 98 | # /bot/ 99 | TWITCH_BOT_NICK= 100 | TWITCH_TOKEN=oauth: 101 | TWITCH_CLIENT_ID= 102 | TWITCH_PREFIX=! # default is ! 103 | TWITCH_CHANNEL= 104 | TWITCH_TEAM= 105 | STREAMLABS_KEY= 106 | BOT_SERVER_KEY= # use key from /server/.env 107 | ``` 108 | 109 | ## Booting up the bot 110 | 111 | **Start the server:** 112 | - Navigate to the server's working directory (`/server/`) 113 | - In the console, run ⇒ `pipenv run python server.py` 114 | 115 | **Start the bot:** 116 | - Navigate to the server's working directory (`/bot/`) 117 | - In the console, run ⇒ `pipenv run python deepthonk.py` 118 | 119 | Once the bot goes online, it should greet you in both console and the configured channel's chat. 120 | 121 |  122 | 123 | ### Test command 124 | 125 | The bot comes pre-loaded with a `!test` command, if you want to try it in chat. 126 | 127 |  128 | 129 | ## Adding custom commands 130 | 131 | Create new commands at the top of `commands.py`. Any command you make needs to follow this format when defining them.. 132 | 133 | - Decorated with `@bot.command(name='whatever')` 134 | - Be asynchronous functions with names that match the `name` variable in the decorator 135 | - Pass the message context in through the function 136 | 137 | How the function works and what it does is all up to you. For this example, we'll create a command called `!test` that says `test passed!` in chat when we call it. 138 | 139 | ```python 140 | # bot.py, below event_message function 141 | @bot.command(name='test') 142 | async def test(ctx): 143 | await ctx.send('test passed!') 144 | ``` 145 | 146 | One key ingredient to making any command work is for them to be handled in the `event_message` function. If you accidently delete the following line, and commands don't work anymore, that's what's up. 147 | 148 | 149 | ```python 150 | # events.py, inside event_message 151 | await bot.handle_commands(ctx) 152 | ``` 153 | 154 | ## Adding SFX 155 | 156 | Place SFX inside `server/static/hooks/`. 157 | 158 | Mp3s, WAVs, and Ogg files inside that folder are loaded as sfx commands when the server starts. 159 | 160 | ### Setting up in OBS 161 | 162 | Inside OBS, create a new browser source with the url of `http://localhost:6969/obs-source/`. You should be able to hear sfx now. 163 | 164 | If you make any changes to the server and add or remove SFX, you'll need to refresh the browser source's cache. 165 | 166 | 167 | ## Events 168 | 169 | There are 2 events that are used in the code right now.. `on_ready` and `on_event`. 170 | 171 | ### on_ready 172 | This executes when the bot comes online, and will print out to the console. 173 | ```python 174 | @bot.event 175 | async def event_ready(): 176 | print(f'Ready | {bot.nick}') 177 | ``` 178 | 179 | ### event_message 180 | This function executes once per event (or message) sent. You can make it handle input from chat that *aren't* necesarily commands, and fun stuff like that. 181 | 182 | ```python 183 | @bot.event 184 | async def event_message(message): 185 | print(message.content) 186 | await bot.handle_commands(message) 187 | ``` 188 | You can find more info in [TwitchIO's official documentation](https://twitchio.readthedocs.io/en/rewrite/twitchio.html). 189 | 190 | 191 | ## Advanced topics 192 | 193 | I'm working on a blog and tutorial series going over more advanced topics in Python Twitch bot building and the TwitchIO library on [dev.to](http://bit.ly/nb9kdevto) 194 | 195 | [Let's make a Twitch bot with Python!](http://bit.ly/twitchbot101) 196 | 197 | 198 | ### Follow Along & Contribute! 199 | Watch & help develop the bot during live streams on [Twitch](https://twitch.tv/ninjabunny9000)! PR's are welcome but you're encouraged to at least spend a little time on stream to get an idea about Deepthonk's "personality" and the direction we're taking the bot first. 200 | 201 | If you have any questions or just wanna brainstorm some neat ideas, [live-streams](https://twitch.tv/ninjabunny9000) are the best time for that! 202 | 203 | 204 | ## Trouble-shooting 205 | 206 | I'm still working on building out this section of the docs. If you have any questions or issues, you have a couple of options to get in touch with me... 207 | 208 | - [Create an issue](https://github.com/NinjaBunny9000/DeepThonk/issues/new) in the repo (slow but well-documented) 209 | - [Twitter](http://bit.ly/nb9kTwitter) (pretty fast) 210 | - [Discord](https://discord.gg/UEUFAUV), in the `#dev` channel (faster) 211 | - [Twitch Streams](http://bit.ly/nb9kTwitch), (schedule pinned on Twitter) (fastest) 212 | 213 | ## Contributors & Licenses 214 | 215 | [NinjaBunny9000](https://github.com/NinjaBunny9000) - _Author, Project Manager_ - [Twitch](https://twitch.tv/ninjabunny9000) // [Twitter](https://twitter.com/ninjabunny9000) 216 | 217 | [](https://ko-fi.com/Y8Y013678) 218 | 219 | ### Special Thanks 220 | Literally everyone that's helped even the smallest bit during streams. Thank you so much, y'all! 221 | --------------------------------------------------------------------------------