From 771dd2025931ed6b70dcd945e9973ef024cc45c8 Mon Sep 17 00:00:00 2001 From: insert Date: Sat, 31 May 2025 19:00:17 -0400 Subject: [PATCH] support changing proxy without restarting the bot --- .env.example | 2 +- newbot.py | 244 +++++++++++++++++++++++++++++++++------------------ 2 files changed, 162 insertions(+), 84 deletions(-) diff --git a/.env.example b/.env.example index feafe6c..600de66 100644 --- a/.env.example +++ b/.env.example @@ -8,4 +8,4 @@ MAX_QUEUE="5" ALLOW_SKIP="TRUE" PERMANENT_MAX_QUEUE="FALSE" LOCK_SHUFFLE="FALSE" -PROXY="socks5://your-proxy-here-or-comment-out-the-line-in-newbot.py" \ No newline at end of file +USE_PROXY="TRUE MEANING YOU HAVE TO EDIT THE PROXIES LIST IN THE FILE OR FALSE" \ No newline at end of file diff --git a/newbot.py b/newbot.py index f8dbb77..2318cbd 100644 --- a/newbot.py +++ b/newbot.py @@ -15,8 +15,9 @@ from disnake.ext import commands, tasks from disnake import TextInputStyle import asyncio from dotenv import load_dotenv -import os, sys +import os, sys, random import datetime +import re import logging import math from tempfile import TemporaryDirectory @@ -36,6 +37,7 @@ shuffle = True retries = 0 failures = 0 vidcounter = 0 +proxies = [] temp_dir = TemporaryDirectory() vid_dir = Path(temp_dir.name) download_dir = TemporaryDirectory() @@ -43,31 +45,118 @@ full_dl_dir = Path(download_dir.name) vid_details = {"title": "", "channel": ""} con = sqlite3.connect(os.getenv("QUEUE_PATH","queue.db"), check_same_thread=False) cur = con.cursor() -options = Options() -options.add_argument("--headless") -driver = webdriver.Firefox(options=options) -print("Getting potoken from firefox...") -driver.get("https://www.youtube.com/embed/aqz-KE-bpKQ") -elem = WebDriverWait(driver, 8, 0.2, None).until(lambda x: x.find_element(By.CLASS_NAME, "html5-video-player")) -sleep(1) -elem.click() -sleep(2) -for r in driver.requests: - if "https://www.youtube.com/youtubei/v1/player" in r.url: - potoken = json.loads(r.body)['serviceIntegrityDimensions']["poToken"] - break -driver.get("https://youtube.com") -cookiesstr = to_netscape_string(driver.get_cookies()) -with open("cookies.txt", 'w') as f: - f.write(f'# Netscape HTTP Cookie File\n# https://curl.haxx.se/rfc/cookie_spec.html\n# This is a generated file! Do not edit.\n\n{cookiesstr}') -driver.close() -print(f"Success! Potoken is {potoken}") + +ydl = None +def initytdl(): + global ydl + if ydl: + ydl.close() + if os.getenv("USE_PROXY","FALSE") == "FALSE": + obs.set_current_program_scene("banned") + ydl = None #In case of any rouge downloaders + con.close() + return + options = Options() + options.add_argument("--headless") + if os.getenv("USE_PROXY","FALSE") == "TRUE": + if not proxies: + obs.set_current_program_scene("banned") + ydl = None #In case of any rouge downloaders + con.close() + return + index = random.randrange(len(proxies)) + proxy = proxies[index] + proxies.pop(index) + proxyoptions = { + 'proxy': { + 'http': proxy, + 'https': proxy, + 'no_proxy': 'localhost,127.0.0.1' + } + } + driver = webdriver.Firefox(options=options,seleniumwire_options=proxyoptions) + else: + driver = webdriver.Firefox(options=options) + proxy = "" + print("Getting potoken from firefox...") + try: + driver.get("https://www.youtube.com/embed/aqz-KE-bpKQ") + except Exception: + driver.close() + initytdl() + return + elem = WebDriverWait(driver, 8, 0.2, None).until(lambda x: x.find_element(By.CLASS_NAME, "html5-video-player")) + sleep(1) + elem.click() + sleep(3) + try: + for r in driver.requests: + if "googlevideo.com" in r.url: + f = re.compile("pot=.+?(?=&)") + gvtoken = f.findall(r.url)[0][4:] + if "https://www.youtube.com/youtubei/v1/player" in r.url: + potoken = json.loads(r.body)['serviceIntegrityDimensions']["poToken"] + except IndexError: + print("Youtube didn't send us a token, trying again") + initytdl() + return + driver.get("https://youtube.com") + cookiesstr = to_netscape_string(driver.get_cookies()) + with open("cookies.txt", 'w') as f: + f.write(f'# Netscape HTTP Cookie File\n# https://curl.haxx.se/rfc/cookie_spec.html\n# This is a generated file! Do not edit.\n\n{cookiesstr}') + driver.quit() + print(f"Success! Potoken is {potoken}\nGvstoken is {gvtoken}") + ydl_opts = { + 'match_filter': video_check, + 'hls_prefer_native': True, + 'extract_flat': 'discard_in_playlist', + 'cookiefile': f'cookies.txt', + 'extractor_args': {'youtube': {'player_client': ['web', 'mweb', 'default'], + 'po_token': [f'web.gvs+{gvtoken}',f'web.player+{potoken}',f'mweb.gvs+{gvtoken}',f'mweb.player+{potoken}']}}, + 'format': 'bestvideo[height<=1080][vcodec!*=av01][ext=mp4]+bestaudio[abr<=256][ext=m4a]/best[ext=mp4]/best', + 'fragment_retries': 10, + 'noplaylist': True, + 'restrictfilenames': True, + 'source_address': '0.0.0.0', + 'concurrent_fragment_downloads': 3, + 'paths': {'home': f'{vid_dir}', 'temp': f'{full_dl_dir}'}, + 'outtmpl': {'default': f'999zznext'}, + 'postprocessors': [{'api': 'https://sponsor.ajay.app', + 'categories': {'sponsor', 'selfpromo'}, + 'key': 'SponsorBlock', + 'when': 'after_filter'}, + {'force_keyframes': False, + 'key': 'ModifyChapters', + 'remove_chapters_patterns': [], + 'remove_ranges': [], + 'remove_sponsor_segments': {'sponsor', 'selfpromo'}, + 'sponsorblock_chapter_title': '[SponsorBlock]: ' + '%(category_names)l'}, + {'key': 'FFmpegConcat', + 'only_multi_video': True, + 'when': 'playlist'}, + {'format': 'vtt', + 'key': 'FFmpegSubtitlesConvertor', + 'when': 'before_dl'}, + {'already_have_subtitle': False, + 'key': 'FFmpegEmbedSubtitle'}], + 'proxy': f'{proxy}', + 'subtitlesformat': 'vtt', + 'subtitleslangs': ['en.*'], + 'writesubtitles': True, + 'retries': 10, + } + ydl = yt_dlp.YoutubeDL(ydl_opts) + if os.getenv("USE_PROXY","FALSE") == "TRUE": + print(f"Connected on {proxy}") + return + + def countuser(usrid): res = cur.execute(f"SELECT ROWID FROM queue WHERE " + ("hasplayed = false AND " if not (os.getenv("PERMANENT_MAX_QUEUE","FALSE") == "TRUE") else "") + f"user = {usrid}") rowid = res.fetchall() - print(len(rowid)) return len(rowid) def sqllen(): @@ -100,54 +189,13 @@ def video_check(info, *, incomplete): propagate_queue(1) skip_list.clear() if queue: - ydl.download(queue[0]) + download_video(0,True) return "video too long... :(" -ydl_opts = { - 'match_filter': video_check, - 'hls_prefer_native': True, - 'extract_flat': 'discard_in_playlist', - 'cookiefile': f'cookies.txt', - 'extractor_args': {'youtube': {'player_client': ['web', 'default'], - 'po_token': [f'web+{potoken}']}}, - 'format': 'bestvideo[height<=1080][vcodec!*=av01][ext=mp4]+bestaudio[abr<=256][ext=m4a]/best[ext=mp4]/best', - 'fragment_retries': 10, - 'noplaylist': True, - 'restrictfilenames': True, - 'source_address': '0.0.0.0', - 'concurrent_fragment_downloads': 3, - 'paths': {'home': f'{vid_dir}', 'temp': f'{full_dl_dir}'}, - 'outtmpl': {'default': f'999zznext'}, - 'postprocessors': [{'api': 'https://sponsor.ajay.app', - 'categories': {'sponsor', 'selfpromo'}, - 'key': 'SponsorBlock', - 'when': 'after_filter'}, - {'force_keyframes': False, - 'key': 'ModifyChapters', - 'remove_chapters_patterns': [], - 'remove_ranges': [], - 'remove_sponsor_segments': {'sponsor', 'selfpromo'}, - 'sponsorblock_chapter_title': '[SponsorBlock]: ' - '%(category_names)l'}, - {'key': 'FFmpegConcat', - 'only_multi_video': True, - 'when': 'playlist'}, - {'format': 'vtt', - 'key': 'FFmpegSubtitlesConvertor', - 'when': 'before_dl'}, - {'already_have_subtitle': False, - 'key': 'FFmpegEmbedSubtitle'}], - 'proxy': f'{os.getenv("PROXY")}', - 'subtitlesformat': 'vtt', - 'subtitleslangs': ['en.*'], - 'writesubtitles': True, - 'retries': 10, -} -#logging.basicConfig(level=logging.DEBUG) -ydl = yt_dlp.YoutubeDL(ydl_opts) obs = obsws_python.ReqClient() obse = obsws_python.EventClient() +initytdl() intents = disnake.Intents.all() intents.message_content = False bot = commands.Bot(intents=intents, command_prefix=".", test_guilds=[int(os.getenv("GUILD_ID"))]) @@ -166,35 +214,54 @@ async def on_ready(): videotimer.start() ensurewaiting.start() titlehandler.start() - if (not os.path.isfile(f"{vid_dir}/{vidcounter}.mp4")) and sqllen() > 1: + if (not os.path.isfile(f"{vid_dir}/{vidcounter}.mp4")) and sqllen() >= 1: print("predefined queue!") propagate_queue(2) loop = asyncio.get_running_loop() await loop.run_in_executor(None, cold_run) + if len(queue) > 1: + download_video(1) + os.rename(f"{vid_dir}/999zznext.mp4", f"{vid_dir}/{vidcounter+1}.mp4") - -def download_video(index): +downloading = False +def download_video(index,bypass=False): global retries + global downloading + if bypass: + for filename in os.listdir(full_dl_dir): + filepath = os.path.join(full_dl_dir, filename) + os.remove(filepath) + if downloading and not bypass: + print("download defered, waiting") + while downloading and not bypass: + pass while len(os.listdir(full_dl_dir)) != 0: pass try: + downloading = True ydl.download(queue[index]) retries = 0 sleep(2) #allow ytdlp to fully cleanup + downloading = False + return except Exception as e: print("handling youtube exception") - if "Sign in to confirm" in str(e) or "forbidden" in str(e).lower(): - print("youtube ban detected") - #This is the worst possible case and therefore all activity must halt - obs.set_current_program_scene("banned") - return global failures + if "not a bot" in str(e).lower() or "forbidden" in str(e).lower() or failures >= 4: + print("youtube ban detected") + initytdl() + download_video(index,True) + failures = 0 + return if retries % 2 == 1: queue.pop(index) propagate_queue(1) failures = failures + 1 retries = retries + 1 - download_video(index) + if len(queue) >= index+1: + download_video(index,True) + else: + downloading = False def wait_for_next_video(): @@ -209,14 +276,17 @@ def wait_for_next_video(): while not os.path.isfile(f"{vid_dir}/{vidcounter+1}.mp4"): counter = counter + 1 sleep(0.5) - if counter == 20 and len(os.listdir(full_dl_dir)) == 0: + if counter == 120 and len(os.listdir(full_dl_dir)) == 0: print("failsafe activated") - download_video(0) + download_video(0,True) os.rename(f"{vid_dir}/999zznext.mp4", f"{vid_dir}/{vidcounter+1}.mp4") obs.set_scene_item_enabled("youtube", stat.scene_item_id, False) return def cold_run(): + scene = obs.get_current_program_scene() + if scene.scene_name != "waiting": + return download_video(0) os.rename(f"{vid_dir}/999zznext.mp4", f"{vid_dir}/{vidcounter}.mp4") obs.set_current_program_scene("youtube") @@ -227,9 +297,9 @@ def cold_run(): obs.set_input_settings("nowplaying", {'text': f'{vid_details["title"]}\nBy {vid_details["channel"]}'}, True) obs.set_scene_item_enabled("youtube", nowplayingid.scene_item_id, True) obs.trigger_media_input_action("player", "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_RESTART") - if len(queue) > 1: - download_video(1) - os.rename(f"{vid_dir}/999zznext.mp4", f"{vid_dir}/{vidcounter+1}.mp4") + #if len(queue) > 1: + # download_video(1) + # os.rename(f"{vid_dir}/999zznext.mp4", f"{vid_dir}/{vidcounter+1}.mp4") def on_media_input_playback_ended(data): global vidcounter @@ -324,13 +394,15 @@ async def play(inter: disnake.AppCmdInter, link: str): if (not os.path.isfile(f"{vid_dir}/{vidcounter}.mp4")) and scene.scene_name == "waiting" and sqllen() >= 1 and len(queue) == 0: loop = asyncio.get_running_loop() #queue.clear() #safety - propagate_queue(2) + propagate_queue(1) await loop.run_in_executor(None, cold_run) + return elif (not os.path.isfile(f"{vid_dir}/{vidcounter+1}.mp4")) and sqllen() >= 1 and len(queue) == 1: loop = asyncio.get_running_loop() propagate_queue(2) await loop.run_in_executor(None, download_video, 1) os.rename(f"{vid_dir}/999zznext.mp4", f"{vid_dir}/{vidcounter+1}.mp4") + return return else: await inter.edit_original_response(f"This bot only accepts youtube links") @@ -338,7 +410,8 @@ async def play(inter: disnake.AppCmdInter, link: str): @bot.slash_command( name="shuffle", - description="toggles shuffle on or off, the queue cannot be unshuffled once it is shuffled", + description="toggles shuffle on or off", + default_member_permissions=disnake.Permissions(8192), ) async def shuffleplay(inter: disnake.AppCmdInter, toggle: str = commands.Param(choices=["on", "off"])): if os.getenv("LOCK_SHUFFLE","FALSE") == "TRUE": @@ -451,6 +524,7 @@ async def button_listener(inter: disnake.MessageInteraction): @bot.slash_command( name="toggleplayback", description="play or pause the video", + default_member_permissions=disnake.Permissions(8192), ) async def toggleplayback(inter: disnake.AppCmdInter): stat = obs.get_media_input_status("player") @@ -616,7 +690,7 @@ async def ensurewaiting(): playing = obs.get_media_input_status("player") if playing.media_cursor == None: obs.set_current_program_scene("error") - if len(os.listdir(full_dl_dir)) == 0 and sqllen() >= 1: #just in case + if len(os.listdir(full_dl_dir)) == 0 and sqllen() >= 1 and not downloading: #just in case loop = asyncio.get_running_loop() propagate_queue(2) await loop.run_in_executor(None, download_video, 0) @@ -636,8 +710,12 @@ async def titlehandler(): bot.run(os.getenv("TOKEN")) -print("cleaning up tempdir") +print("cleaning up") temp_dir.cleanup() download_dir.cleanup() +cur.close() +con.commit() con.close() -obs.set_current_program_scene("nosignal") \ No newline at end of file +obs.set_current_program_scene("nosignal") +obs.disconnect() +obse.disconnect() \ No newline at end of file