diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..3ba13e0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: false diff --git a/.github/ISSUE_TEMPLATE/issue_form.yml b/.github/ISSUE_TEMPLATE/issue_form.yml new file mode 100644 index 0000000..cb5e0a7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/issue_form.yml @@ -0,0 +1,48 @@ +name: Issue/Feature request +description: Submit your issue/feature request here +body: + - type: checkboxes + attributes: + label: Is there an existing issue for this? + description: Please search to see if an issue already exists for the bug you encountered. + options: + - label: I have searched the existing issues + required: true + - type: checkboxes + attributes: + label: I'm submitting a ... + options: + - label: bug report + - label: feature request + - label: support request --> Contact me over mail for support https://github.com/MShawon + - type: textarea + attributes: + label: Description + description: | + A concise description of what you're experiencing and what you expected to happen. + If applicable, include terminal logs or screenshots! + validations: + required: true + - type: textarea + attributes: + label: Environment + description: | + examples: + - **OS** : Ubuntu 20.04 + - **Python** : 3.9.10 / EXE without Python + - **Script version** : 1.7.1 + value: | + - OS : + - Python : + - Script version : + render: markdown + validations: + required: true + - type: textarea + attributes: + label: config.json + description: If applicable, attach your config.json to resolve the issue + placeholder: Before posting, make sure to remove information like proxy username and password or private API + render: markdown + validations: + required: true diff --git a/LICENSE b/LICENSE index 5fa30e4..fa24a6d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022 MShawon +Copyright (c) 2021-2022 MShawon Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index ab72a51..1bb5beb 100644 --- a/README.md +++ b/README.md @@ -47,8 +47,8 @@ Simple program to increase YouTube views written in Python. Works with live stre [Urls](https://github.com/MShawon/YouTube-Viewer#urls) [Search](https://github.com/MShawon/YouTube-Viewer#search) [Live Stream](https://github.com/MShawon/YouTube-Viewer#live-stream) -[YouTube Music](https://github.com/MShawon/YouTube-Viewer#youtube-music) -[Fast VPS](https://github.com/MShawon/YouTube-Viewer#fast-vps-with-unlimited-traffic) +[YouTube Music](https://github.com/MShawon/YouTube-Viewer#youtube-music) +[Fast VPS](https://github.com/MShawon/YouTube-Viewer#fast-vps-with-unlimited-traffic) [Windows](https://github.com/MShawon/YouTube-Viewer#windows)   [Binary Release](https://github.com/MShawon/YouTube-Viewer#binary-release)   [Installation](https://github.com/MShawon/YouTube-Viewer#installation) @@ -68,12 +68,6 @@ Simple program to increase YouTube views written in Python. Works with live stre * Good proxy list (http, https, socks4, socks5) * Google Chrome installed on your OS (not Chromium) -# New Update - Update from v1.7.0 include a feature to get views as suggested videos which can decrease the view drops issue. To get the must out of it, one must run the bot on multiple videos. However this is still an experimental feature. If you face any major issue, roll back to previous version. - - If you are not using the release version, don't forget to update the dependency by - `pip install -r requirements.txt` - # Features * YouTube default, live streaming and YouTube Music support @@ -86,12 +80,12 @@ Simple program to increase YouTube views written in Python. Works with live stre * format : `ip:port`, `user:pass@ip:port`, `ip:port:user:pass` * proxy refresh after a certain time specified by the user * rotating proxy support - * chrome v70+ randomized user agent based on platform + * chrome v80+ randomized user agent based on platform * canvas,audio,font,webgl fingerprint defender and IP leak prevent by webrtc control * geolocation, timezone, referer spoofing * can add extra extensions in the `extension/custom_extension/` folder * direct link or search *keyword* on YouTube then watch the video by matching exact video *title* - * modify urls.txt and search.txt on the fly without restarting program + * modify **urls.txt, search.txt and config.json** on the fly without restarting program * HTTP api on localhost and a database to store view count * config.json to save settings * bypass consent page and several other pop up @@ -101,8 +95,19 @@ Simple program to increase YouTube views written in Python. Works with live stre * YouTube Search * Suggested Videos * External (Google, Yahoo, DuckDuckGo, Bing, Twitter) + * End Screens + * Channel Pages * Direct or unknown +# How to get started + 1) First, install the script following any one of these + * [Windows without installing python](https://github.com/MShawon/YouTube-Viewer#binary-release) + * [Windows from source code](https://github.com/MShawon/YouTube-Viewer#installation) + * [Linux / Mac from source code](https://github.com/MShawon/YouTube-Viewer#linux--mac) + 2) Then put your video links in the [urls.txt](https://github.com/MShawon/YouTube-Viewer#urls) file + 3) To search your video on YouTube and then play it put the search keywords and video title in the [search.txt](https://github.com/MShawon/YouTube-Viewer#search) file + 4) Get your [proxy](https://github.com/MShawon/YouTube-Viewer#proxies) list + 5) Run the script and follow the instructions from there. # Proxies @@ -129,10 +134,11 @@ Simple program to increase YouTube views written in Python. Works with live stre # HTTP API Live logs fetched every 10 seconds and statistics in graphs are available on http://localhost:5000/ .Or [http://ip_of_your_pc:5000/](http://ip_of_your_pc:5000/) use this to access from another device under same network. A SQLite Database is being used to store your generated views from this script. - Last 20 logs from scripts are fetched every 10 seconds to show on website and graph is updated every 5 minutes. + Last 200 logs from scripts are fetched every 10 seconds to show on website and graph is updated every 5 minutes. # Config.json No need to type everything everytime you run the script. A config file will be created automatically to save and use your preferences. + You can modify it on the fly without restarting the program. # Urls Put video links in the urls.txt. For multiple videos place urls in multple lines. @@ -157,7 +163,7 @@ Simple program to increase YouTube views written in Python. Works with live stre For YouTube Music use version 1.6.2 or earlier until the bug is fixed in the latest releases. # Fast VPS with Unlimited Traffic - *[PetroSky](https://petrosky.io/mshawon) is one of the various CloudHosting services with the fastest and most convenient cloud technology. Their servers are powered by the latest **AMD RYZEN/EPYC CPUs** with High-Performance **NVMe SSD Hard Drives** that will let your application run faster than ever. You can get 2 vCPU with 4 GB ECC RAM for as low as 11.99€/month which will work very well for YouTube-Viewer script for 2 threads. Visit [PetroSky](https://petrosky.io/mshawon) to get the fastest VPS with unlimited traffic at the lowest price.* + *[PetroSky](https://petrosky.io/mshawon) is one of the various CloudHosting services with the fastest and most convenient cloud technology. Their servers are powered by the latest **AMD RYZEN/EPYC CPUs** with High-Performance **NVMe SSD Hard Drives** that will let your application run faster than ever. You can get 2 vCPU with 4 GB ECC RAM for as low as 11.99€/month which will work very well for YouTube-Viewer script for 2 threads. Visit [PetroSky](https://petrosky.io/mshawon) to get the fastest VPS with unlimited traffic at the lowest price. Use this code `mshawon25` to get **25% discount** on your purchase* # Windows * ## Binary Release @@ -186,7 +192,7 @@ Simple program to increase YouTube views written in Python. Works with live stre ``` * ## Important - * If you've got a large proxy collection, you should run this command to filter Good proxies. Then use **GoodProxy.txt** for proxy in **youtube_viewer.py** + * If you've got a large free proxies collection, you should run this command to filter Good proxies. Then use **GoodProxy.txt** for proxy in **youtube_viewer.py** ``` python proxy_check.py ``` @@ -225,7 +231,7 @@ Simple program to increase YouTube views written in Python. Works with live stre ``` * ## Important - * If you've got a large proxy collection, you should run this command to filter Good proxies. Then use **GoodProxy.txt** for proxy in **youtube_viewer.py** + * If you've got a large free proxies collection, you should run this command to filter Good proxies. Then use **GoodProxy.txt** for proxy in **youtube_viewer.py** ``` python3 proxy_check.py ``` @@ -251,8 +257,5 @@ Simple program to increase YouTube views written in Python. Works with live stre * And use as many [urls](https://github.com/MShawon/YouTube-Viewer#urls) and [keyword::::title](https://github.com/MShawon/YouTube-Viewer#search) as you can. Don't use just one video. - # Issues - Before opening an issue, please read this page thoroughly. Maybe someone already faced the same problem you have right now. So it's always a good idea to check the answer to issues first. If your problem isn't there, feel free to open an issue. Also, don't forget to give as many details as you can. config.json and a screenshot of terminal output provide a handful of information to resolve your problem. - # Credits I want to thank all of you who have opened an issue or shared your code snippets or ideas with me! diff --git a/extension/always_active.zip b/extension/always_active.zip index f4b46e9..6c0d2b7 100644 Binary files a/extension/always_active.zip and b/extension/always_active.zip differ diff --git a/proxy_check.py b/proxy_check.py index 4f7b6a4..0489b0d 100644 --- a/proxy_check.py +++ b/proxy_check.py @@ -1,7 +1,7 @@ """ MIT License -Copyright (c) 2022 MShawon +Copyright (c) 2021-2022 MShawon Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -21,12 +21,12 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -import concurrent.futures.thread import os import shutil import sys -from concurrent.futures import ThreadPoolExecutor, as_completed +from concurrent.futures import ThreadPoolExecutor, wait from glob import glob +from time import sleep import requests from fake_headers import Headers @@ -64,25 +64,25 @@ class bcolors: """ + bcolors.ENDC) -try: - os.remove('ProxyBackup.txt') -except: - pass +checked = {} +cancel_all = False -try: - shutil.copy('GoodProxy.txt', 'ProxyBackup.txt') - print(bcolors.WARNING + 'GoodProxy.txt backed up in ProxyBackup.txt' + bcolors.ENDC) - os.remove('GoodProxy.txt') -except: - pass -checked = {} +def backup(): + try: + shutil.copy('GoodProxy.txt', 'ProxyBackup.txt') + print(bcolors.WARNING + + 'GoodProxy.txt backed up in ProxyBackup.txt' + bcolors.ENDC) + except Exception: + pass + + print('', file=open('GoodProxy.txt', 'w')) def clean_exe_temp(folder): try: temp_name = sys._MEIPASS.split('\\')[-1] - except: + except Exception: temp_name = None for f in glob(os.path.join('temp', folder, '*')): @@ -99,8 +99,13 @@ def load_proxy(): if not os.path.isfile(filename) and filename[-4:] != '.txt': filename = f'{filename}.txt' - with open(filename, encoding="utf-8") as fh: - loaded = [x.strip() for x in fh if x.strip() != ''] + try: + with open(filename, encoding="utf-8") as fh: + loaded = [x.strip() for x in fh if x.strip() != ''] + except Exception as e: + print(bcolors.FAIL + str(e) + bcolors.ENDC) + input('') + sys.exit() for lines in loaded: if lines.count(':') == 3: @@ -112,15 +117,16 @@ def load_proxy(): def main_checker(proxy_type, proxy, position): + if cancel_all: + raise KeyboardInterrupt checked[position] = None - proxyDict = { - "http": f"{proxy_type}://{proxy}", - "https": f"{proxy_type}://{proxy}", - } - try: + proxy_dict = { + "http": f"{proxy_type}://{proxy}", + "https": f"{proxy_type}://{proxy}", + } header = Headers( headers=False @@ -132,64 +138,88 @@ def main_checker(proxy_type, proxy, position): } response = requests.get( - 'https://www.youtube.com/', headers=headers, proxies=proxyDict, timeout=30) + 'https://www.youtube.com/', headers=headers, proxies=proxy_dict, timeout=30) status = response.status_code if status != 200: - raise Exception + raise Exception(status) - print(bcolors.OKBLUE + f"Tried {position+1} |" + bcolors.OKGREEN + - f' {proxy} | GOOD | Type : {proxy_type} | Response : {status}' + bcolors.ENDC) + print(bcolors.OKBLUE + f"Worker {position+1} | " + bcolors.OKGREEN + + f'{proxy} | GOOD | Type : {proxy_type} | Response : {status}' + bcolors.ENDC) - print(proxy, file=open('GoodProxy.txt', 'a')) + print(f'{proxy}|{proxy_type}', file=open('GoodProxy.txt', 'a')) - except: - print(bcolors.OKBLUE + f"Tried {position+1} |" + bcolors.FAIL + - f' {proxy} | {proxy_type} | BAD ' + bcolors.ENDC) + except Exception as e: + try: + e = int(e.args[0]) + except Exception: + e = '' + print(bcolors.OKBLUE + f"Worker {position+1} | " + bcolors.FAIL + + f'{proxy} | {proxy_type} | BAD | {e}' + bcolors.ENDC) checked[position] = proxy_type - pass def proxy_check(position): - + sleep(2) proxy = proxy_list[position] - main_checker('http', proxy, position) - if checked[position] == 'http': - main_checker('socks4', proxy, position) - if checked[position] == 'socks4': - main_checker('socks5', proxy, position) + if '|' in proxy: + splitted = proxy.split('|') + main_checker(splitted[-1], splitted[0], position) + else: + main_checker('http', proxy, position) + if checked[position] == 'http': + main_checker('socks4', proxy, position) + if checked[position] == 'socks4': + main_checker('socks5', proxy, position) def main(): + global cancel_all + + cancel_all = False pool_number = [i for i in range(total_proxies)] with ThreadPoolExecutor(max_workers=threads) as executor: futures = [executor.submit(proxy_check, position) for position in pool_number] - + done, not_done = wait(futures, timeout=0) try: - for future in as_completed(futures): - future.result() + while not_done: + freshly_done, not_done = wait(not_done, timeout=5) + done |= freshly_done except KeyboardInterrupt: - executor._threads.clear() - concurrent.futures.thread._threads_queues.clear() + print(bcolors.WARNING + + 'Hold on!!! Allow me a moment to finish the running threads' + bcolors.ENDC) + cancel_all = True + for future in not_done: + _ = future.cancel() + _ = wait(not_done, timeout=None) + raise KeyboardInterrupt except IndexError: - print(bcolors.WARNING + 'Number of proxies are less than threads. Provide more proxies or less threads.' + bcolors.ENDC) - pass + print(bcolors.WARNING + 'Number of proxies are less than threads. Provide more proxies or less threads. ' + bcolors.ENDC) if __name__ == '__main__': - + clean_exe_temp(folder='proxy_check') - threads = int( - input(bcolors.OKBLUE+'Threads (recommended = 100): ' + bcolors.ENDC)) + backup() + + try: + threads = int( + input(bcolors.OKBLUE+'Threads (recommended = 100): ' + bcolors.ENDC)) + except Exception: + threads = 100 proxy_list = load_proxy() - proxy_list = list(set(proxy_list)) # removing duplicate proxies - proxy_list = list(filter(None, proxy_list)) # removing empty proxies + # removing empty & duplicate proxies + proxy_list = list(set(filter(None, proxy_list))) total_proxies = len(proxy_list) - print(bcolors.OKCYAN + f'Total proxies : {total_proxies}' + bcolors.ENDC) + print(bcolors.OKCYAN + + f'Total unique proxies : {total_proxies}' + bcolors.ENDC) - main() + try: + main() + except KeyboardInterrupt: + sys.exit() diff --git a/requirements.txt b/requirements.txt index aabc21a..f8c297c 100644 Binary files a/requirements.txt and b/requirements.txt differ diff --git a/search.txt b/search.txt index 0309fe0..da9eed5 100644 --- a/search.txt +++ b/search.txt @@ -1 +1,4 @@ -keyword :::: exact video title \ No newline at end of file +keyword 1 :::: exact video title 1 +keyword 2 :::: exact video title 2 +keyword 3 :::: exact video title 2 +keyword 4 :::: exact video title 3 \ No newline at end of file diff --git a/youtube_viewer.py b/youtube_viewer.py index ece5ddf..b8e9bf1 100644 --- a/youtube_viewer.py +++ b/youtube_viewer.py @@ -1,7 +1,7 @@ """ MIT License -Copyright (c) 2022 MShawon +Copyright (c) 2021-2022 MShawon Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -21,15 +21,15 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -import concurrent.futures.thread import io import json import logging -import queue -from concurrent.futures import ThreadPoolExecutor, as_completed +from concurrent.futures import ThreadPoolExecutor, wait from time import gmtime, sleep, strftime, time +import psutil from fake_headers import Headers, browsers +from requests.exceptions import RequestException from undetected_chromedriver.patcher import Patcher from youtubeviewer import website @@ -46,28 +46,29 @@ print(bcolors.OKGREEN + """ -Yb dP dP"Yb 88 88 888888 88 88 88""Yb 888888 - YbdP dP Yb 88 88 88 88 88 88__dP 88__ - 8P Yb dP Y8 8P 88 Y8 8P 88""Yb 88"" - dP YbodP `YbodP' 88 `YbodP' 88oodP 888888 +Yb dP dP"Yb 88 88 888888 88 88 88""Yb 888888 + YbdP dP Yb 88 88 88 88 88 88__dP 88__ + 8P Yb dP Y8 8P 88 Y8 8P 88""Yb 88"" + dP YbodP `YbodP' 88 `YbodP' 88oodP 888888 - Yb dP 88 888888 Yb dP 888888 88""Yb - Yb dP 88 88__ Yb db dP 88__ 88__dP - YbdP 88 88"" YbdPYbdP 88"" 88"Yb - YP 88 888888 YP YP 888888 88 Yb + Yb dP 88 888888 Yb dP 888888 88""Yb + Yb dP 88 88__ Yb db dP 88__ 88__dP + YbdP 88 88"" YbdPYbdP 88"" 88"Yb + YP 88 888888 YP YP 888888 88 Yb """ + bcolors.ENDC) print(bcolors.OKCYAN + """ [ GitHub : https://github.com/MShawon/YouTube-Viewer ] """ + bcolors.ENDC) -SCRIPT_VERSION = '1.7.1' +SCRIPT_VERSION = '1.7.2' proxy = None driver = None status = None start_time = None server_running = False +cancel_all = False urls = [] queries = [] @@ -75,17 +76,25 @@ hash_urls = None hash_queries = None +hash_config = None -driver_list = [] -view = [] +driver_dict = {} duration_dict = {} checked = {} +view = [] +bad_proxies = [] +used_proxies = [] console = [] +temp_folders = [] threads = 0 +global views +views = 100 + cwd = os.getcwd() patched_drivers = os.path.join(cwd, 'patched_drivers') config_path = os.path.join(cwd, 'config.json') +driver_identifier = os.path.join(cwd, 'patched_drivers', 'chromedriver') DATABASE = os.path.join(cwd, 'database.db') DATABASE_BACKUP = os.path.join(cwd, 'database_backup.db') @@ -102,22 +111,6 @@ website.database = DATABASE -class UrlsError(Exception): - pass - - -class SearchError(Exception): - pass - - -class CaptchaError(Exception): - pass - - -class QueryError(Exception): - pass - - def monkey_patch_exe(self): linect = 0 replacement = self.gen_random_cdc() @@ -135,15 +128,17 @@ def monkey_patch_exe(self): def timestamp(): + global date_fmt, cpu_usage date_fmt = datetime.now().strftime("%d-%b-%Y %H:%M:%S") - return bcolors.OKGREEN + f'[{date_fmt}] ' + cpu = str(psutil.cpu_percent()) + cpu_usage = cpu + '%' + ' '*(5-len(cpu)) if cpu != '0.0' else cpu_usage + return bcolors.OKGREEN + f'[{date_fmt}] | ' + bcolors.OKCYAN + f'{cpu_usage} | ' def clean_exe_temp(folder): - try: + temp_name = None + if hasattr(sys, '_MEIPASS'): temp_name = sys._MEIPASS.split('\\')[-1] - except: - temp_name = None for f in glob(os.path.join('temp', folder, '*')): if temp_name not in f: @@ -165,7 +160,7 @@ def check_update(): RELEASE_VERSION = response.json()['tag_name'] - if SCRIPT_VERSION != RELEASE_VERSION: + if RELEASE_VERSION > SCRIPT_VERSION: print(bcolors.OKCYAN + '#'*100 + bcolors.ENDC) print(bcolors.OKCYAN + 'Update Available!!! ' + f'YouTube Viewer version {SCRIPT_VERSION} needs to update to {RELEASE_VERSION} version.' + bcolors.ENDC) @@ -175,55 +170,191 @@ def check_update(): for note in notes: if note: print(bcolors.HEADER + note + bcolors.ENDC) - except: + except Exception: pass print(bcolors.OKCYAN + '#'*100 + '\n' + bcolors.ENDC) def create_html(text_dict): - global console + if len(console) > 250: + console.pop() - if len(console) > 50: - console.pop(0) - - date_fmt = f' [{datetime.now().strftime("%d-%b-%Y %H:%M:%S")}] ' + date = f' [{date_fmt}] | ' + cpu = f' {cpu_usage} | ' str_fmt = ''.join( [f' {value} ' for key, value in text_dict.items()]) - html = date_fmt + str_fmt + html = date + cpu + str_fmt - console.append(html) + console.insert(0, html) def detect_file_change(): - global hash_urls, hash_queries, urls, queries, suggested + global hash_urls, hash_queries, urls, queries - new_hash = get_hash("urls.txt") - if new_hash != hash_urls: - hash_urls = new_hash + if hash_urls != get_hash("urls.txt"): + hash_urls = get_hash("urls.txt") urls = load_url() - suggested = [] + suggested.clear() - new_hash = get_hash("search.txt") - if new_hash != hash_queries: - hash_queries = new_hash + if hash_queries != get_hash("search.txt"): + hash_queries = get_hash("search.txt") queries = load_search() - suggested = [] + suggested.clear() + + +def direct_or_search(position): + keyword = None + video_title = None + if position % 2: + try: + method = 1 + url = choice(urls) + if 'music.youtube.com' in url: + youtube = 'Music' + else: + youtube = 'Video' + except IndexError: + raise Exception("Your urls.txt is empty!") + + else: + try: + method = 2 + query = choice(queries) + keyword = query[0] + video_title = query[1] + url = "https://www.youtube.com" + youtube = 'Video' + except IndexError: + if urls and 'music.youtube.com' in url: + url = choice(urls) + youtube = 'Music' + else: + raise Exception("Your search.txt is empty!") + + return url, method, youtube, keyword, video_title def features(driver): + if bandwidth: + save_bandwidth(driver) + bypass_popup(driver) bypass_other_popup(driver) play_video(driver) - if bandwidth: - save_bandwidth(driver) - change_playback_speed(driver, playback_speed) -def control_player(driver, output, position, proxy, youtube): +def update_view_count(position): + view.append(position) + view_count = len(view) + print(timestamp() + bcolors.OKCYAN + + f'Worker {position} | View added : {view_count}' + bcolors.ENDC) + + create_html({"#29b2d3": f'Worker {position} | View added : {view_count}'}) + + if database: + try: + update_database( + database=DATABASE, threads=max_threads) + except Exception: + pass + + +def set_referer(position, url, method, driver): + referer = choice(REFERERS) + if referer: + if method == 2 and 't.co/' in referer: + driver.get(url) + else: + if 'search.yahoo.com' in referer: + driver.get('https://duckduckgo.com/') + driver.execute_script( + "window.history.pushState('page2', 'Title', arguments[0]);", referer) + else: + driver.get(referer) + + driver.execute_script( + "window.location.href = '{}';".format(url)) + + print(timestamp() + bcolors.OKBLUE + + f"Worker {position} | Referer used : {referer}" + bcolors.ENDC) + + create_html( + {"#3b8eea": f"Worker {position} | Referer used : {referer}"}) + + else: + driver.get(url) + + +def youtube_normal(method, keyword, video_title, driver, output): + if method == 1: + skip_initial_ad(driver, output, duration_dict) + + else: + msg = search_video(driver, keyword, video_title) + if msg == 'failed': + raise Exception( + f"Can't find this [{video_title}] video with this keyword [{keyword}]") + + skip_initial_ad(driver, output, duration_dict) + + try: + WebDriverWait(driver, 10).until(EC.visibility_of_element_located( + (By.ID, 'movie_player'))) + except WebDriverException: + raise Exception( + "Slow internet speed or Stuck at recaptcha! Can't load YouTube...") + + features(driver) + + view_stat = WebDriverWait(driver, 30).until( + EC.presence_of_element_located((By.CSS_SELECTOR, '#count span'))).text + + return view_stat + + +def youtube_music(driver): + if 'coming-soon' in driver.current_url: + raise Exception( + "YouTube Music is not available in your area!") + try: + WebDriverWait(driver, 10).until(EC.visibility_of_element_located( + (By.XPATH, '//*[@id="player-page"]'))) + except WebDriverException: + raise Exception( + "Slow internet speed or Stuck at recaptcha! Can't load YouTube...") + + bypass_popup(driver) + + play_music(driver) + + view_stat = 'music' + return view_stat + + +def spoof_geolocation(proxy_type, proxy, driver): + try: + proxy_dict = { + "http": f"{proxy_type}://{proxy}", + "https": f"{proxy_type}://{proxy}", + } + location = requests.get( + "http://ip-api.com/json", proxies=proxy_dict, timeout=30).json() + params = { + "latitude": location['lat'], + "longitude": location['lon'], + "accuracy": randint(20, 100) + } + driver.execute_cdp_cmd( + "Emulation.setGeolocationOverride", params) + except (RequestException, WebDriverException): + pass + + +def control_player(driver, output, position, proxy, youtube, collect_id=True): current_url = driver.current_url if not output: output = driver.title[:-10] @@ -241,17 +372,19 @@ def control_player(driver, output, position, proxy, youtube): video_len = video_len*uniform(minimum, maximum) duration = strftime("%Hh:%Mm:%Ss", gmtime(video_len)) - print(timestamp() + bcolors.OKBLUE + f"Tried {position} | " + bcolors.OKGREEN + + print(timestamp() + bcolors.OKBLUE + f"Worker {position} | " + bcolors.OKGREEN + f"{proxy} --> {youtube} Found : {output} | Watch Duration : {duration} " + bcolors.ENDC) - create_html({"#3b8eea": f"Tried {position} | ", - "#23d18b": f"{proxy} --> {youtube} Found : {output} | Watch Duration : {duration} "}) + create_html({"#3b8eea": f"Worker {position} | ", + "#23d18b": f"{proxy.split('@')[-1]} --> {youtube} Found : {output} | Watch Duration : {duration} "}) - video_id = driver.find_element( - By.XPATH, '//*[@id="page-manager"]/ytd-watch-flexy').get_attribute('video-id') - if video_id not in suggested: - suggested.append(video_id) + if youtube == 'Video' and collect_id: + video_id = driver.find_element( + By.XPATH, '//*[@id="page-manager"]/ytd-watch-flexy').get_attribute('video-id') + if video_id and video_id not in suggested: + suggested.append(video_id) + error = 0 loop = int(video_len/4) for _ in range(loop): sleep(5) @@ -264,341 +397,319 @@ def control_player(driver, output, position, proxy, youtube): elif youtube == 'Music': play_music(driver) - if current_time > video_len or driver.current_url != current_url: + current_state = driver.execute_script( + "return document.getElementById('movie_player').getPlayerState()") + if current_state in [-1, 3]: + error += 1 + else: + error = 0 + + if error == 10: + error_msg = f'Taking too long to play the video | Reason : buffering' + if current_state == -1: + error_msg = f'Failed to play the video | Possible Reason : {proxy} not working anymore' + raise Exception(error_msg) + + elif current_time > video_len or driver.current_url != current_url: break return current_url -def quit_driver(driver, pluginfile): - try: - driver_list.remove(driver) - except: - pass - - driver.quit() +def youtube_live(proxy, position, driver, output): + error = 0 + while True: + view_stat = driver.find_element( + By.CSS_SELECTOR, '#count span').text + if 'watching' in view_stat: + print(timestamp() + bcolors.OKBLUE + f"Worker {position} | " + bcolors.OKGREEN + + f"{proxy} | {output} | " + bcolors.OKCYAN + f"{view_stat} " + bcolors.ENDC) + + create_html({"#3b8eea": f"Worker {position} | ", + "#23d18b": f"{proxy.split('@')[-1]} | {output} | ", "#29b2d3": f"{view_stat} "}) + else: + error += 1 - try: - os.remove(pluginfile) - except: - pass + play_video(driver) - status = 400 - return status + random_command(driver) + if error == 5: + break + sleep(60) -def sleeping(): - sleep(5) + update_view_count(position) -def main_viewer(proxy_type, proxy, position): - try: - global width, viewports +def music_and_video(proxy, position, youtube, driver, output, view_stat): + rand_choice = 1 + if len(suggested) > 1 and view_stat != 'music': + rand_choice = randint(1, 3) - detect_file_change() + for i in range(rand_choice): + if i == 0: + current_url = control_player( + driver, output, position, proxy, youtube) - checked[position] = None + update_view_count(position) - header = Headers( - browser="chrome", - os=osname, - headers=False - ).generate() - agent = header['User-Agent'] + else: + print(timestamp() + bcolors.OKBLUE + + f"Worker {position} | Suggested video loop : {i}" + bcolors.ENDC) - url = '' + create_html( + {"#3b8eea": f"Worker {position} | Suggested video loop : {i}"}) - if position % 2: try: - method = 1 - url = choice(urls) - if 'music.youtube.com' in url: - youtube = 'Music' - else: - youtube = 'Video' - except: - raise UrlsError + output = play_next_video(driver, suggested) + except WebDriverException as e: + raise Exception(f'Error suggested | {e.args[0]}') - else: - try: - method = 2 - query = choice(queries) - keyword = query[0] - video_title = query[1] - url = "https://www.youtube.com" - youtube = 'Video' - except: - url = choice(urls) - if 'music.youtube.com' in url: - youtube = 'Music' - else: - raise SearchError + print(timestamp() + bcolors.OKBLUE + + f"Worker {position} | Found next suggested video : [{output}]" + bcolors.ENDC) - if category == 'r' and proxy_api: - proxies = scrape_api(link=proxy) - proxy = choice(proxies) + create_html( + {"#3b8eea": f"Worker {position} | Found next suggested video : [{output}]"}) - status = check_proxy(category, agent, proxy, proxy_type) + skip_initial_ad(driver, output, duration_dict) - if status == 200: - try: - print(timestamp() + bcolors.OKBLUE + f"Tried {position} | " + bcolors.OKGREEN + - f"{proxy} | {proxy_type.upper()} | Good Proxy | Opening a new driver..." + bcolors.ENDC) + features(driver) - create_html({"#3b8eea": f"Tried {position} | ", - "#23d18b": f"{proxy} | {proxy_type.upper()} | Good Proxy | Opening a new driver..."}) + current_url = control_player( + driver, output, position, proxy, youtube) - patched_driver = os.path.join( - patched_drivers, f'chromedriver_{position%threads}{exe_name}') + update_view_count(position) - try: - Patcher(executable_path=patched_driver).patch_exe() - except: - pass + return current_url - pluginfile = os.path.join( - 'extension', f'proxy_auth_plugin{position}.zip') - factor = int(threads/6) - sleep_time = int((str(position)[-1])) * factor - sleep(sleep_time) +def channel_or_endscreen(proxy, position, youtube, driver, view_stat, current_url): + option = 1 + if view_stat != 'music' and driver.current_url == current_url: + option = choices([1, 2, 3], cum_weights=(0.5, 0.75, 1.00), k=1)[0] - driver = get_driver(background, viewports, agent, auth_required, - patched_driver, proxy, proxy_type, pluginfile) + if option == 2: + channel_name = driver.find_element( + By.CSS_SELECTOR, '#upload-info a').text - driver_list.append(driver) + try: + output, log, option = play_from_channel(driver, channel_name) + except WebDriverException as e: + raise Exception(f'Error channel | {e.args[0]}') - sleep(2) + print(timestamp() + bcolors.OKBLUE + + f"Worker {position} | {log}" + bcolors.ENDC) - try: - proxy_dict = { - "http": f"{proxy_type}://{proxy}", - "https": f"{proxy_type}://{proxy}", - } - location = requests.get( - "http://ip-api.com/json", proxies=proxy_dict, timeout=30).json() - params = { - "latitude": location['lat'], - "longitude": location['lon'], - "accuracy": randint(20, 100) - } - driver.execute_cdp_cmd( - "Emulation.setGeolocationOverride", params) - except: - pass + create_html({"#3b8eea": f"Worker {position} | {log}"}) - referer = choice(REFERERS) - if referer: - if method == 2 and 't.co/' in referer: - driver.get(url) - else: - if 'search.yahoo.com' in referer: - driver.get('https://duckduckgo.com/') - driver.execute_script( - "window.history.pushState('page2', 'Title', arguments[0]);", referer) - else: - driver.get(referer) + elif option == 3: + try: + output = play_end_screen_video(driver) + except WebDriverException as e: + raise Exception(f'Error end screen | {e.args[0]}') - driver.execute_script( - "window.location.href = '{}';".format(url)) + print(timestamp() + bcolors.OKBLUE + + f"Worker {position} | Video played from end screen : [{output}]" + bcolors.ENDC) - print(timestamp() + bcolors.OKBLUE + - f"Tried {position} | Referer used : {referer}" + bcolors.ENDC) + create_html( + {"#3b8eea": f"Worker {position} | Video played from end screen : [{output}]"}) - create_html( - {"#3b8eea": f"Tried {position} | Referer used : {referer}"}) + if option in [2, 3]: + skip_initial_ad(driver, output, duration_dict) - else: - driver.get(url) + features(driver) - if 'consent' in driver.current_url: - print(timestamp() + bcolors.OKBLUE + - f"Tried {position} | Bypassing consent..." + bcolors.ENDC) + current_url = control_player( + driver, output, position, proxy, youtube, collect_id=False) - create_html( - {"#3b8eea": f"Tried {position} | Bypassing consent..."}) + if option in [2, 3, 4]: + update_view_count(position) - bypass_consent(driver) - output = driver.title[:-10] +def windows_kill_drivers(): + for process in constructor.Win32_Process(["CommandLine", "ProcessId"]): + try: + if 'UserAgentClientHint' in process.CommandLine or driver_identifier in process.CommandLine: + # print(f'Killing PID : {process.ProcessId}') + subprocess.Popen(['taskkill', '/F', '/PID', f'{process.ProcessId}'], + stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL) + except Exception: + pass - if youtube == 'Video': - if method == 1: - skip_initial_ad(driver, output, duration_dict) - else: - scroll = search_video(driver, keyword, video_title) - if scroll == 0: - raise CaptchaError - elif scroll == 10: - raise QueryError - else: - pass +def quit_driver(driver, data_dir): + if driver in driver_dict: + driver.quit() + if data_dir in temp_folders: + temp_folders.remove(data_dir) - skip_initial_ad(driver, output, duration_dict) + proxy_folder = driver_dict.pop(driver, None) + if proxy_folder: + shutil.rmtree(proxy_folder, ignore_errors=True) - try: - WebDriverWait(driver, 5).until(EC.visibility_of_element_located( - (By.XPATH, '//ytd-player[@id="ytd-player"]'))) - except: - raise CaptchaError + status = 400 + return status - features(driver) - view_stat = WebDriverWait(driver, 30).until(EC.visibility_of_element_located( - (By.XPATH, '//span[@class="view-count style-scope ytd-video-view-count-renderer"]'))).text +def main_viewer(proxy_type, proxy, position): + global width, viewports - else: - try: - WebDriverWait(driver, 5).until(EC.visibility_of_element_located( - (By.XPATH, '//*[@id="player-page"]'))) - except: - raise CaptchaError + if cancel_all: + raise KeyboardInterrupt - bypass_popup(driver) + try: + detect_file_change() - play_music(driver) + checked[position] = None - view_stat = 'music' + header = Headers( + browser="chrome", + os=osname, + headers=False + ).generate() + agent = header['User-Agent'] - if width == 0: - width = driver.execute_script('return screen.width') - viewports = [i for i in viewports if int(i[:4]) <= width] + url, method, youtube, keyword, video_title = direct_or_search(position) - if 'watching' in view_stat: - error = 0 - while True: - view_stat = driver.find_element( - By.XPATH, '//span[@class="view-count style-scope ytd-video-view-count-renderer"]').text - if 'watching' in view_stat: - print(timestamp() + bcolors.OKBLUE + f"Tried {position} | " + bcolors.OKGREEN + - f"{proxy} | {output} | " + bcolors.OKCYAN + f"{view_stat} " + bcolors.ENDC) + if category == 'r' and proxy_api: + proxies = scrape_api(link=proxy) + proxy = choice(proxies) + for _ in range(20): + if proxy in used_proxies: + proxy = choice(proxies) + else: + break + used_proxies.append(proxy) - create_html({"#3b8eea": f"Tried {position} | ", - "#23d18b": f"{proxy} | {output} | ", "#29b2d3": f"{view_stat} "}) - else: - error += 1 + status = check_proxy(category, agent, proxy, proxy_type) - play_video(driver) - random_command(driver) + if status != 200: + raise RequestException(status) - if error == 5: - break - sleep(60) + try: + print(timestamp() + bcolors.OKBLUE + f"Worker {position} | " + bcolors.OKGREEN + + f"{proxy} | {proxy_type.upper()} | Good Proxy | Opening a new driver..." + bcolors.ENDC) - else: - rand_choice = 1 - if len(suggested) > 1 and view_stat != 'music': - rand_choice = randint(1, 3) + create_html({"#3b8eea": f"Worker {position} | ", + "#23d18b": f"{proxy.split('@')[-1]} | {proxy_type.upper()} | Good Proxy | Opening a new driver..."}) - increment = 0 - for i in range(rand_choice): - if i == 0: - control_player( - driver, output, position, proxy, youtube) + while proxy in bad_proxies: + bad_proxies.remove(proxy) - view.append(position) - increment = i + 1 + patched_driver = os.path.join( + patched_drivers, f'chromedriver_{position%threads}{exe_name}') - else: - print(timestamp() + bcolors.OKBLUE + - f"Tried {position} | Suggested video loop : {i}" + bcolors.ENDC) + try: + Patcher(executable_path=patched_driver).patch_exe() + except Exception: + pass - create_html( - {"#3b8eea": f"Tried {position} | Suggested video loop : {i}"}) + proxy_folder = os.path.join( + cwd, 'extension', f'proxy_auth_{position}') - output = play_next_video(driver, suggested) + factor = int(threads/(0.1*threads + 1)) + sleep_time = int((str(position)[-1])) * factor + sleep(sleep_time) + if cancel_all: + raise KeyboardInterrupt - print(timestamp() + bcolors.OKBLUE + - f"Tried {position} | Found next suggested video : [{output}]" + bcolors.ENDC) + driver = get_driver(background, viewports, agent, auth_required, + patched_driver, proxy, proxy_type, proxy_folder) - create_html( - {"#3b8eea": f"Tried {position} | Found next suggested video : [{output}]"}) + driver_dict[driver] = proxy_folder - skip_initial_ad(driver, output, duration_dict) + data_dir = driver.capabilities['chrome']['userDataDir'] + temp_folders.append(data_dir) - features(driver) + sleep(2) - control_player( - driver, output, position, proxy, youtube) + spoof_geolocation(proxy_type, proxy, driver) - view.append(position) - increment = i + 1 + if width == 0: + width = driver.execute_script('return screen.width') + viewports = [i for i in viewports if int(i[:4]) <= width] - if randint(1, 2) == 1: - driver.find_element(By.ID, 'movie_player').send_keys('k') + set_referer(position, url, method, driver) - view_count = len(view) - print(timestamp() + bcolors.OKCYAN + - f'View added : {view_count}' + bcolors.ENDC) + if 'consent' in driver.current_url: + print(timestamp() + bcolors.OKBLUE + + f"Worker {position} | Bypassing consent..." + bcolors.ENDC) - create_html({"#29b2d3": f'View added : {view_count}'}) + create_html( + {"#3b8eea": f"Worker {position} | Bypassing consent..."}) - if database: - try: - update_database( - database=DATABASE, threads=max_threads, increment=increment) - except: - pass + bypass_consent(driver) - status = quit_driver(driver, pluginfile) + output = driver.title[:-10] - except CaptchaError: - print(timestamp() + bcolors.FAIL + - f"Tried {position} | Slow internet speed or Stuck at recaptcha! Can't load YouTube..." + bcolors.ENDC) + if youtube == 'Video': + view_stat = youtube_normal( + method, keyword, video_title, driver, output) - create_html( - {"#f14c4c": f"Tried {position} | Slow internet speed or Stuck at recaptcha! Can't load YouTube..."}) + else: + view_stat = youtube_music(driver) - status = quit_driver(driver, pluginfile) - pass + if 'watching' in view_stat: + youtube_live(proxy, position, driver, output) - except QueryError: - print(timestamp() + bcolors.FAIL + - f"Tried {position} | Can't find this [{video_title}] video with this keyword [{keyword}]" + bcolors.ENDC) + else: + current_url = music_and_video( + proxy, position, youtube, driver, output, view_stat) - create_html( - {"#f14c4c": f"Tried {position} | Can't find this [{video_title}] video with this keyword [{keyword}]"}) + channel_or_endscreen(proxy, position, youtube, + driver, view_stat, current_url) - status = quit_driver(driver, pluginfile) - pass + if randint(1, 2) == 1: + try: + driver.find_element(By.ID, 'movie_player').send_keys('k') + except WebDriverException: + pass - except Exception as e: - *_, exc_tb = sys.exc_info() - print(timestamp() + bcolors.FAIL + - f"Tried {position} | Line : {exc_tb.tb_lineno} | " + str(e.args[0]) + bcolors.ENDC) + status = quit_driver(driver=driver, data_dir=data_dir) - create_html( - {"#f14c4c": f"Tried {position} | Line : {exc_tb.tb_lineno} | " + str(e.args[0])}) + except Exception as e: + *_, exc_tb = sys.exc_info() + print(timestamp() + bcolors.FAIL + + f"Worker {position} | Line : {exc_tb.tb_lineno} | " + str(e.args[0]) + bcolors.ENDC) - status = quit_driver(driver, pluginfile) - pass + create_html( + {"#f14c4c": f"Worker {position} | Line : {exc_tb.tb_lineno} | " + str(e.args[0])}) - except UrlsError: - print(timestamp() + bcolors.FAIL + - f"Tried {position} | Your urls.txt is empty!" + bcolors.ENDC) + status = quit_driver(driver=driver, data_dir=data_dir) - create_html( - {"#f14c4c": f"Tried {position} | Your urls.txt is empty!"}) - pass + except RequestException: + print(timestamp() + bcolors.OKBLUE + f"Worker {position} | " + + bcolors.FAIL + f"{proxy} | {proxy_type.upper()} | Bad proxy " + bcolors.ENDC) + + create_html({"#3b8eea": f"Worker {position} | ", + "#f14c4c": f"{proxy.split('@')[-1]} | {proxy_type.upper()} | Bad proxy "}) + + checked[position] = proxy_type + bad_proxies.append(proxy) - except SearchError: + except Exception as e: print(timestamp() + bcolors.FAIL + - f"Tried {position} | Your search.txt is empty!" + bcolors.ENDC) + f"Worker {position} | {e.args[0]}" + bcolors.ENDC) create_html( - {"#f14c4c": f"Tried {position} | Your search.txt is empty!"}) - pass + {"#f14c4c": f"Worker {position} | {e.args[0]}"}) - except: - print(timestamp() + bcolors.OKBLUE + f"Tried {position} | " + - bcolors.FAIL + f"{proxy} | {proxy_type.upper()} | Bad proxy " + bcolors.ENDC) - create_html({"#3b8eea": f"Tried {position} | ", - "#f14c4c": f"{proxy} | {proxy_type.upper()} | Bad proxy "}) +def get_proxy_list(): + if filename: + if category == 'r': + factor = max_threads if max_threads > 1000 else 1000 + proxy_list = [filename] * factor + else: + if proxy_api: + proxy_list = scrape_api(filename) + else: + proxy_list = load_proxy(filename) - checked[position] = proxy_type - pass + else: + proxy_list = gather_proxy() + + return proxy_list def stop_server(immediate=False): @@ -606,11 +717,52 @@ def stop_server(immediate=False): if api and server_running: if not immediate: + print('Server will shut down later') while 'state=running' in str(futures[1:-1]): sleep(5) server_running = False - requests.post(f'http://127.0.0.1:{port}/shutdown') + response = requests.post(f'http://127.0.0.1:{port}/shutdown') + for _ in range(10): + if response.status_code != 200: + print(f'Server shut down error : {response.status_code}') + response = requests.post(f'http://127.0.0.1:{port}/shutdown') + sleep(3) + else: + break + if response.status_code == 200: + print('Server shut down successfully!') + + +def clean_exit(): + print(timestamp() + bcolors.WARNING + + 'Cleaning up processes...' + bcolors.ENDC) + create_html({"#f3f342": "Cleaning up processes..."}) + + if osname == 'win': + driver_dict.clear() + windows_kill_drivers() + else: + for driver in list(driver_dict): + quit_driver(driver=driver, data_dir=None) + + for folder in temp_folders: + shutil.rmtree(folder, ignore_errors=True) + + +def cancel_pending_task(not_done): + global cancel_all + + cancel_all = True + for future in not_done: + _ = future.cancel() + + clean_exit() + + stop_server(immediate=True) + _ = wait(not_done, timeout=None) + + clean_exit() def view_video(position): @@ -622,13 +774,17 @@ def view_video(position): website.start_server(host=host, port=port) elif position == total_proxies - 1: - stop_server() + stop_server(immediate=False) else: + sleep(2) proxy = proxy_list[position] if proxy_type: main_viewer(proxy_type, proxy, position) + elif '|' in proxy: + splitted = proxy.split('|') + main_viewer(splitted[-1], splitted[0], position) else: main_viewer('http', proxy, position) if checked[position] == 'http': @@ -637,27 +793,28 @@ def view_video(position): main_viewer('socks5', proxy, position) -def clean_exit(executor): - executor.shutdown(wait=False) - - driver_list_ = list(driver_list) - for driver in driver_list_: - quit_driver(driver, None) - - while True: - try: - work_item = executor._work_queue.get_nowait() - except queue.Empty: - break - - if work_item is not None: - work_item.future.cancel() - - def main(): - global start_time, total_proxies, proxy_list, threads, futures + global cancel_all, proxy_list, total_proxies, threads, hash_config, futures + cancel_all = False start_time = time() + hash_config = get_hash(config_path) + + proxy_list = get_proxy_list() + if category != 'r': + print(bcolors.OKCYAN + + f'Total proxies : {len(proxy_list)}' + bcolors.ENDC) + + proxy_list = [x for x in proxy_list if x not in bad_proxies] + if len(proxy_list) == 0: + bad_proxies.clear() + proxy_list = get_proxy_list() + if proxy_list[0] != 'dummy': + proxy_list.insert(0, 'dummy') + if proxy_list[-1] != 'dummy': + proxy_list.append('dummy') + total_proxies = len(proxy_list) + threads = randint(min_threads, max_threads) if api: threads += 1 @@ -668,69 +825,80 @@ def main(): futures = [executor.submit(view_video, position) for position in pool_number] + done, not_done = wait(futures, timeout=0) try: - for future in as_completed(futures): + while not_done: + freshly_done, not_done = wait(not_done, timeout=1) + done |= freshly_done + sleep(15) if len(view) >= views: - print( - bcolors.WARNING + f'Amount of views added : {views} | Stopping program...' + bcolors.ENDC) + print(timestamp() + bcolors.WARNING + + f'Amount of views added : {views} | Stopping program...' + bcolors.ENDC) + create_html( + {"#f3f342": f'Amount of views added : {views} | Stopping program...'}) - clean_exit(executor) - stop_server() + cancel_pending_task(not_done=not_done) break - elif refresh != 0: - - if (time() - start_time) > refresh*60: + elif hash_config != get_hash(config_path): + hash_config = get_hash(config_path) + print(timestamp() + bcolors.WARNING + + 'Modified config.json will be in effect soon...' + bcolors.ENDC) + create_html( + {"#f3f342": 'Modified config.json will be in effect soon...'}) - if filename: - if proxy_api: - proxy_list = scrape_api(filename) - else: - proxy_list = load_proxy(filename) - else: - proxy_list = gather_proxy() + cancel_pending_task(not_done=not_done) + break - print(bcolors.WARNING + - f'Proxy reloaded from : {filename}' + bcolors.ENDC) + elif refresh != 0 and category != 'r': - total_proxies = len(proxy_list) - print(bcolors.OKCYAN + - f'Total proxies : {total_proxies}' + bcolors.ENDC) + if (time() - start_time) > refresh*60: + start_time = time() - proxy_list.insert(0, 'dummy') - proxy_list.append('dummy') + proxy_list_new = get_proxy_list() + proxy_list_new = [ + x for x in proxy_list_new if x not in bad_proxies] - total_proxies += 2 + proxy_list_old = [ + x for x in proxy_list[1:-1] if x not in bad_proxies] - clean_exit(executor) - stop_server() - break + if sorted(proxy_list_new) != sorted(proxy_list_old): + print(timestamp() + bcolors.WARNING + + f'Refresh {refresh} minute triggered. Proxies will be reloaded soon...' + bcolors.ENDC) + create_html( + {"#f3f342": f'Refresh {refresh} minute triggered. Proxies will be reloaded soon...'}) - future.result() + cancel_pending_task(not_done=not_done) + break except KeyboardInterrupt: - clean_exit(executor) - executor._threads.clear() - concurrent.futures.thread._threads_queues.clear() - stop_server(immediate=True) - sys.exit() + print(timestamp() + bcolors.WARNING + + 'Hold on!!! Allow me a moment to close all the running drivers.' + bcolors.ENDC) + create_html( + {"#f3f342": 'Hold on!!! Allow me a moment to close all the running drivers.'}) + + cancel_pending_task(not_done=not_done) + raise KeyboardInterrupt if __name__ == '__main__': clean_exe_temp(folder='youtube_viewer') + date_fmt = datetime.now().strftime("%d-%b-%Y %H:%M:%S") + cpu_usage = str(psutil.cpu_percent()) update_chrome_version() check_update() osname, exe_name = download_driver(patched_drivers=patched_drivers) create_database(database=DATABASE, database_backup=DATABASE_BACKUP) + if osname == 'win': + import wmi + constructor = wmi.WMI() + urls = load_url() queries = load_search() - hash_urls = get_hash("urls.txt") - hash_queries = get_hash("search.txt") - if os.path.isfile(config_path): with open(config_path, 'r') as openfile: config = json.load(openfile) @@ -741,79 +909,58 @@ def main(): bcolors.OKBLUE + 'Config file exists! Do you want to continue with previous saved preferences ? [Yes/No] : ' + bcolors.ENDC)).lower() if previous == 'n' or previous == 'no': create_config(config_path=config_path) - else: - pass else: print(bcolors.FAIL + 'Previous config file is not compatible with the latest script! Create a new one...' + bcolors.ENDC) create_config(config_path=config_path) else: create_config(config_path=config_path) - with open(config_path, 'r') as openfile: - config = json.load(openfile) - - api = config["http_api"]["enabled"] - host = config["http_api"]["host"] - port = config["http_api"]["port"] - database = config["database"] - views = config["views"] - minimum = config["minimum"] / 100 - maximum = config["maximum"] / 100 - category = config["proxy"]["category"] - proxy_type = config["proxy"]["proxy_type"] - filename = config["proxy"]["filename"] - auth_required = config["proxy"]["authentication"] - proxy_api = config["proxy"]["proxy_api"] - refresh = config["proxy"]["refresh"] - background = config["background"] - bandwidth = config["bandwidth"] - playback_speed = config["playback_speed"] - max_threads = config["max_threads"] - min_threads = config["min_threads"] - - if auth_required and background: - print(bcolors.FAIL + - "Premium proxy needs extension to work. Chrome doesn't support extension in Headless mode." + bcolors.ENDC) - input(bcolors.WARNING + - f"Either use proxy without username & password or disable headless mode " + bcolors.ENDC) - sys.exit() - - copy_drivers(cwd=cwd, patched_drivers=patched_drivers, - exe=exe_name, total=max_threads) - - if filename: - if category == 'r': - proxy_list = [filename] - proxy_list = proxy_list * 1000 - else: - if proxy_api: - proxy_list = scrape_api(filename) - else: - proxy_list = load_proxy(filename) - - else: - proxy_list = gather_proxy() - - total_proxies = len(proxy_list) - if category != 'r': - print(bcolors.OKCYAN + - f'Total proxies : {total_proxies}' + bcolors.ENDC) - - proxy_list.insert(0, 'dummy') - proxy_list.append('dummy') - - total_proxies += 2 + hash_urls = get_hash("urls.txt") + hash_queries = get_hash("search.txt") + hash_config = get_hash(config_path) - check = -1 while len(view) < views: try: - check += 1 - if check == 0: - main() - else: - sleeping() - print(bcolors.WARNING + - f'Total Checked : {check} times' + bcolors.ENDC) - main() + with open(config_path, 'r') as openfile: + config = json.load(openfile) + + if cancel_all: + print(json.dumps(config, indent=4)) + api = config["http_api"]["enabled"] + host = config["http_api"]["host"] + port = config["http_api"]["port"] + database = config["database"] + views = config["views"] + minimum = config["minimum"] / 100 + maximum = config["maximum"] / 100 + category = config["proxy"]["category"] + proxy_type = config["proxy"]["proxy_type"] + filename = config["proxy"]["filename"] + auth_required = config["proxy"]["authentication"] + proxy_api = config["proxy"]["proxy_api"] + refresh = config["proxy"]["refresh"] + background = config["background"] + bandwidth = config["bandwidth"] + playback_speed = config["playback_speed"] + max_threads = config["max_threads"] + min_threads = config["min_threads"] + + if minimum >= maximum: + minimum = maximum - 5 + + if min_threads >= max_threads: + max_threads = min_threads + + if auth_required and background: + print(bcolors.FAIL + + "Premium proxy needs extension to work. Chrome doesn't support extension in Headless mode." + bcolors.ENDC) + input(bcolors.WARNING + + f"Either use proxy without username & password or disable headless mode " + bcolors.ENDC) + sys.exit() + + copy_drivers(cwd=cwd, patched_drivers=patched_drivers, + exe=exe_name, total=max_threads) + + main() except KeyboardInterrupt: sys.exit() diff --git a/youtubeviewer/__init__.py b/youtubeviewer/__init__.py index e69de29..49d37fb 100644 --- a/youtubeviewer/__init__.py +++ b/youtubeviewer/__init__.py @@ -0,0 +1,23 @@ +""" +MIT License + +Copyright (c) 2021-2022 MShawon + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" \ No newline at end of file diff --git a/youtubeviewer/basics.py b/youtubeviewer/basics.py index b5db67c..34d23d7 100644 --- a/youtubeviewer/basics.py +++ b/youtubeviewer/basics.py @@ -1,13 +1,31 @@ +""" +MIT License + +Copyright (c) 2021-2022 MShawon + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" import os -import zipfile from glob import glob -from selenium import webdriver -from selenium.common.exceptions import NoSuchElementException - from .features import * - WEBRTC = os.path.join('extension', 'webrtc_control.zip') ACTIVE = os.path.join('extension', 'always_active.zip') FINGERPRINT = os.path.join('extension', 'fingerprint_defender.zip') @@ -16,16 +34,81 @@ glob(os.path.join('extension', 'custom_extension', '*.crx')) -def get_driver(background, viewports, agent, auth_required, path, proxy, proxy_type, pluginfile): +def create_proxy_folder(proxy, folder_name): + proxy = proxy.replace('@', ':') + proxy = proxy.split(':') + manifest_json = """ +{ + "version": "1.0.0", + "manifest_version": 2, + "name": "Chrome Proxy", + "permissions": [ + "proxy", + "tabs", + "unlimitedStorage", + "storage", + "", + "webRequest", + "webRequestBlocking" + ], + "background": { + "scripts": ["background.js"] + }, + "minimum_chrome_version":"22.0.0" +} + """ + + background_js = """ +var config = { + mode: "fixed_servers", + rules: { + singleProxy: { + scheme: "http", + host: "%s", + port: parseInt(%s) + }, + bypassList: ["localhost"] + } + }; +chrome.proxy.settings.set({value: config, scope: "regular"}, function() {}); +function callbackFn(details) { + return { + authCredentials: { + username: "%s", + password: "%s" + } + }; +} +chrome.webRequest.onAuthRequired.addListener( + callbackFn, + {urls: [""]}, + ['blocking'] +); +""" % (proxy[2], proxy[-1], proxy[0], proxy[1]) + + os.makedirs(folder_name, exist_ok=True) + with open(os.path.join(folder_name, "manifest.json"), 'w') as fh: + fh.write(manifest_json) + + with open(os.path.join(folder_name, "background.js"), 'w') as fh: + fh.write(background_js) + + +def get_driver(background, viewports, agent, auth_required, path, proxy, proxy_type, proxy_folder): options = webdriver.ChromeOptions() options.headless = background - options.add_argument(f"--window-size={choice(viewports)}") + if viewports: + options.add_argument(f"--window-size={choice(viewports)}") options.add_argument("--log-level=3") options.add_experimental_option( "excludeSwitches", ["enable-automation", "enable-logging"]) options.add_experimental_option('useAutomationExtension', False) - options.add_experimental_option( - 'prefs', {'intl.accept_languages': 'en_US,en'}) + prefs = {"intl.accept_languages": 'en_US,en', + "credentials_enable_service": False, + "profile.password_manager_enabled": False, + "profile.default_content_setting_values.notifications": 2, + "download_restrictions": 3} + options.add_experimental_option("prefs", prefs) options.add_argument(f"user-agent={agent}") options.add_argument("--mute-audio") options.add_argument('--no-sandbox') @@ -46,69 +129,13 @@ def get_driver(background, viewports, agent, auth_required, path, proxy, proxy_t options.add_extension(extension) if auth_required: - proxy = proxy.replace('@', ':') - proxy = proxy.split(':') - manifest_json = """ - { - "version": "1.0.0", - "manifest_version": 2, - "name": "Chrome Proxy", - "permissions": [ - "proxy", - "tabs", - "unlimitedStorage", - "storage", - "", - "webRequest", - "webRequestBlocking" - ], - "background": { - "scripts": ["background.js"] - }, - "minimum_chrome_version":"22.0.0" - } - """ - - background_js = """ - var config = { - mode: "fixed_servers", - rules: { - singleProxy: { - scheme: "http", - host: "%s", - port: parseInt(%s) - }, - bypassList: ["localhost"] - } - }; - - chrome.proxy.settings.set({value: config, scope: "regular"}, function() {}); - - function callbackFn(details) { - return { - authCredentials: { - username: "%s", - password: "%s" - } - }; - } - - chrome.webRequest.onAuthRequired.addListener( - callbackFn, - {urls: [""]}, - ['blocking'] - ); - """ % (proxy[2], proxy[-1], proxy[0], proxy[1]) - - with zipfile.ZipFile(pluginfile, 'w') as zp: - zp.writestr("manifest.json", manifest_json) - zp.writestr("background.js", background_js) - options.add_extension(pluginfile) - + create_proxy_folder(proxy, proxy_folder) + options.add_argument(f"--load-extension={proxy_folder}") else: options.add_argument(f'--proxy-server={proxy_type}://{proxy}') - driver = webdriver.Chrome(executable_path=path, options=options) + service = Service(executable_path=path) + driver = webdriver.Chrome(service=service, options=options) return driver @@ -116,53 +143,49 @@ def get_driver(background, viewports, agent, auth_required, path, proxy, proxy_t def play_video(driver): try: driver.find_element(By.CSS_SELECTOR, '[title^="Pause (k)"]') - except: + except WebDriverException: try: driver.find_element( By.CSS_SELECTOR, 'button.ytp-large-play-button.ytp-button').send_keys(Keys.ENTER) - except: + except WebDriverException: try: driver.find_element( By.CSS_SELECTOR, '[title^="Play (k)"]').click() - except: + except WebDriverException: try: driver.execute_script( "document.querySelector('button.ytp-play-button.ytp-button').click()") - except: + except WebDriverException: pass - try: - skip_ad = driver.find_element( - By.CLASS_NAME, "ytp-ad-skip-button-container") - skip_ad.click() - except: - pass + skip_again(driver) def play_music(driver): try: driver.find_element( By.XPATH, '//*[@id="play-pause-button" and @title="Pause"]') - except: + except WebDriverException: try: driver.find_element( By.XPATH, '//*[@id="play-pause-button" and @title="Play"]').click() - except: + except WebDriverException: driver.execute_script( 'document.querySelector("#play-pause-button").click()') + skip_again(driver) + def type_keyword(driver, keyword, retry=False): input_keyword = driver.find_element(By.CSS_SELECTOR, 'input#search') if retry: - for _ in range(10): + for _ in range(30): try: input_keyword.click() break - except: - sleep(5) - pass + except WebDriverException: + sleep(3) input_keyword.clear() for letter in keyword: @@ -173,44 +196,66 @@ def type_keyword(driver, keyword, retry=False): if method == 1: input_keyword.send_keys(Keys.ENTER) else: - try: - driver.find_element( - By.XPATH, '//*[@id="search-icon-legacy"]').click() - except: - driver.execute_script( - 'document.querySelector("#search-icon-legacy").click()') + icon = driver.find_element( + By.XPATH, '//button[@id="search-icon-legacy"]') + ensure_click(driver, icon) -def search_video(driver, keyword, video_title): - i = 0 - try: - type_keyword(driver, keyword) - except: - try: - bypass_popup(driver) - type_keyword(driver, keyword, retry=True) - except: - return i - +def scroll_search(driver, video_title): + msg = None for i in range(1, 11): try: section = WebDriverWait(driver, 60).until(EC.visibility_of_element_located( (By.XPATH, f'//ytd-item-section-renderer[{i}]'))) + if driver.find_element(By.XPATH, f'//ytd-item-section-renderer[{i}]').text == 'No more results': + msg = 'failed' + break find_video = section.find_element( By.XPATH, f'//*[@title="{video_title}"]') driver.execute_script( "arguments[0].scrollIntoViewIfNeeded();", find_video) sleep(1) bypass_popup(driver) - try: - find_video.click() - except: - driver.execute_script( - "arguments[0].click();", find_video) + ensure_click(driver, find_video) + msg = 'success' break except NoSuchElementException: - sleep(5) + sleep(randint(2, 5)) WebDriverWait(driver, 30).until(EC.visibility_of_element_located( (By.TAG_NAME, 'body'))).send_keys(Keys.CONTROL, Keys.END) - return i + if i == 10: + msg = 'failed' + + return msg + + +def search_video(driver, keyword, video_title): + try: + type_keyword(driver, keyword) + except WebDriverException: + try: + bypass_popup(driver) + type_keyword(driver, keyword, retry=True) + except WebDriverException: + raise Exception( + "Slow internet speed or Stuck at recaptcha! Can't perfrom search keyword") + + msg = scroll_search(driver, video_title) + + if msg == 'failed': + bypass_popup(driver) + + filters = driver.find_element(By.CSS_SELECTOR, '#filter-menu a') + driver.execute_script('arguments[0].scrollIntoViewIfNeeded()', filters) + sleep(randint(1, 3)) + ensure_click(driver, filters) + + sleep(randint(1, 3)) + sort = WebDriverWait(driver, 30).until(EC.element_to_be_clickable( + (By.XPATH, '//div[@title="Sort by upload date"]'))) + ensure_click(driver, sort) + + msg = scroll_search(driver, video_title) + + return msg diff --git a/youtubeviewer/bypass.py b/youtubeviewer/bypass.py index 1c67720..d6918d5 100644 --- a/youtubeviewer/bypass.py +++ b/youtubeviewer/bypass.py @@ -1,11 +1,46 @@ -from random import choice, shuffle +""" +MIT License + +Copyright (c) 2021-2022 MShawon + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" +from random import choice, choices, randint, shuffle, uniform from time import sleep +from selenium import webdriver +from selenium.common.exceptions import (NoSuchElementException, + WebDriverException) +from selenium.webdriver.chrome.service import Service from selenium.webdriver.common.by import By +from selenium.webdriver.common.keys import Keys from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.ui import WebDriverWait +def ensure_click(driver, element): + try: + element.click() + except WebDriverException: + driver.execute_script("arguments[0].click();", element) + + def personalization(driver): search = driver.find_element( By.XPATH, f'//button[@aria-label="Turn {choice(["on","off"])} Search customization"]') @@ -34,7 +69,7 @@ def bypass_consent(driver): consent.click() if 'consent' in driver.current_url: personalization(driver) - except: + except WebDriverException: consent = driver.find_element( By.XPATH, "//input[@type='submit' and @value='I agree']") driver.execute_script("arguments[0].scrollIntoView();", consent) @@ -43,38 +78,25 @@ def bypass_consent(driver): personalization(driver) -def bypass_signin(driver): - for _ in range(10): - sleep(2) - try: - nothanks = driver.find_element( - By.CLASS_NAME, "style-scope.yt-button-renderer.style-text.size-small") - nothanks.click() - sleep(1) - driver.switch_to.frame(driver.find_element(By.ID, "iframe")) - iagree = driver.find_element(By.ID, 'introAgreeButton') - iagree.click() - driver.switch_to.default_content() - except: - try: - driver.switch_to.frame(driver.find_element(By.ID, "iframe")) - iagree = driver.find_element(By.ID, 'introAgreeButton') - iagree.click() - driver.switch_to.default_content() - except: - pass +def click_popup(driver, element): + driver.execute_script( + "arguments[0].scrollIntoViewIfNeeded();", element) + sleep(1) + element.click() def bypass_popup(driver): try: agree = WebDriverWait(driver, 5).until(EC.visibility_of_element_located( (By.XPATH, '//*[@aria-label="Agree to the use of cookies and other data for the purposes described"]'))) - driver.execute_script( - "arguments[0].scrollIntoViewIfNeeded();", agree) - sleep(1) - agree.click() - except: - pass + click_popup(driver=driver, element=agree) + except WebDriverException: + try: + agree = driver.find_element( + By.XPATH, '//*[@aria-label="Accept the use of cookies and other data for the purposes described"]') + click_popup(driver=driver, element=agree) + except WebDriverException: + pass def bypass_other_popup(driver): @@ -85,5 +107,5 @@ def bypass_other_popup(driver): try: driver.find_element( By.XPATH, f"//*[@id='button' and @aria-label='{popup}']").click() - except: + except WebDriverException: pass diff --git a/youtubeviewer/colors.py b/youtubeviewer/colors.py index f733730..2cf3c46 100644 --- a/youtubeviewer/colors.py +++ b/youtubeviewer/colors.py @@ -1,7 +1,31 @@ +""" +MIT License + +Copyright (c) 2021-2022 MShawon + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" import os os.system("") + class bcolors: HEADER = '\033[95m' OKBLUE = '\033[94m' diff --git a/youtubeviewer/config.py b/youtubeviewer/config.py index f4440ae..7c2b4b4 100644 --- a/youtubeviewer/config.py +++ b/youtubeviewer/config.py @@ -1,164 +1,234 @@ +""" +MIT License + +Copyright (c) 2021-2022 MShawon + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" import json -import sys -from .colors import * +PROXY_TYPES = { + '1': 'http', + '2': 'socks4', + '3': 'socks5', + '4': False +} -def create_config(config_path): - print(bcolors.WARNING + 'Your preferences will be saved so that you don\'t need to answer these questions again.' + bcolors.ENDC) - print(bcolors.WARNING + 'Just Hit Enter to accept default or recommended values without typing anything.' + bcolors.ENDC) - - config = {} +def config_api(config): port = 5000 - auth_required = False - proxy_api = False - http_api = str(input( bcolors.OKBLUE + '\nDo you want to enable a HTTP API on local server? (default=Yes) [Yes/no] : ' + bcolors.ENDC)).lower() - if http_api == 'y' or http_api == 'yes' or http_api == '': + if http_api == 'n' or http_api == 'no': + enabled = False + else: enabled = True port = input(bcolors.OKCYAN + '\nEnter a free port (default=5000) : ' + bcolors.ENDC) - if port == '': - port = 5000 - else: + try: port = int(port) - - else: - enabled = False + except Exception: + port = 5000 config["http_api"] = { "enabled": enabled, "host": "0.0.0.0", "port": port } + return config + +def config_database(config): database = str(input( bcolors.OKBLUE + '\nDo you want to store your daily generated view counts in a Database ? (default=Yes) [Yes/no] : ' + bcolors.ENDC)).lower() - if database == 'y' or database == 'yes' or database == "": - database = True - else: + if database == 'n' or database == 'no': database = False - config["database"] = database + else: + database = True - views = int(input(bcolors.WARNING + '\nAmount of views : ' + bcolors.ENDC)) - config["views"] = views + config["database"] = database + return config + + +def config_views(config): + for _ in range(10): + try: + views = float( + input(bcolors.WARNING + '\nAmount of views you want : ' + bcolors.ENDC)) + break + except Exception as e: + print(bcolors.FAIL + e.args[0] + bcolors.ENDC) + print(bcolors.BOLD + 'Type a number like 1000 ' + bcolors.ENDC) + try: + config["views"] = int(views) + except Exception: + config["views"] = 100 + return config + + +def config_min_max(config): + print(bcolors.WARNING + + '\n--> Minimum and Maximum watch duration percentages have no impact on live streams.' + bcolors.ENDC) + print(bcolors.WARNING + + '--> For live streaming, script will play the video until the stream is finished.' + bcolors.ENDC) - print(bcolors.WARNING + '\nMinimum and Maximum watch duration percentages have no impact on live streams.' + bcolors.ENDC) - print(bcolors.WARNING + 'For live streaming, script will play the video until the stream is finished.' + bcolors.ENDC) - minimum = input( bcolors.WARNING + '\nMinimum watch duration in percentage (default = 85) : ' + bcolors.ENDC) - if minimum == '': - minimum = 85.0 - else: + try: minimum = float(minimum) - config["minimum"] = minimum + except Exception: + minimum = 85.0 maximum = input( bcolors.WARNING + '\nMaximum watch duration in percentage (default = 95) : ' + bcolors.ENDC) - if maximum == '': - maximum = 95.0 - else: + try: maximum = float(maximum) + except Exception: + maximum = 95.0 + + if minimum >= maximum: + minimum = maximum - 5 + + config["minimum"] = minimum config["maximum"] = maximum + return config - category = input(bcolors.OKCYAN + "\nWhat's your proxy category? " + - "[F = Free (without user:pass), P = Premium (with user:pass), R = Rotating Proxy] : " + bcolors.ENDC).lower() - if category == 'f': - handle_proxy = str(input( - bcolors.OKBLUE + '\nLet YouTube Viewer handle proxies ? (recommended=No) [No/yes] : ' + bcolors.ENDC)).lower() +def config_free_proxy(category): + auth_required = False + proxy_api = False - if handle_proxy == 'y' or handle_proxy == 'yes': - filename = False - proxy_type = False + handle_proxy = str(input( + bcolors.OKBLUE + '\nLet YouTube Viewer handle proxies ? (recommended=No) [No/yes] : ' + bcolors.ENDC)).lower() - else: - filename = input(bcolors.OKCYAN + - '\nEnter your proxy File Name or Proxy API link : ' + bcolors.ENDC) + if handle_proxy == 'y' or handle_proxy == 'yes': + proxy_type = False + filename = False - if 'http://' in filename or 'https://' in filename: - proxy_api = True + else: + filename = "" + while not filename: + filename = str(input( + bcolors.OKCYAN + '\nEnter your proxy File Name or Proxy API link : ' + bcolors.ENDC)) + + if 'http://' in filename or 'https://' in filename: + proxy_api = True + handle_proxy = str(input( + bcolors.OKBLUE + "\nSelect proxy type [1 = HTTP , 2 = SOCKS4, 3 = SOCKS5, 4 = ALL] : " + bcolors.ENDC)).lower() + while handle_proxy not in ['1', '2', '3', '4']: handle_proxy = str(input( - bcolors.OKBLUE + "\nSelect proxy type [1 = HTTP , 2 = SOCKS4, 3 = SOCKS5, 4 = ALL] : " + bcolors.ENDC)).lower() + '\nPlease input 1 for HTTP, 2 for SOCKS4, 3 for SOCKS5 and 4 for ALL proxy type : ')).lower() + + proxy_type = PROXY_TYPES[handle_proxy] - if handle_proxy == '1': + return proxy_type, filename, auth_required, proxy_api + + +def config_premium_proxy(category): + auth_required = False + proxy_api = False + + if category == 'r': + print(bcolors.WARNING + '\n--> If you use the Proxy API link, script will scrape the proxy list on each thread start.' + bcolors.ENDC) + print(bcolors.WARNING + '--> And will use one proxy randomly from that list to ensure session management.' + bcolors.ENDC) + + filename = "" + while not filename: + filename = str(input( + bcolors.OKCYAN + '\nEnter your Rotating Proxy service Main Gateway or Proxy API link : ' + bcolors.ENDC)) + + if 'http://' in filename or 'https://' in filename: + proxy_api = True + auth_required = input(bcolors.OKCYAN + + '\nProxies need authentication? (default=No) [No/yes] : ' + bcolors.ENDC).lower() + if auth_required == 'y' or auth_required == 'yes': + auth_required = True proxy_type = 'http' - elif handle_proxy == '2': - proxy_type = 'socks4' - elif handle_proxy == '3': - proxy_type = 'socks5' - elif handle_proxy == '4': - proxy_type = False else: - input( - '\nPlease input 1 for HTTP, 2 for SOCKS4, 3 for SOCKS5 and 4 for ALL proxy type ') - sys.exit() + auth_required = False - elif category == 'p' or category == 'r': - if category == 'r': - print(bcolors.WARNING + '\nIf you use the Proxy API link, script will scrape the proxy list on each thread start.' + bcolors.ENDC) - print(bcolors.WARNING + 'And will use one proxy randomly from that list to ensure session management.' + bcolors.ENDC) - filename = input(bcolors.OKCYAN + - '\nEnter your Rotating Proxy service Main Gateway or Proxy API link : ' + bcolors.ENDC) - - if 'http://' in filename or 'https://' in filename: - proxy_api = True - auth_required = input(bcolors.OKCYAN + - '\nProxies need authentication? (default=No) [No/yes] : ' + bcolors.ENDC).lower() - if auth_required == 'y' or auth_required == 'yes' or auth_required == "": - auth_required = True - proxy_type = 'http' - else: - auth_required = False + else: + if '@' in filename: + auth_required = True + proxy_type = 'http' + elif filename.count(':') == 3: + split = filename.split(':') + filename = f'{split[2]}:{split[-1]}@{split[0]}:{split[1]}' + auth_required = True + proxy_type = 'http' - else: - if '@' in filename: - auth_required = True - proxy_type = 'http' - elif filename.count(':') == 3: - split = filename.split(':') - filename = f'{split[2]}:{split[-1]}@{split[0]}:{split[1]}' - auth_required = True - proxy_type = 'http' - - if not auth_required: + if not auth_required: + handle_proxy = str(input( + bcolors.OKBLUE + "\nSelect proxy type [1 = HTTP , 2 = SOCKS4, 3 = SOCKS5] : " + bcolors.ENDC)).lower() + while handle_proxy not in ['1', '2', '3']: handle_proxy = str(input( - bcolors.OKBLUE + "\nSelect proxy type [1 = HTTP , 2 = SOCKS4, 3 = SOCKS5] : " + bcolors.ENDC)).lower() - - if handle_proxy == '1': - proxy_type = 'http' - elif handle_proxy == '2': - proxy_type = 'socks4' - elif handle_proxy == '3': - proxy_type = 'socks5' - else: - input( - '\nPlease input 1 for HTTP, 2 for SOCKS4 and 3 for SOCKS5 proxy type ') - sys.exit() + '\nPlease input 1 for HTTP, 2 for SOCKS4 and 3 for SOCKS5 proxy type : ')).lower() + + proxy_type = PROXY_TYPES[handle_proxy] - else: - filename = input(bcolors.OKCYAN + - '\nEnter your proxy File Name or Proxy API link : ' + bcolors.ENDC) - auth_required = True - proxy_type = 'http' - if 'http://' in filename or 'https://' in filename: - proxy_api = True else: - input('\nPlease input F for Free, P for Premium and R for Rotating proxy ') - sys.exit() + filename = "" + while not filename: + filename = str(input( + bcolors.OKCYAN + '\nEnter your proxy File Name or Proxy API link : ' + bcolors.ENDC)) + auth_required = True + proxy_type = 'http' + if 'http://' in filename or 'https://' in filename: + proxy_api = True + + return proxy_type, filename, auth_required, proxy_api + + +def config_proxy(config): + print(bcolors.WARNING + + '\n--> Free proxy : NO authentication required | Format : [IP:PORT] | Type : HTTP/SOCKS4/SOCKS5' + bcolors.ENDC) + print(bcolors.WARNING + + '--> Premium proxy : authentication required | Format : [USER:PASS@IP:PORT] or [IP:PORT:USER:PASS] | Type : HTTP only' + bcolors.ENDC) + print(bcolors.WARNING + '--> Rotating proxy : follows Free proxy for no authentication and vice versa' + bcolors.ENDC) + category = input(bcolors.OKCYAN + "\nWhat's your proxy category? " + + "[F = Free, P = Premium, R = Rotating Proxy] : " + bcolors.ENDC).lower() + while category not in ['f', 'p', 'r']: + category = input( + '\nPlease input F for Free, P for Premium and R for Rotating proxy : ').lower() + + if category == 'f': + proxy_type, filename, auth_required, proxy_api = config_free_proxy( + category) + + elif category == 'p' or category == 'r': + proxy_type, filename, auth_required, proxy_api = config_premium_proxy( + category) refresh = 0.0 if category != 'r': - print(bcolors.WARNING + '\nRefresh interval means after every X minutes, program will reload proxies from your File or API' + bcolors.ENDC) - print(bcolors.WARNING + 'You should use this if and only if there will be new proxies in your File or API after every X minutes.' + bcolors.ENDC) - print(bcolors.WARNING + 'Otherwise just enter 0 as the interval' + bcolors.ENDC) - refresh = float(input( - bcolors.OKCYAN+'\nEnter a interval to reload proxies from File or API (in minute) : ' + bcolors.ENDC)) + print(bcolors.WARNING + '\n--> Refresh interval means after every X minutes, program will reload proxies from your File or API' + bcolors.ENDC) + print(bcolors.WARNING + '--> You should use this if and only if there will be new proxies in your File or API after every X minutes.' + bcolors.ENDC) + print(bcolors.WARNING + + '--> Otherwise just enter 0 as the interval.' + bcolors.ENDC) + try: + refresh = abs(float(input( + bcolors.OKCYAN+'\nEnter a interval to reload proxies from File or API (in minute) [default=0]: ' + bcolors.ENDC))) + except Exception: + refresh = 0.0 config["proxy"] = { "category": category, @@ -168,7 +238,10 @@ def create_config(config_path): "proxy_api": proxy_api, "refresh": refresh } + return config + +def config_gui(config): gui = str(input( bcolors.OKCYAN + '\nDo you want to run in headless(background) mode? (recommended=No) [No/yes] : ' + bcolors.ENDC)).lower() @@ -177,6 +250,11 @@ def create_config(config_path): else: background = False + config["background"] = background + return config + + +def config_bandwidth(config): bandwidth = str(input( bcolors.OKBLUE + '\nReduce video quality to save Bandwidth? (recommended=No) [No/yes] : ' + bcolors.ENDC)).lower() @@ -185,44 +263,87 @@ def create_config(config_path): else: bandwidth = False + config["bandwidth"] = bandwidth + return config + + +def config_playback(config): playback_speed = input( bcolors.OKBLUE + '\nChoose Playback speed [1 = Normal(1x), 2 = Slow(random .25x, .5x, .75x), 3 = Fast(random 1.25x, 1.5x, 1.75x)] (default = 1) : ' + bcolors.ENDC) - if playback_speed == "": + try: + playback_speed = int(playback_speed) if playback_speed in [ + '2', '3'] else 1 + except Exception: playback_speed = 1 - else: - playback_speed = int(playback_speed) + config["playback_speed"] = playback_speed + return config + + +def config_threads(config): + print(bcolors.WARNING + + '\n--> Script will dynamically update thread amount when proxy reload happens.' + bcolors.ENDC) print(bcolors.WARNING + - '\nScript will dynamically update thread amount when proxy reload happens.' + bcolors.ENDC) - print(bcolors.WARNING + 'If you wish to use the same amount of threads all the time, enter the same number in Maximum and Minimum threads.' + bcolors.ENDC) + '--> If you wish to use the same amount of threads all the time, enter the same number in Maximum and Minimum threads.' + bcolors.ENDC) + max_threads = input( bcolors.OKCYAN + '\nMaximum Threads [Amount of chrome driver you want to use] (recommended = 5): ' + bcolors.ENDC) - if max_threads == '': - max_threads = 5 - else: + try: max_threads = int(max_threads) + except Exception: + max_threads = 5 min_threads = input( bcolors.OKCYAN + '\nMinimum Threads [Amount of chrome driver you want to use] (recommended = 2): ' + bcolors.ENDC) - if min_threads == '': - min_threads = 2 - else: + try: min_threads = int(min_threads) + except Exception: + min_threads = 2 + + if min_threads >= max_threads: + max_threads = min_threads - config["background"] = background - config["bandwidth"] = bandwidth - config["playback_speed"] = playback_speed config["max_threads"] = max_threads config["min_threads"] = min_threads + return config + + +def create_config(config_path): + print(bcolors.WARNING + '\n--> Your preferences will be saved so that you don\'t need to answer these questions again.' + bcolors.ENDC) + print(bcolors.WARNING + '--> Just Hit Enter to accept default or recommended values without typing anything.' + bcolors.ENDC) + + config = {} + + config = config_api(config=config) + + config = config_database(config=config) + + config = config_views(config=config) + + config = config_min_max(config=config) + + config = config_proxy(config=config) + + config = config_gui(config=config) + + config = config_bandwidth(config=config) + + config = config_playback(config=config) + + config = config_threads(config=config) json_object = json.dumps(config, indent=4) with open(config_path, "w") as outfile: outfile.write(json_object) - print(bcolors.OKGREEN + '\nYour preferences are saved in config.json. You can always create a new config file from youtube_viewer.py' + bcolors.ENDC) - print(bcolors.OKGREEN + 'Or by running `python config.py` ' + bcolors.ENDC) + print(bcolors.OKGREEN + '\n--> Your preferences are saved in config.json. You can always create a new config file from youtube_viewer.py' + bcolors.ENDC) + print(bcolors.OKGREEN + + '--> Or by running `python youtubeviewer/config.py` ' + bcolors.ENDC) if __name__ == '__main__': + from colors import * create_config(config_path='config.json') +else: + from .colors import * diff --git a/youtubeviewer/database.py b/youtubeviewer/database.py index 4a42e7c..00fcd20 100644 --- a/youtubeviewer/database.py +++ b/youtubeviewer/database.py @@ -1,7 +1,30 @@ -from contextlib import closing -import sqlite3 +""" +MIT License + +Copyright (c) 2021-2022 MShawon + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" import os import shutil +import sqlite3 +from contextlib import closing from datetime import datetime @@ -14,15 +37,13 @@ def create_database(database, database_backup): connection.commit() try: - # remove previous backup if exists os.remove(database_backup) - except: + except Exception: pass try: - # backup latest database shutil.copy(database, database_backup) - except: + except Exception: pass @@ -36,7 +57,7 @@ def update_database(database, threads, increment=1): previous_count = cursor.fetchone()[0] cursor.execute("UPDATE statistics SET view = ? WHERE date = ?", (previous_count + increment, today)) - except: + except Exception: cursor.execute( "INSERT INTO statistics VALUES (?, ?)", (today, 0),) diff --git a/youtubeviewer/download_driver.py b/youtubeviewer/download_driver.py index cc873fb..2912454 100644 --- a/youtubeviewer/download_driver.py +++ b/youtubeviewer/download_driver.py @@ -1,4 +1,26 @@ - +""" +MIT License + +Copyright (c) 2021-2022 MShawon + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" import platform import shutil import subprocess @@ -42,7 +64,7 @@ def download_driver(patched_drivers): ) version = process.communicate()[0].decode( 'UTF-8').strip().split()[-1] - except: + except Exception: for i in CHROME: for j in ['opv', 'pv']: try: @@ -54,7 +76,7 @@ def download_driver(patched_drivers): ) version = process.communicate()[0].decode( 'UTF-8').strip().split()[-1] - except: + except Exception: pass if not version: @@ -63,13 +85,13 @@ def download_driver(patched_drivers): version = input(bcolors.WARNING + 'Please input your google chrome version (ex: 91.0.4472.114) : ' + bcolors.ENDC) else: - print('{} OS is not supported.'.format(osname)) + input('{} OS is not supported.'.format(osname)) sys.exit() try: with open('version.txt', 'r') as f: previous_version = f.read() - except: + except Exception: previous_version = '0' with open('version.txt', 'w') as f: @@ -78,7 +100,7 @@ def download_driver(patched_drivers): if version != previous_version: try: os.remove(f'chromedriver{exe_name}') - except: + except Exception: pass shutil.rmtree(patched_drivers, ignore_errors=True) @@ -97,8 +119,8 @@ def copy_drivers(cwd, patched_drivers, exe, total): os.makedirs(patched_drivers, exist_ok=True) for i in range(total+1): try: - destination = os.path.join(patched_drivers, f'chromedriver_{i}{exe}') + destination = os.path.join( + patched_drivers, f'chromedriver_{i}{exe}') shutil.copy(current, destination) - except Exception as e: - print(e) + except Exception: pass diff --git a/youtubeviewer/features.py b/youtubeviewer/features.py index 4674cf9..f15498b 100644 --- a/youtubeviewer/features.py +++ b/youtubeviewer/features.py @@ -1,51 +1,89 @@ -from random import choices, randint, uniform - -from selenium.webdriver.common.keys import Keys - +""" +MIT License + +Copyright (c) 2021-2022 MShawon + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" from .bypass import * COMMANDS = [Keys.UP, Keys.DOWN, 'share', 'k', 'j', 'l', 't', 'c'] +QUALITY = { + 1: ['144p', "tiny"], + 2: ['240p', "small"], + 3: ['360p', "medium"] +} + + +def skip_again(driver): + try: + skip_ad = driver.find_element( + By.CLASS_NAME, "ytp-ad-skip-button-container") + driver.execute_script("arguments[0].click();", skip_ad) + except WebDriverException: + pass + def skip_initial_ad(driver, video, duration_dict): try: video_len = duration_dict[video] if video_len > 30: bypass_popup(driver) - skip_ad = WebDriverWait(driver, 15).until(EC.element_to_be_clickable( - (By.CLASS_NAME, "ytp-ad-skip-button-container"))) - - ad_duration = driver.find_element( - By.CLASS_NAME, 'ytp-time-duration').get_attribute('innerText') - ad_duration = sum(x * int(t) - for x, t in zip([60, 1], ad_duration.split(":"))) - ad_duration = ad_duration * uniform(.01, .1) - sleep(ad_duration) - skip_ad.click() - except: + try: + skip_ad = WebDriverWait(driver, 15).until(EC.element_to_be_clickable( + (By.CLASS_NAME, "ytp-ad-skip-button-container"))) + + ad_duration = driver.find_element( + By.CLASS_NAME, 'ytp-time-duration').get_attribute('innerText') + ad_duration = sum(x * int(t) + for x, t in zip([60, 1], ad_duration.split(":"))) + ad_duration = ad_duration * uniform(.01, .1) + sleep(ad_duration) + skip_ad.click() + except WebDriverException: + skip_again(driver) + except KeyError: pass def save_bandwidth(driver): + quality_index = choices([1, 2, 3], cum_weights=(0.7, 0.9, 1.00), k=1)[0] try: + random_quality = QUALITY[quality_index][0] driver.find_element( By.CSS_SELECTOR, "button.ytp-button.ytp-settings-button").click() driver.find_element( By.XPATH, "//div[contains(text(),'Quality')]").click() - random_quality = choices( - ['144p', '240p', '360p'], cum_weights=(0.7, 0.9, 1.00), k=1)[0] quality = WebDriverWait(driver, 10).until(EC.element_to_be_clickable( (By.XPATH, f"//span[contains(string(),'{random_quality}')]"))) driver.execute_script( "arguments[0].scrollIntoViewIfNeeded();", quality) quality.click() - except: + except WebDriverException: try: - driver.find_element( - By.XPATH, '//*[@id="container"]/h1/yt-formatted-string').click() - except: + random_quality = QUALITY[quality_index][1] + driver.execute_script( + f"document.getElementById('movie_player').setPlaybackQualityRange('{random_quality}')") + except WebDriverException: pass @@ -70,7 +108,7 @@ def random_command(driver): driver.find_element( By.ID, 'movie_player').send_keys(command) driver.execute_script( - f'document.querySelector("#comments"){choices(["scrollIntoView", "scrollIntoViewIfNeeded"])}();') + f'document.querySelector("#comments").{choice(["scrollIntoView", "scrollIntoViewIfNeeded"])}();') sleep(uniform(4, 10)) driver.execute_script( 'document.querySelector("#movie_player").scrollIntoViewIfNeeded();') @@ -86,10 +124,21 @@ def random_command(driver): else: driver.find_element(By.ID, 'movie_player').send_keys(command*randint(1, 5)) - except: + except WebDriverException: pass +def wait_for_new_page(driver, previous_url=False, previous_title=False): + for _ in range(30): + sleep(1) + if previous_url: + if driver.current_url != previous_url: + break + elif previous_title: + if driver.title != previous_title: + break + + def play_next_video(driver, suggested): shuffle(suggested) video_id = choice(suggested) @@ -117,13 +166,84 @@ def play_next_video(driver, suggested): driver.execute_script( "arguments[0].scrollIntoViewIfNeeded();", find_video) + previous_title = driver.title + ensure_click(driver, find_video) + wait_for_new_page(driver=driver, previous_url=False, + previous_title=previous_title) + + return driver.title[:-10] + + +def play_from_channel(driver, actual_channel): + channel = driver.find_elements( + By.CSS_SELECTOR, 'ytd-video-owner-renderer a')[randint(0, 1)] + driver.execute_script("arguments[0].scrollIntoViewIfNeeded();", channel) + previous_title = driver.title + ensure_click(driver, channel) + wait_for_new_page(driver=driver, previous_url=False, + previous_title=previous_title) + + channel_name = driver.title[:-10] + + if randint(1, 2) == 1: + x = randint(30, 50) + sleep(x) + output = driver.find_element( + By.XPATH, '//yt-formatted-string[@id="title"]/a').text + log = f'Video [{output}] played for {x} seconds from channel home page : {channel_name}' + option = 4 + else: + sleep(randint(2, 5)) + previous_url = driver.current_url + driver.find_element(By.XPATH, "//tp-yt-paper-tab[2]").click() + wait_for_new_page(driver=driver, previous_url=previous_url, + previous_title=False) + + driver.refresh() + videos = WebDriverWait(driver, 10).until( + EC.presence_of_all_elements_located((By.XPATH, "//a[@id='video-title']"))) + video = choice(videos) + driver.execute_script("arguments[0].scrollIntoViewIfNeeded();", video) + sleep(randint(2, 5)) + previous_title = driver.title + ensure_click(driver, video) + wait_for_new_page(driver=driver, previous_url=False, + previous_title=previous_title) + + output = driver.title[:-10] + log = f'Random video [{output}] played from channel : {channel_name}' + option = 2 + + channel_name = driver.find_element( + By.CSS_SELECTOR, '#upload-info a').text + if channel_name != actual_channel: + raise Exception( + f"Accidentally opened video {output} from another channel : {channel_name}. Closing it...") + + return output, log, option + + +def play_end_screen_video(driver): try: - find_video.click() - except: - driver.execute_script( - "arguments[0].click();", find_video) - - sleep(10) - video_title = driver.title[:-10] + driver.find_element(By.CSS_SELECTOR, '[title^="Pause (k)"]') + driver.find_element(By.ID, 'movie_player').send_keys('k') + except WebDriverException: + pass - return video_title \ No newline at end of file + total = driver.execute_script( + "return document.getElementById('movie_player').getDuration()") + driver.execute_script( + f"document.querySelector('#movie_player').seekTo({total}-{randint(2,5)})") + end_screen = WebDriverWait(driver, 5).until(EC.presence_of_all_elements_located( + (By.XPATH, "//*[@class='ytp-ce-covering-overlay']"))) + previous_title = driver.title + sleep(randint(2, 5)) + if end_screen: + ensure_click(driver, choice(end_screen)) + else: + raise Exception( + f'Unfortunately no end screen video found on this video : {previous_title[:-10]}') + wait_for_new_page(driver=driver, previous_url=False, + previous_title=previous_title) + + return driver.title[:-10] diff --git a/youtubeviewer/load_files.py b/youtubeviewer/load_files.py index 05a1a39..7ad3d58 100644 --- a/youtubeviewer/load_files.py +++ b/youtubeviewer/load_files.py @@ -1,3 +1,26 @@ +""" +MIT License + +Copyright (c) 2021-2022 MShawon + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" import hashlib from .colors import * @@ -31,5 +54,5 @@ def load_search(): def get_hash(path): with open(path, "rb") as f: current_hash = hashlib.md5(f.read()).hexdigest() - + return current_hash diff --git a/youtubeviewer/proxies.py b/youtubeviewer/proxies.py index 5569e55..d320125 100644 --- a/youtubeviewer/proxies.py +++ b/youtubeviewer/proxies.py @@ -1,3 +1,27 @@ +""" +MIT License + +Copyright (c) 2021-2022 MShawon + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" +import sys from random import shuffle import requests @@ -19,12 +43,20 @@ def gather_proxy(): for link in link_list: response = requests.get(link) output = response.content.decode() - proxy = output.split('\n') - proxies = proxies + proxy - print(bcolors.OKGREEN + - f'{len(proxy)} proxies gathered from {link}' + bcolors.ENDC) - proxies = list(filter(None, proxies)) + if '\r\n' in output: + proxy = output.split('\r\n') + else: + proxy = output.split('\n') + + for lines in proxy: + for line in lines.split('\n'): + proxies.append(line) + + print(bcolors.BOLD + f'{len(proxy)}' + bcolors.OKBLUE + + ' proxies gathered from ' + bcolors.OKCYAN + f'{link}' + bcolors.ENDC) + + proxies = list(set(filter(None, proxies))) shuffle(proxies) return proxies @@ -36,8 +68,13 @@ def load_proxy(filename): if not os.path.isfile(filename) and filename[-4:] != '.txt': filename = f'{filename}.txt' - with open(filename, encoding="utf-8") as fh: - loaded = [x.strip() for x in fh if x.strip() != ''] + try: + with open(filename, encoding="utf-8") as fh: + loaded = [x.strip() for x in fh if x.strip() != ''] + except Exception as e: + print(bcolors.FAIL + str(e) + bcolors.ENDC) + input('') + sys.exit() for lines in loaded: if lines.count(':') == 3: @@ -54,8 +91,14 @@ def load_proxy(filename): def scrape_api(link): proxies = [] - response = requests.get(link) - output = response.content.decode() + try: + response = requests.get(link) + output = response.content.decode() + except Exception as e: + print(bcolors.FAIL + str(e) + bcolors.ENDC) + input('') + sys.exit() + if '\r\n' in output: proxy = output.split('\r\n') else: diff --git a/youtubeviewer/web/static/app.js b/youtubeviewer/web/static/app.js index 6b1633b..08b5fd1 100644 --- a/youtubeviewer/web/static/app.js +++ b/youtubeviewer/web/static/app.js @@ -44,7 +44,7 @@ function queryLogs() { req.done(function (data) { var i; - for (i = 1; i < 21; i++) { + for (i = 1; i < 201; i++) { $('#logs-' + i).html(data.console[i - 1]); } }); diff --git a/youtubeviewer/web/templates/homepage.html b/youtubeviewer/web/templates/homepage.html index 50702fe..c5f4213 100644 --- a/youtubeviewer/web/templates/homepage.html +++ b/youtubeviewer/web/templates/homepage.html @@ -14,6 +14,14 @@ YouTube Viewer + @@ -30,8 +38,8 @@

Live Logs

-
- {% for n in range(1,21) %} +
+ {% for n in range(1,201) %}

{% endfor %}
diff --git a/youtubeviewer/website.py b/youtubeviewer/website.py index 9380019..0681b36 100644 --- a/youtubeviewer/website.py +++ b/youtubeviewer/website.py @@ -1,12 +1,36 @@ +""" +MIT License + +Copyright (c) 2021-2022 MShawon + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" import calendar import sqlite3 import warnings from contextlib import closing from datetime import date, datetime, timedelta -warnings.filterwarnings("ignore", category=Warning) from flask import Flask, jsonify, render_template, request +warnings.filterwarnings("ignore", category=Warning) + MONTHS = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'] @@ -22,11 +46,10 @@ def create_graph_data(dropdown_text): year = now.year today = now.date() days = False - number = False try: number = [int(s) for s in dropdown_text.split() if s.isdigit()][0] - except: - pass + except Exception: + number = False if number: if dropdown_text.startswith('Last'): @@ -67,7 +90,7 @@ def create_graph_data(dropdown_text): total += view[0][0] else: graph_data.append([i[-2:], 0]) - except: + except Exception: pass return graph_data, total, first_date, last_date @@ -96,7 +119,7 @@ def shutdown_server(): func() -def start_server(host, port): +def start_server(host, port, debug=False): app = Flask(__name__, static_url_path='', static_folder='web/static', @@ -109,7 +132,7 @@ def home(): @app.route('/update', methods=['POST']) def update(): - return jsonify({'result': 'success', 'console': console[-20:]}) + return jsonify({'result': 'success', 'console': console[:200]}) @app.route('/graph', methods=['GET', 'POST']) def graph(): @@ -130,8 +153,8 @@ def shutdown(): shutdown_server() return 'Server shutting down...' - app.run(host=host, port=port) + app.run(host=host, port=port, debug=debug) if __name__ == '__main__': - start_server(host='0.0.0.0', port=5000) + start_server(host='0.0.0.0', port=5000, debug=True)