├── .gitignore ├── LICENSE ├── README.md ├── auth.py ├── config.ini.sample ├── config.json ├── config.json.sample ├── getLiveChatID.py └── main.py /.gitignore: -------------------------------------------------------------------------------- 1 | OAuthCredentials.json 2 | config.ini 3 | authcode.txt 4 | token.txt 5 | client_secrets.json 6 | 7 | # Byte-compiled / optimized / DLL files 8 | __pycache__/ 9 | *.py[cod] 10 | *$py.class 11 | 12 | # C extensions 13 | *.so 14 | 15 | # Distribution / packaging 16 | .Python 17 | env/ 18 | build/ 19 | develop-eggs/ 20 | dist/ 21 | downloads/ 22 | eggs/ 23 | .eggs/ 24 | lib/ 25 | lib64/ 26 | parts/ 27 | sdist/ 28 | var/ 29 | *.egg-info/ 30 | .installed.cfg 31 | *.egg 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *,cover 52 | .hypothesis/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | 62 | # Flask stuff: 63 | instance/ 64 | .webassets-cache 65 | 66 | # Scrapy stuff: 67 | .scrapy 68 | 69 | # Sphinx documentation 70 | docs/_build/ 71 | 72 | # PyBuilder 73 | target/ 74 | 75 | # IPython Notebook 76 | .ipynb_checkpoints 77 | 78 | # pyenv 79 | .python-version 80 | 81 | # celery beat schedule file 82 | celerybeat-schedule 83 | 84 | # dotenv 85 | .env 86 | 87 | # virtualenv 88 | venv/ 89 | ENV/ 90 | 91 | # Spyder project settings 92 | .spyderproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Josh 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 | # CircBot 2 | YouTube LiveStream Chatbot 3 | 4 | # Quickstart 5 | 1. Make sure Python 2.7 with pip is installed (Python 3 should work but hasn't been tested) 6 | 2. pip install configparser oauth2client httplib2 requests 7 | 2. Make a new project with the [Google API Console](https://console.developers.google.com/apis/) and enable the Youtube Data API. 8 | 3. Add OAuth API credentials, download the JSON file and save it as client_secrets.json in the project folder 9 | 4. Run auth.py ("python auth.py"), approve the OAuth request in your webbrowser, and copy the auth code to the console 10 | 5. clear the console ("cls" in Windows) 11 | 6. Start the stream so that the bot can fetch the live chat identifier 12 | 7. Run main.py ("python main.py") 13 | 14 | 15 | # Related Documentaion and Links 16 | https://developers.google.com/youtube/v3/live/docs/liveChatMessages 17 | https://developers.google.com/youtube/v3/live/docs/liveBroadcasts/list 18 | https://developers.google.com/youtube/v3/live/getting-started 19 | https://developers.google.com/api-client-library/python/auth/installed-app 20 | 21 | http://docs.python-requests.org/en/latest/user/quickstart/ 22 | -------------------------------------------------------------------------------- /auth.py: -------------------------------------------------------------------------------- 1 | #This script is licensed under the Apace 2.0 License (http://www.apache.org/licenses/LICENSE-2.0) 2 | #This script is a derivative work of the script at https://developers.google.com/api-client-library/python/auth/installed-app 3 | 4 | import json 5 | import webbrowser 6 | import configparser 7 | import httplib2 8 | from oauth2client import client 9 | 10 | flow = client.flow_from_clientsecrets( 11 | 'client_secrets.json', 12 | scope='https://www.googleapis.com/auth/youtube.readonly https://www.googleapis.com/auth/youtube https://www.googleapis.com/auth/youtube.force-ssl', 13 | redirect_uri='urn:ietf:wg:oauth:2.0:oob') 14 | 15 | auth_uri = flow.step1_get_authorize_url() 16 | webbrowser.open(auth_uri) 17 | 18 | print "Opening web browser to request auth code" 19 | auth_code = raw_input('Enter the auth code: ') 20 | 21 | credentials = flow.step2_exchange(auth_code) 22 | http_auth = credentials.authorize(httplib2.Http()) 23 | 24 | outFile = open("OAuthCredentials.json","w") 25 | outFile.write(str(credentials.to_json())) 26 | outFile.close() 27 | -------------------------------------------------------------------------------- /config.ini.sample: -------------------------------------------------------------------------------- 1 | [Settings] 2 | debug=0 3 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "debug":0, 3 | "echoMode":"whitelist", 4 | "whitelistFilter":["[Q]","[q]","{Q}","{q}"] 5 | } 6 | -------------------------------------------------------------------------------- /config.json.sample: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/compujo/CircBot/4cb3080e1a3028245f9b61cbf7823ee32e7d95a2/config.json.sample -------------------------------------------------------------------------------- /getLiveChatID.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os.path 3 | import json 4 | import time 5 | import configparser 6 | import requests 7 | 8 | #OAuth2 libs 9 | import httplib2 10 | from oauth2client import client 11 | 12 | def get_livechat_id(): 13 | # Authenticate 14 | 15 | if (not os.path.isfile("OAuthCredentials.json")): 16 | import auth 17 | 18 | credentialsFile = open("OAuthCredentials.json","r") 19 | credentialsJSON = credentialsFile.read() 20 | 21 | credentials = client.OAuth2Credentials.from_json(credentialsJSON) 22 | 23 | token_obj = credentials.get_access_token() 24 | token_str = str(token_obj.access_token) 25 | 26 | url = 'https://content.googleapis.com/youtube/v3/liveBroadcasts?broadcastStatus=active&broadcastType=all&part=id%2Csnippet%2CcontentDetails' 27 | 28 | headers = { "Authorization": "Bearer "+token_str } 29 | 30 | r = requests.get(url, headers=headers) 31 | 32 | if (r.status_code == 200): 33 | resp = r.json() 34 | if (len(resp["items"]) <= 0): 35 | return False 36 | else: 37 | # Success, get the id and save 38 | 39 | # Should only be 1 item unless YT adds multiple livestreams, then we'll assume it's the first for now 40 | streamMeta = resp["items"][0]["snippet"] 41 | liveChatID = streamMeta["liveChatId"] 42 | return liveChatID 43 | else: 44 | print("Unrecognized error:\n") 45 | resp = r.json() 46 | print(json.dumps(resp, indent=4, sort_keys=True)) 47 | 48 | if __name__ == '__main__': 49 | liveChatID = get_livechat_id() 50 | if (liveChatID == False): 51 | print("No livestream found :(") 52 | sys.exit(1) 53 | print liveChatID -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os.path 3 | import json 4 | import time 5 | import configparser 6 | import requests 7 | import getLiveChatID 8 | 9 | #OAuth2 libs 10 | import httplib2 11 | from oauth2client import client 12 | 13 | VERSION = "0.3.2" 14 | PYTHONIOENCODING="UTF-8" 15 | 16 | config = configparser.ConfigParser() 17 | config.read('config.ini') 18 | 19 | debug = int(config["Settings"]["debug"]) 20 | 21 | with open("config.json") as jsonFile: 22 | configJSON = json.load(jsonFile) 23 | 24 | # Message handler 25 | def handle_msg(msg): 26 | if (configJSON["echoMode"] == "whitelist"): 27 | wlWords = configJSON["whitelistFilter"] 28 | for word in wlWords: 29 | if word in msg["snippet"]["displayMessage"]: 30 | try: 31 | print(('<'+msg["authorDetails"]["displayName"]+'> '+msg["snippet"]["displayMessage"]).encode('utf-8',"ignore")) 32 | except UnicodeEncodeError: 33 | print('Couldn\'t display a message, skipped.') 34 | return 35 | elif (configJSON["echoMode"] == "all"): 36 | try: 37 | print(('<'+msg["authorDetails"]["displayName"]+'> '+msg["snippet"]["displayMessage"]).encode('utf-8', "ignore")) 38 | except UnicodeEncodeError: 39 | print('Couldn\'t display a message, skipped.') 40 | return 41 | 42 | # Authenticate 43 | 44 | if (not os.path.isfile("OAuthCredentials.json")): 45 | import auth 46 | os.system('cls') 47 | 48 | os.system('cls') 49 | print("Welcome to Circbot v"+VERSION+"!") 50 | 51 | credentialsFile = open("OAuthCredentials.json","r") 52 | credentialsJSON = credentialsFile.read() 53 | 54 | credentials = client.OAuth2Credentials.from_json(credentialsJSON) 55 | 56 | token_obj = credentials.get_access_token() 57 | token_str = str(token_obj.access_token) 58 | 59 | # End of authentication 60 | 61 | liveChatID = getLiveChatID.get_livechat_id() 62 | if (liveChatID == False): 63 | print("No livestream found :(") 64 | sys.exit(1) 65 | 66 | nextPageToken = '' 67 | 68 | while (True): 69 | 70 | # Make sure access token is valid before request 71 | if (credentials.access_token_expired): 72 | # Access token expired, get a new one 73 | token_obj = credentials.get_access_token() #get_access_token() should refresh the token automatically 74 | token_str = str(token_obj.access_token) 75 | 76 | url = 'https://content.googleapis.com/youtube/v3/liveChat/messages?liveChatId='+liveChatID+'&part=snippet,authorDetails&pageToken='+nextPageToken 77 | 78 | headers = { "Authorization": "Bearer "+token_str } 79 | 80 | r = requests.get(url, headers=headers) 81 | 82 | if (r.status_code == 200): 83 | resp = r.json() 84 | if (debug >= 2): 85 | print json.dumps(resp, indent=4, sort_keys=True) 86 | 87 | nextPageToken = resp["nextPageToken"] 88 | 89 | msgs = resp["items"] 90 | 91 | for msg in msgs: 92 | #Message handling 93 | handle_msg(msg) 94 | 95 | delay_ms = resp['pollingIntervalMillis'] 96 | delay = float(float(delay_ms)/1000) 97 | 98 | elif (r.status_code == 401): 99 | #Unauthorized 100 | delay = 10 101 | 102 | if (credentials.access_token_expired): 103 | # Access token expired, get a new one 104 | if (debug >= 1): 105 | print "Access token expired, obtaining a new one" 106 | 107 | token_obj = credentials.get_access_token() #get_access_token() should refresh the token automatically 108 | token_str = str(token_obj.access_token) 109 | else: 110 | print "Error: Unauthorized. Trying again in 30 seconds... (ctrl+c to force quit)" 111 | resp = r.json() 112 | if (debug >= 1): 113 | print json.dumps(resp, indent=4, sort_keys=True) 114 | 115 | delay = 30 116 | 117 | else: 118 | print("Unrecognized error:\n") 119 | resp = r.json() 120 | print(json.dumps(resp, indent=4, sort_keys=True)) 121 | delay = 30 122 | 123 | time.sleep(delay) 124 | --------------------------------------------------------------------------------