diff --git a/.dockerignore b/.dockerignore index 8e13e58..c471654 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,2 +1 @@ -config -Config \ No newline at end of file +/config/config.json \ No newline at end of file diff --git a/.gitignore b/.gitignore index ef1066e..c471654 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -/config \ No newline at end of file +/config/config.json \ No newline at end of file diff --git a/Readme.md b/Readme.md index 2e16e51..ffbcc16 100644 --- a/Readme.md +++ b/Readme.md @@ -1,42 +1,81 @@ # What it does This is a script i wrote so i can get notifications if certain channels go live on youtube, it check their live videos to a list of keywords and if it contains said keyword it posts a link to the video to a discord webhook. it automaticly calculates the time between polls to avoid getting rate limited. however the youtube api is very restrictive on quota's and at most you can poll every 15 minutes if you only check 1 channel -the code itself is heavily commented so go ham and make it your own +![Alt text](screenshot.png) + +**the script currently comes with 2 versions:** +- the lite version, wich just contains the script to check for livestreams and posts them to discord aswell. +- the full version, wich contains the script, the option to send log messages to a discord webhook for easy remote monitoring and a simple webserver that can be pinged to monitor uptime, aswell as simple remote post service that will send a http post to an adress of your chosing with a configurable timer with the json ```{'name': "bot_name", 'time': "unix timestamp of current time in utc"}``` all of this can be toggled on or off in the config -included are: -- a post script that sends a http post to a watchdog server of your choosing, wich wont happen if the url is left blank in the config -- a http webserver running to use for uptime monitoring by having an approachable url wich can be checked with something like uptime kuma -- remote logging capabilities with use of a discord webhook where debugging messages get posted, wich wont happen if the url is left blank in the config # how to use -1. install python on your system from the python website https://www.python.org/downloads/ -2. place all contents in a folder and make a "config" folder. -3. make a config.json and copy the text below into the file +1. install python on your system from the python website https://www.python.org/downloads/ if you plan on using the included batch files make sure to select ```intall to path``` during the installation +2. you will need a youtube data api key for the data api v3, you can get one on https://console.cloud.google.com/ it can be difficult to find the right menu's if your new to this. refer to a quick google search on how to +3. go to the config folder and copy the ```example_config.json``` or ```example_lite_config.json``` and rename it to ```config.json```. +4. adjust the appropriate config for your version +* you will need the actual channelID of the channel you want to monitor, not a handle like @youtube, plenty of tools out there that do it for you, just google ```youtube channel id finder``` + +lite: ``` { - "youtubeApiKey": "youtubedataAPIkey", - "webhookurl": "URL where the notification gets posted to", - "webhooklogurl": "optional URL to log output to a discord webhook", - "webhookmonitorurl": "optional URL to send simple post requests to a watchdog server in format {"name":"botname":"time":"UNIX timestamp"}", - "botname": "name to send to watchdog server OPTIONAL", - "posttimeout": "time between posts to watchdog OPTIONAL", - "wordlist": ["example1","example2","example3"], - "notificationmessage": "Example message to post to discord", - "channels": ["YoutubechannelID1","YoutubechannelID2"], - "hostname": "Adress for simple webserver", - "webport": "port for simple webserver" + "youtube_api_key": "YOUR_YOUTUBE_API_KEY", + "discord_webhook_url": "URL_TO_YOU_DISCORD_WEBHOOK_TO_POST_LINKS_TO", + "word_list": ["list", "of", "words", "to", "match"], + "ignore_list": ["list", "of", "words", "to", "ignore"], + "notification_message": "message to post with the link", + "channels": ["channelid1","channelid2"] } ``` -4. input the correct data and launch the script youtubelivebot.py -* leave optional entries blank if you do not intent to use them but do include them otherwise the script will throw an error -* you will need the actual channelID of the channel you want to monitor, not a handle like @youtube +full: +``` +{ + "youtube_api_key": "YOUR_YOUTUBE_API_KEY", + "discord_webhook_url": "URL_TO_YOU_DISCORD_WEBHOOK_TO_POST_LINKS_TO", + "word_list": ["list", "of", "words", "to", "match"], + "ignore_list": ["list", "of", "words", "to", "ignore"], + "notification_message": "message to post with the link", + "channels": ["channelid1","channelid2"], + "use_discord_logs": "true or false", + "discord_remote_log_url": "URL_TO_YOU_DISCORD_WEBHOOK_TO_POST_LOGS_TO", + "use_web_server": "true or false", + "web_server_url": "0.0.0.0", + "web_server_port": "8888", + "use_remote_post": "true or false", + "remote_http_server_url": "http://google.com", + "bot_name": "NAME_OF_YOUR_BOT_FOR_REMOTE_POST", + "post_interval": "time in minutes to post to remote post" +} +``` +5. launch the script +on windows: with the included batch files for your version +on linux: in a terminal with ```python youtubelivebot.py``` or ```python youtubelivebotlite.py``` Optionally a dockerfile is included wich can be used to build a docker image or use the one on my repository with the following code ``` docker run -dit --name youtubelivebot -v /path/to/config:/usr/src/app/config -p : keyboardmedic/youtubelivebot:latest ``` +# how it works +**main script** +- loads the config. +- launches the webserver and post server in seperate threads if selected in the config. +- calculates time between polls to be as much as possible within the given rate limit quota provided by the api (for 1 channel this is every 15 minutes, for 2 channels every 30 minutes and so forth) +- loops over the channels configured and checks if they have any live streams active +- if a livestream is found it is checked against the wordlist to see if the video title contains ANY of the words provided in the word_list +- if a match is found it checks if any of the words in the ignore_list are in the title, if ANY of those words are present in the title it will ignore the livestream and not post it. +- if NONE of the ignore_list words are present it will post the video to the configured webhook with the configured message +- it then waits for the calculated time and loops over the channels again + +**webserver** +- loads the config +- serves a website on the defined adress with the defined port with just a plain text message that says "hello i am a webserver" + +**remote post** +- loads the config +- sends a http post to the configured adress with the following json ```{'name': "bot_name as configured", 'time': "unix timestamp of current time in utc"}``` +- waits for the configured timeout and then sends a post again + # disclaimer Scripts are written by an amateur, use at your own risk \ No newline at end of file diff --git a/config/example_config.json b/config/example_config.json new file mode 100644 index 0000000..ef81f86 --- /dev/null +++ b/config/example_config.json @@ -0,0 +1,17 @@ +{ + "youtube_api_key": "YOUR_YOUTUBE_API_KEY", + "discord_webhook_url": "URL_TO_YOU_DISCORD_WEBHOOK_TO_POST_LINKS_TO", + "word_list": ["list", "of", "words", "to", "match"], + "ignore_list": ["list", "of", "words", "to", "ignore"], + "notification_message": "message to post with the link", + "channels": ["channelid1","channelid2"], + "use_discord_logs": "true or false", + "discord_remote_log_url": "URL_TO_YOU_DISCORD_WEBHOOK_TO_POST_LOGS_TO", + "use_web_server": "true or false", + "web_server_url": "0.0.0.0", + "web_server_port": "8888", + "use_remote_post": "true or false", + "remote_http_server_url": "http://google.com", + "bot_name": "NAME_OF_YOUR_BOT_FOR_REMOTE_POST", + "post_interval": "time in minutes to post to remote post" +} \ No newline at end of file diff --git a/config/example_lite_config.json b/config/example_lite_config.json new file mode 100644 index 0000000..f673432 --- /dev/null +++ b/config/example_lite_config.json @@ -0,0 +1,8 @@ +{ + "youtube_api_key": "YOUR_YOUTUBE_API_KEY", + "discord_webhook_url": "URL_TO_YOU_DISCORD_WEBHOOK_TO_POST_LINKS_TO", + "word_list": ["list", "of", "words", "to", "match"], + "ignore_list": ["list", "of", "words", "to", "ignore"], + "notification_message": "message to post with the link", + "channels": ["channelid1","channelid2"] +} \ No newline at end of file diff --git a/image.png b/image.png new file mode 100644 index 0000000..cbb6231 Binary files /dev/null and b/image.png differ diff --git a/screenshot.png b/screenshot.png new file mode 100644 index 0000000..5e39284 Binary files /dev/null and b/screenshot.png differ diff --git a/start lite.bat b/start lite.bat new file mode 100644 index 0000000..d1b8a83 --- /dev/null +++ b/start lite.bat @@ -0,0 +1,3 @@ +@echo off +python youtubelivebotlite.py +pause \ No newline at end of file diff --git a/start.bat b/start.bat index 2b127c9..030d40a 100644 --- a/start.bat +++ b/start.bat @@ -1,2 +1,3 @@ @echo off -python "youtubelivebot" youtubelivebot.py \ No newline at end of file +python youtubelivebot.py +pause \ No newline at end of file diff --git a/youtubelivebotlite.py b/youtubelivebotlite.py new file mode 100644 index 0000000..ba828d9 --- /dev/null +++ b/youtubelivebotlite.py @@ -0,0 +1,61 @@ +# imports needed libraries for code +import requests +import time +import json +import math +import threading +from os.path import exists +from subprocess import call + +#variables used in script +l= [] + +#pulls data from config +with open("config/config.json") as config: + config_json = json.load(config) + youtube_api_key = config_json["youtube_api_key"] + discord_webhook_url = config_json["discord_webhook_url"] + word_list = config_json["word_list"] + ignore_list = config_json["ignore_list"] + notification_message = config_json["notification_message"] + channels = config_json["channels"] +print("succesfully loaded config") + +# checks if name of video contains pursuit and if so posts video to webhook +while True: + try: + # calculates minimum time between api calls to avoid rate limiting + time_to_poll= math.ceil(1440 * len(channels) / 100) + time_to_sleep = time_to_poll * 60 + print(f"calculated time between polls is {str(time_to_poll)} minutes") + # loop checks all channels in config and searches for video titles matching defined keywords in config + for channel in channels: + print(f"polling channel {channel}") + r = requests.get('https://www.googleapis.com/youtube/v3/search?part=snippet&channelId=' + channel, '&eventType=live&type=video&key=' + youtube_api_key) + request = r + print(f"requested information for channel {channel} with response: {str(request)}") + # checks if get request was succesfull + if "200" in str(request): + yt_api_json = request.json() + try: + item_count = len(yt_api_json["items"]) + except: + item_count = 0 + if item_count > 0: + for item in yt_api_json["items"]: + # checks if videos found contain keywords and do not contain ignored words + if any(s in (item["snippet"]["title"].lower()) for s in word_list) and all(s not in (item["snippet"]["title"].lower()) for s in ignore_list): + video_id_to_send = item["id"]["videoId"] + if video_id_to_send not in l: + l.append(video_id_to_send) + print(f"found video matching criteria, posting video with id: {video_id_to_send}") + r = requests.post(discord_webhook_url, data={"content": notification_message + "https://www.youtube.com/watch?v=" + video_id_to_send,}) + else: + print(f"Live video found but it did not match the criteria for {channel}") + else: + print(f"no live videos found for channel {channel}") + print(f"waiting for {str(time_to_poll)} minutes") + time.sleep(time_to_sleep) + except Exception as e: + print(f"An exception occurred in main loop: {str(e)} waiting for 1 minute") + time.sleep(60) \ No newline at end of file