├── add_or_remove_from_startup.vbs ├── launch headless.vbs ├── main.py ├── readme.md ├── server.py ├── settings.py ├── startup.py ├── static ├── script.js └── style.css ├── stats.json └── templates └── index.html /add_or_remove_from_startup.vbs: -------------------------------------------------------------------------------- 1 | Dim fso, file 2 | Set fso = CreateObject("Scripting.FileSystemObject") 3 | file = "startup.py" 4 | 5 | If fso.FileExists(file) Then 6 | Set oShell = CreateObject ("Wscript.Shell") 7 | Dim strArgs 8 | strArgs = "python startup.py" 9 | oShell.Run strArgs, 1, true 10 | Else 11 | MsgBox("Unable to find startup.py, (make sure you're running the this file from the same directory as startup.py)") 12 | End If -------------------------------------------------------------------------------- /launch headless.vbs: -------------------------------------------------------------------------------- 1 | Dim fso, file 2 | Set fso = CreateObject("Scripting.FileSystemObject") 3 | file = "main.py" 4 | 5 | If fso.FileExists(file) Then 6 | Set oShell = CreateObject ("Wscript.Shell") 7 | Dim strArgs 8 | strArgs = "python main.py" 9 | oShell.Run strArgs, 0, false 10 | 11 | msgbox("Successfully started Auto-OWL-Watcher, running in background now :)") 12 | Else 13 | MsgBox("Unable to find main.py, (make sure you're running the this file from the same directory as main.py)") 14 | End If -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import google.auth 2 | from googleapiclient.discovery import build 3 | import time, requests, os, json, logging, datetime 4 | from selenium import webdriver 5 | from selenium.webdriver.chrome.options import Options 6 | from selenium.webdriver.common.by import By 7 | from settings import * 8 | from threading import Thread 9 | 10 | #made by github.com/ingobeans 11 | 12 | 13 | def run_server(): 14 | try: 15 | import server 16 | except Exception as e: 17 | with open("error server crash at " + datetime.datetime.now().strftime("%m-%d-%H-%M"), "w") as f: 18 | f.write(str(e)) 19 | 20 | thread = Thread(target=run_server) 21 | thread.start() 22 | time.sleep(1) 23 | 24 | stats = {} 25 | 26 | with open("stats.json","r") as f: 27 | stats = json.load(f) 28 | 29 | all_time_time_spent = stats["time_watched_all_time"] 30 | 31 | owl_id = "UCiAInBL9kUzz1XRxk66v-gw" 32 | #owl_id = "UCSJ4gkVC6NrvII8umztf0Ow" #for testing purposes 33 | 34 | 35 | #use the default chrome data dir as selenium user-data-dir (this is so that you are logged in to your youtube account saved in chrome) 36 | chrome_data_path = os.getenv('LOCALAPPDATA') + "\\Google\\Chrome\\User Data" 37 | 38 | 39 | logging.getLogger("selenium.webdriver.remote.remote_connection").disabled = True #disable selenium logging 40 | 41 | 42 | def write_to_stats(new_stats): 43 | with open("stats.json","w") as f: 44 | json.dump(new_stats,f) 45 | 46 | 47 | def log_error(msg): 48 | requests.get("http://localhost:7676/api/throw_error/",headers={"msg":msg}) 49 | 50 | def get_live_stream_url(channel_id): 51 | try: 52 | youtube = build('youtube', 'v3', developerKey=youtube_api_key) 53 | request = youtube.search().list( 54 | part='id', 55 | channelId=channel_id, 56 | eventType='live', 57 | type='video' 58 | ) 59 | response = request.execute() 60 | if len(response['items']) > 0: 61 | video_id = response['items'][0]['id']['videoId'] 62 | return f'https://www.youtube.com/watch?v={video_id}' 63 | else: 64 | return None 65 | except Exception as e: 66 | err = str(e) 67 | if "API key not valid. Please pass a valid API key" in err: 68 | err = "Invalid youtube API key. Please check your API key, and try again." 69 | log_error(err) 70 | quit() 71 | 72 | 73 | has_streamed = False 74 | previous_stream = None 75 | time_watched = 0 76 | 77 | def send_status(status): 78 | requests.get("http://localhost:7676/api/set_status/",headers={"status":status}) 79 | 80 | def format_time(minutes): 81 | if minutes < 60: 82 | time_watched_fancy = f"{minutes} minutes" 83 | elif minutes/60/24 >= 7: 84 | time_watched_fancy = f"{int(minutes/60/24/7)} week{'s' if int(minutes/60/24/7) > 1 else ''}" 85 | elif minutes/60 >= 24: 86 | time_watched_fancy = f"{int(minutes/60/24)} day{'s' if int(minutes/60/24) > 1 else ''}" 87 | elif minutes >= 60: 88 | time_watched_fancy = f"{int(minutes/60)} hour{'s' if int(minutes/60) > 1 else ''}" 89 | 90 | return time_watched_fancy 91 | 92 | send_status(f"launching|0|0|{int(all_time_time_spent/60*5)}|{format_time(all_time_time_spent)}") 93 | 94 | while True: 95 | url = get_live_stream_url(owl_id) 96 | if not url == None: 97 | if not has_streamed: 98 | #if this is the first time that OWL has started streaming during the current session, launch up selenium (No need to launch selenium until they start streaming to save ram) 99 | chrome_options = Options() 100 | chrome_options.add_argument("--user-data-dir="+chrome_data_path) 101 | chrome_options.add_argument("--headless") 102 | driver = webdriver.Chrome(options=chrome_options) 103 | has_streamed = True 104 | driver.get(url) 105 | time.sleep(5) 106 | try: 107 | b = driver.find_element(By.CLASS_NAME, "ytp-large-play-button") 108 | b.click() 109 | except: 110 | pass 111 | 112 | if previous_stream == None or url != previous_stream: 113 | previous_stream = url 114 | 115 | if url != driver.current_url: 116 | log_error(f"**Url should be: {url} ,but is: {driver.current_url}**") 117 | 118 | time_watched_fancy = format_time(time_watched) 119 | 120 | new_stats = {"time_watched_all_time": all_time_time_spent} 121 | all_time_tokens_earnt = int(all_time_time_spent / 60 * 5) 122 | estimated_tokens_earnt = int(time_watched / 60 * 5) 123 | write_to_stats(new_stats) 124 | send_status(f"{url}|{estimated_tokens_earnt}|{time_watched_fancy}|{all_time_tokens_earnt}|{format_time(all_time_time_spent)}") 125 | 126 | #reload because sometimes youtube flags as inactive and also as a failsafe if something in youtube stream breaks 127 | time.sleep(30*60) 128 | time_watched+=30 129 | all_time_time_spent+=30 130 | 131 | else: 132 | send_status(f"0|0|0|{int(all_time_time_spent/60*5)}|{format_time(all_time_time_spent)}") 133 | time_watched = 0 134 | #check every 15 mins if OWL is streaming 135 | time.sleep(15*60) 136 | 137 | 138 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Auto Overwatch League Watcher 2 | 3 | Automatically watch Overwatch League youtube streams to generate Overwatch League Tokens in Overwatch. It checks every few minutes if OWL is streaming, if so it opens a hidden selenium browser logged in via chrome, to watch the stream. 4 | 5 | This branch displays statistics and status at http://localhost:7676. 6 | If you instead want it to send discord messages periodically (or whenever it starts watching stream), check out the other branch (https://github.com/ingobeans/Auto-OWL-Watcher/tree/discord-webhook-logging) 7 | 8 | 9 | ## **------------------- Setup instructions -------------------** 10 | 11 | 1. Download and extract this project to a folder. 12 | 13 | 2. Install Python. 14 | 15 | 3. Run this command in cmd: `pip install google-api-python-client google-auth requests selenium flask` 16 | 17 | 4. Go into settings.py and change youtube_api_key to your Youtube API Key. (How to get API key: https://blog.hubspot.com/website/how-to-get-youtube-api-key) 18 | 19 | 5. You can now run the script by running "launch headless.vbs". 20 | 21 | 6. While the script is running, you can goto http://localhost:7676 to see statistics, status, errors, etc. 22 | 23 | 24 | ## **------------------- How it works -------------------** 25 | 26 | Auto OWL Watcher works by checking if OWL youtube channel is streaming every few minutes, if so it opens a headless (hidden) selenium window that watches the stream. 27 | It uses your chrome data to connect to your youtube channel. This way you receive OWL tokens (as long as you are logged in to youtube in chrome and also has connected your youtube account to battle.net for rewards) 28 | 29 | 30 | ## **------------------- Troubleshooting -------------------** 31 | 32 | "No rewards getting received" - Could be due to not being logged in to youtube in google chrome, or not connected youtube channel to battle.net (like this: https://dotesports.com/overwatch/news/how-to-earn-overwatch-league-tokens) 33 | 34 | 35 | "Opening launch headless.vbs just shows an error popup saying that the system cannot find the file specified" - Make sure Python is installed, if it is, try restarting your PC, then making sure Python is added to path. 36 | 37 | 38 | For errors with script crashing on start, make sure that your youtube api token is correct 39 | 40 | Otherwise feel free to ask for help 41 | -------------------------------------------------------------------------------- /server.py: -------------------------------------------------------------------------------- 1 | from flask import Flask,render_template,request,jsonify 2 | 3 | app = Flask(__name__) 4 | #status format: <0 if not streaming, else URL>||