Compare commits

...

17 commits

8 changed files with 1532 additions and 295 deletions

12
.env.example Normal file
View file

@ -0,0 +1,12 @@
TOKEN=""
PO_TOKEN=""
COOKIES_FILE=""
GUILD_ID=""
QUEUE_PATH=""
MAX_MIN="8"
MAX_QUEUE="5"
ALLOW_SKIP="TRUE"
PERMANENT_MAX_QUEUE="FALSE"
LOCK_SHUFFLE="FALSE"
USE_PROXY="TRUE MEANING YOU HAVE TO EDIT THE PROXIES LIST IN THE FILE OR FALSE"
BLOCK_REGEX=""

8
.gitignore vendored
View file

@ -29,6 +29,12 @@ share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
*.mp4
*.vtt
cookies*
testing/
*.db
next*
MANIFEST
# PyInstaller
@ -126,7 +132,7 @@ celerybeat.pid
# Environments
.env
.venv
.*venv
env/
venv/
ENV/

293
bot.py
View file

@ -1,293 +0,0 @@
from selenium.webdriver.firefox.options import Options
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.common.exceptions import NoSuchElementException, ElementNotVisibleException, TimeoutException
from time import sleep
import time
import disnake
import random
from urllib.parse import urlparse
from disnake.ext import commands
from disnake import TextInputStyle
import asyncio
from dotenv import load_dotenv
import os
import logging
import math
logger = logging.getLogger('disnake')
logger.setLevel(logging.DEBUG)
handler = logging.FileHandler(filename='disnake.log', encoding='utf-8', mode='w')
handler.setFormatter(logging.Formatter('%(asctime)s:%(levelname)s:%(name)s: %(message)s'))
logger.addHandler(handler)
queue = []
user_queue = []
skip_list = []
shuffle = False
load_dotenv()
options = Options()
options.profile = webdriver.FirefoxProfile(os.getenv("PROFILE_PATH"))
driver = webdriver.Firefox(options=options)
intents = disnake.Intents.all()
intents.message_content = False
bot = commands.Bot(intents=intents, command_prefix=".", test_guilds=[int(os.getenv("GUILD_ID"))])
@bot.event
async def on_ready():
global queuetask
queuetask = asyncio.create_task(queuehandler()) #this will set the waiting for videos image, then exit
def play_video(videourl):
driver.get(videourl)
try:
elem = WebDriverWait(driver, 8, 0.2, None).until(lambda x: x.find_element(By.CLASS_NAME, "ytp-fullscreen-button"))
sleep(1)
elem.send_keys(Keys.RETURN)
except (NoSuchElementException,TimeoutException) as e:
print(e)
return #if this errors there is no fullscreen options, such as playlists, so skip the link
try:
elem = driver.find_element(By.XPATH, '//button[@data-tooltip-target-id="ytp-autonav-toggle-button"][@aria-label="Autoplay is on"]')
elem.send_keys(Keys.RETURN)
except (NoSuchElementException,ElementNotVisibleException,TimeoutException):
pass
try:
elem = WebDriverWait(driver, 5, 0.2, (NoSuchElementException,ElementNotVisibleException)).until(lambda x: x.find_element(By.XPATH, '//button[@aria-label="Dismiss"]'))
elem.send_keys(Keys.RETURN)
except (NoSuchElementException,ElementNotVisibleException,TimeoutException) as e:
print(e)
try:
elem = WebDriverWait(driver, (float(os.getenv("MAX_MIN")) * 60), 1, None).until(lambda x: x.find_element(By.CLASS_NAME, "html5-endscreen").is_displayed())
except (NoSuchElementException,ElementNotVisibleException,TimeoutException) as e:
print(e)
sleep(1)
return #same as above
sleep(1)
return
@bot.slash_command(
name="play",
description="adds a video to the queue",
)
async def play(inter: disnake.AppCmdInter, link: str):
await inter.response.defer(ephemeral=True)
if user_queue.count(inter.user.id) >= (int(os.getenv("MAX_QUEUE"))):
await inter.edit_original_response(f"You have reached the queue limit of {os.getenv('MAX_QUEUE')}, try again after one of your videos has played.")
return
if urlparse(link).netloc == 'youtube.com' or urlparse(link).netloc == 'www.youtube.com' or urlparse(link).netloc == 'youtu.be':
queue.append(link)
user_queue.append(inter.user.id)
await inter.edit_original_response(f"added to queue!")
global queuetask
if queuetask.done():
queuetask = asyncio.create_task(queuehandler())
return
else:
await inter.edit_original_response(f"This bot only accepts youtube links")
return
@bot.slash_command(
name="shuffle",
description="toggles shuffle on or off, the queue cannot be unshuffled once it is shuffled",
)
async def shuffleplay(inter: disnake.AppCmdInter, toggle: str = commands.Param(choices=["on", "off"])):
await inter.response.defer(ephemeral=True)
global shuffle
if toggle == "on":
shuffle = True
await inter.edit_original_response(f"shuffle enabled")
return
else:
shuffle = False
await inter.edit_original_response(f"shuffle disabled")
return
@bot.slash_command(
name="queue",
description="list the videos in queue",
)
async def getqueue(inter: disnake.AppCmdInter):
await inter.response.defer(ephemeral=True)
if not queue:
await inter.edit_original_response("There are no items in queue")
return
message = f"Now Playing: <{queue[0]}>\n"
message = message + f"Shuffle is currently " + ("off\n" if not shuffle else "on!\n")
for i in range(11):
if i == 0:
continue
try:
message = message + f"{i}. <{queue[i]}>\n"
except IndexError:
break
message = message + f"1 of {math.ceil((len(queue)-1)/10) if (len(queue)-1)/10 > 1 else 1}"
if math.ceil((len(queue)-1)/10) > 1:
await inter.edit_original_response(message, components=[disnake.ui.Button(label=">>", style=disnake.ButtonStyle.primary, custom_id="Forward"),])
else:
await inter.edit_original_response(message)
@bot.listen("on_button_click")
async def button_listener(inter: disnake.MessageInteraction):
if not queue:
await inter.response.edit_message("There are no items in queue")
return
ogmsg = inter.message.content
page = ogmsg.split("\n")
page = page[-1].split(" of ")
if inter.component.custom_id == "Forward":
message = f"Now Playing: <{queue[0]}>\n"
message = message + f"Shuffle is currently " + ("off\n" if not shuffle else "on!\n")
offset = int(int(page[0]) * 10)
for i in range(11):
if i == 0:
continue
try:
message = message + f"{int(i+offset)}. <{queue[int(i+offset)]}>\n"
except IndexError:
break
message = message + f"{int(page[0])+1} of {math.ceil((len(queue)-1)/10) if (len(queue)-1)/10 > 1 else 1}"
if (int(page[0])+1) >= int(page[1]):
await inter.response.edit_message(message, components=[disnake.ui.Button(label="<<", style=disnake.ButtonStyle.primary, custom_id="Backward"),])
else:
await inter.response.edit_message(message, components=[disnake.ui.Button(label="<<", style=disnake.ButtonStyle.primary, custom_id="Backward"),
disnake.ui.Button(label=">>", style=disnake.ButtonStyle.primary, custom_id="Forward"),])
return
if inter.component.custom_id == "Backward":
message = f"Now Playing: <{queue[0]}>\n"
message = message + f"Shuffle is currently " + ("off\n" if not shuffle else "on!\n")
offset = int((int(page[0]) - 2) * 10)
for i in range(11):
if i == 0:
continue
try:
message = message + f"{int(i+offset)}. <{queue[int(i+offset)]}>\n"
except IndexError:
break
message = message + f"{int(page[0])-1} of {math.ceil((len(queue)-1)/10) if (len(queue)-1)/10 > 1 else 1}"
if (int(page[0])-1) <= 1 and int(int(page[1]) == 1):
await inter.response.edit_message(message)
elif (int(page[0])-1) <= 1 and int(int(page[1]) > 1):
await inter.response.edit_message(message, components=[disnake.ui.Button(label=">>", style=disnake.ButtonStyle.primary, custom_id="Forward"),])
else:
await inter.response.edit_message(message, components=[disnake.ui.Button(label="<<", style=disnake.ButtonStyle.primary, custom_id="Backward"),
disnake.ui.Button(label=">>", style=disnake.ButtonStyle.primary, custom_id="Forward"),])
await inter.response.edit_message(message, components=[])
return
@bot.slash_command(
name="toggleplayback",
description="pauses or unpauses the video, does not bypass the video timelimit",
)
async def toggleplayback(inter: disnake.AppCmdInter):
await inter.response.defer(ephemeral=True)
try:
elem = driver.find_element(By.XPATH, '//button[@aria-keyshortcuts="k"]')
elem.send_keys(Keys.RETURN)
except Exception:
pass
await inter.edit_original_response("toggled!")
@bot.slash_command(
name="skip",
description="skips the current video",
default_member_permissions=disnake.Permissions(8192),
)
async def skip(inter: disnake.AppCmdInter):
await inter.response.defer(ephemeral=False)
global queuetask
queuetask.cancel()
try:
await queuetask
except asyncio.CancelledError:
queue.pop(0)
user_queue.pop(0)
skip_list.clear()
if len(queue) < 1:
driver.get(f"file://{os.getcwd()}/waitingforvideo.png")
driver.fullscreen_window()
else:
queuetask = asyncio.create_task(queuehandler())
await inter.edit_original_response("skipped")
@bot.slash_command(
name="voteskip",
description="vote to skip the current video",
)
async def voteskip(inter: disnake.AppCmdInter):
await inter.response.defer(ephemeral=False)
if not inter.user.voice:
await inter.edit_original_response("You are not in the voice channel")
return
if inter.user.id in skip_list:
await inter.edit_original_response("You have already voted to skip this video")
return
vc = inter.user.voice.channel.members #this could be better due to potential for abuse, but it's fine for now
print(inter.user.voice.channel.name)
broadcaster = False
for m in vc:
if m.voice.self_video or m.voice.self_stream:
broadcaster = True
break
if not broadcaster:
await inter.edit_original_response("No one is playing video so how can this be the correct vc?")
return
skip_list.append(inter.user.id)
print(len(skip_list))
print(math.floor(len(vc)/2))
print(vc)
if len(skip_list) >= (math.floor(len(vc)/2)):
global queuetask
queuetask.cancel()
try:
await queuetask
except asyncio.CancelledError:
queue.pop(0)
user_queue.pop(0)
skip_list.clear()
if len(queue) < 1:
driver.get(f"file://{os.getcwd()}/waitingforvideo.png")
driver.fullscreen_window()
else:
queuetask = asyncio.create_task(queuehandler())
await inter.edit_original_response("skipped as sufficient votes were reached")
else:
await inter.edit_original_response(f"**{inter.user.display_name}** has voted to skip the video, {len(skip_list)}/{math.floor(len(vc)/2)}")
@bot.slash_command(
name="remove",
description="removes a video from the queue",
)
async def remove(inter: disnake.AppCmdInter, toremove: int):
await inter.response.defer(ephemeral=True)
if toremove == 0:
await inter.edit_original_response("that is the currently playing video!", ephemeral=True)
return
else:
queue.pop(toremove)
user_queue.pop(toremove)
await inter.edit_original_response("removed!")
async def queuehandler():
loop = asyncio.get_running_loop()
global queue
while queue:
if shuffle:
seed = time.time()
random.seed(seed)
random.shuffle(queue)
random.seed(seed)
random.shuffle(user_queue)
print(queue)
print(user_queue)
await loop.run_in_executor(None, play_video, queue[0])
queue.pop(0)
user_queue.pop(0)
skip_list.clear()
driver.get(f"file://{os.getcwd()}/waitingforvideo.png")
driver.fullscreen_window()
bot.run(os.getenv("TOKEN"))

704
newbot.py Normal file
View file

@ -0,0 +1,704 @@
import obsws_python
from selenium.webdriver.firefox.options import Options
from seleniumwire import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from netscape_cookies import to_netscape_string
import json
import yt_dlp
from time import sleep
import time
import disnake
import random
from urllib.parse import urlparse
from disnake.ext import commands, tasks
from disnake import TextInputStyle
import asyncio
from dotenv import load_dotenv
import os, sys, random
import datetime
import re
import logging
import math
from tempfile import TemporaryDirectory
from pathlib import Path
import sqlite3
if len(sys.argv) > 1:
if sys.argv[1] == "--clear-queue":
if os.path.isfile(os.getenv("QUEUE_PATH","queue.db")):
os.remove(os.getenv("QUEUE_PATH","queue.db"))
print("Queue cleared!")
load_dotenv()
queue = []
skip_list = []
shuffle = True
retries = 0
failures = 0
vidcounter = 0
proxies = []
temp_dir = TemporaryDirectory()
vid_dir = Path(temp_dir.name)
download_dir = TemporaryDirectory()
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()
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()
return len(rowid)
def sqllen():
res = cur.execute(f"SELECT ROWID FROM queue WHERE hasplayed = false")
rowid = res.fetchall()
return len(rowid)
def propagate_queue(times):
res = cur.execute(f"SELECT ROWID,link FROM queue WHERE hasplayed = false " + ("ORDER BY RANDOM() " if shuffle else "") + f"LIMIT {times}")
for rowid,link in res.fetchall():
if len(queue) == 2:
print("the queue is already propagated, exiting early")
return
elif len(queue) > 2:
print(f"The queue is larger than two videos this WILL cause issues: {queue}")
return
queue.append(link)
cur.execute(f"UPDATE queue SET hasplayed = true WHERE ROWID='{rowid}'")
print(f"added {link} to queue")
con.commit()
def video_check(info, *, incomplete):
duration = info.get('duration')
vid_details["title"] = info.get("title")
vid_details["channel"] = info.get("channel")
print(info.get("channel"))
print(info.get("title"))
banned_video = re.compile((os.getenv("BLOCK_REGEX","^\b$") if os.getenv("BLOCK_REGEX","^\b$") != "" else "^\b$"), re.IGNORECASE).findall(f"{vid_details['title']} {vid_details['channel']}")
if (duration and duration >= ((float(os.getenv("MAX_MIN")) * 60) + 480)) or banned_video:
return "video too long" #TODO why is this an issue if its the second video played
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"))])
ver = obs.get_version()
print(f"OBS Version: {ver.obs_version}")
@bot.event
async def on_ready():
res = cur.execute("SELECT name FROM sqlite_master WHERE name='queue'")
if res.fetchone() is None:
cur.execute("CREATE TABLE queue(link, user, hasplayed)")
con.commit()
obs.set_current_program_scene("waiting")
obse.callback.register(on_media_input_playback_ended)
videotimer.start()
ensurewaiting.start()
titlehandler.start()
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)
downloading = False
def download_video(index,bypass=False,renameoffset=1):
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])
sleep(2) #allow ytdlp to fully cleanup
os.rename(f"{vid_dir}/999zznext.mp4", f"{vid_dir}/{vidcounter+renameoffset}.mp4")
downloading = False
retries = 0
return
except Exception as e:
print("handling youtube exception")
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 "no such file or directory" in str(e).lower(): #Thanks for the silent fail ytdlp
queue.pop(index)
propagate_queue(1)
else:
if retries % 2 == 1:
queue.pop(index)
propagate_queue(1)
failures = failures + 1
retries = retries + 1
if len(queue) >= index+1:
download_video(index,True)
else:
downloading = False
def wait_for_next_video():
counter = 0
if len(queue) <= 1:
return
stat = obs.get_scene_item_id("youtube", "notice")
print(stat.scene_item_id)
print("Attempting to enable")
obs.set_scene_item_enabled("youtube", stat.scene_item_id, True)
while not os.path.isfile(f"{vid_dir}/{vidcounter+1}.mp4"):
counter = counter + 1
sleep(0.5)
if counter == 120 and len(os.listdir(full_dl_dir)) == 0:
print("failsafe activated")
download_video(0,True)
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,renameoffset=0)
if not os.path.isfile(f"{vid_dir}/{vidcounter}.mp4"):
return
obs.set_current_program_scene("youtube")
nowplayingid = obs.get_scene_item_id("youtube", "nowplaying")
stat = obs.get_scene_item_id("youtube", "notice")
obs.set_scene_item_enabled("youtube", stat.scene_item_id, False)
obs.set_input_settings("player", {'playlist': [{'hidden': False, 'selected': False, 'value': f"{str(vid_dir)}"}]}, True)
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")
def on_media_input_playback_ended(data):
global vidcounter
try:
queue.pop(0)
propagate_queue(2)
except IndexError as e:
print(f"Video ended but somehow it wasn't in the queue? Resetting {e}")
obs.set_current_program_scene("waiting")
skip_list.clear()
if not queue:
print("queue is now empty")
if sqllen() >= 1:
print("alternative download triggered")
propagate_queue(2)
download_video(0) #will be noticeably slow but this should not happen
else:
obs.set_current_program_scene("waiting")
os.remove(f"{vid_dir}/{vidcounter}.mp4")
vidcounter = vidcounter + 1
return
wait_for_next_video()
print(queue)
os.remove(f"{vid_dir}/{vidcounter}.mp4")
vidcounter = vidcounter + 1
print("changing obs settigs")
scene = obs.get_current_program_scene()
if scene.scene_name != "youtube":
obs.set_current_program_scene("youtube")
nowplayingid = obs.get_scene_item_id("youtube", "nowplaying")
obs.set_input_settings("player", {'playlist': [{'hidden': False, 'selected': False, 'value': f"{str(vid_dir)}"}]}, True)
obs.set_input_settings("nowplaying", {'text': f'{vid_details["title"]}\nBy {vid_details["channel"]}'}, True)
obs.set_input_settings("nowplaying", {'text': f'{vid_details["title"]}\nBy {vid_details["channel"]}'}, True) #sometimes it doesn't apply for some reason TODO: investigate further
print("starting playback")
obs.trigger_media_input_action("player", "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_RESTART")
obs.set_scene_item_enabled("youtube", nowplayingid.scene_item_id, True)
obs.set_scene_item_enabled("youtube", nowplayingid.scene_item_id, True) #same as above
if len(queue) > 1:
download_video(1)
elif sqllen() >= 1 and len(queue) == 1:
propagate_queue(1)
download_video(1)
@bot.slash_command(
name="stats",
description="get information about whats happening",
)
async def stats(inter: disnake.AppCmdInter):
scene = obs.get_current_program_scene()
if scene.scene_name != "youtube":
await inter.response.send_message("Stats can only be shown while a video is playing", ephemeral=True)
return
await inter.response.defer(ephemeral=True)
ver = obs.get_version()
message = f"OBS Version: {ver.obs_version}\n"
message = message + f"Failures: {failures}\n"
nowplaying = obs.get_input_settings("nowplaying")
message = message + f"Title: {nowplaying.input_settings['text']}\n"
message = message + f"Link: <{queue[0]}>\n"
playing = obs.get_media_input_status("player")
message = message + f"Video Duration: {str(datetime.timedelta(seconds=(round(playing.media_cursor/1000))))}/{str(datetime.timedelta(seconds=(round(playing.media_duration/1000))))}\n"
if inter.permissions.moderate_members:
res = cur.execute(f"SELECT user FROM queue WHERE hasplayed = true AND link = '{queue[0]}'")
res = res.fetchall() #We can't gaurentee the result so just show likely possibilites
message = message + f"Users who have queued this video: "
for [usrid] in list(set(res)):
message = message + f"<@{usrid}>, "
await inter.edit_original_response(message)
@bot.slash_command(
name="play",
description="adds a video to the queue",
)
async def play(inter: disnake.AppCmdInter, link: str):
await inter.response.defer(ephemeral=True)
if countuser(inter.user.id) >= (int(os.getenv("MAX_QUEUE"))):
await inter.edit_original_response(f"You have reached the queue limit of {os.getenv('MAX_QUEUE')}, " + ("try again after one of your videos has played." if not (os.getenv("PERMANENT_MAX_QUEUE","FALSE") == "TRUE") else "you may not queue videos for the rest of the session."))
return
if (urlparse(link).netloc == 'youtube.com' or urlparse(link).netloc == 'www.youtube.com' or urlparse(link).netloc == 'youtu.be') and urlparse(link).scheme == 'https':
cur.execute(f"""INSERT INTO queue VALUES
('{link}',{inter.user.id},false)
""")
con.commit()
await inter.edit_original_response(f"added to queue!")
scene = obs.get_current_program_scene()
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()
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)
return
return
else:
await inter.edit_original_response(f"This bot only accepts youtube links")
return
@bot.slash_command(
name="shuffle",
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":
await inter.response.send_message("the bot owner has locked shuffling",ephemeral=True)
return
await inter.response.defer(ephemeral=True)
global shuffle
if toggle == "on":
shuffle = True
await inter.edit_original_response(f"shuffle enabled")
return
else:
shuffle = False
await inter.edit_original_response(f"shuffle disabled")
return
@bot.slash_command(
name="queue",
description="list the videos in queue",
)
async def getqueue(inter: disnake.AppCmdInter):
await inter.response.defer(ephemeral=True)
if not queue:
await inter.edit_original_response("There are no items in queue")
return
message = f"Now Playing: <{queue[0]}>\n"
try:
message = message + f"Up Next: <{queue[1]}>\n"
except IndexError:
pass
message = message + f"Shuffle is currently " + ("off\n" if not shuffle else "on!\n")
res = cur.execute(f"SELECT link FROM queue WHERE hasplayed = false")
links = [x[0] for x in res.fetchall()]
links.insert(0,"dummy")
for i in range(10):
if i == 0:
continue
try:
message = message + f"{i}. <{links[i]}>\n"
except IndexError:
break
message = message + f"1 of {math.ceil((len(links)-1)/10) if (len(links)-1)/10 > 1 else 1}"
if math.ceil((len(links)-1)/10) > 1:
await inter.edit_original_response(message, components=[disnake.ui.Button(label=">>", style=disnake.ButtonStyle.primary, custom_id="Forward"),])
else:
await inter.edit_original_response(message)
@bot.listen("on_button_click")
async def button_listener(inter: disnake.MessageInteraction):
if not queue:
await inter.response.edit_message("There are no items in queue")
return
ogmsg = inter.message.content
page = ogmsg.split("\n")
page = page[-1].split(" of ")
if inter.component.custom_id == "Forward":
message = f"Now Playing: <{queue[0]}>\n"
try:
message = message + f"Up Next: <{queue[1]}>\n"
except IndexError:
pass
message = message + f"Shuffle is currently " + ("off\n" if not shuffle else "on!\n")
res = cur.execute(f"SELECT link FROM queue WHERE hasplayed = false")
links = [x[0] for x in res.fetchall()]
links.insert(0,"dummy")
offset = int(int(page[0]) * 10)
for i in range(11):
if i == 0:
continue
try:
message = message + f"{int(i+offset)}. <{links[int(i+offset)]}>\n"
except IndexError:
break
message = message + f"{int(page[0])+1} of {math.ceil((len(links)-1)/10) if (len(links)-1)/10 > 1 else 1}"
if (int(page[0])+1) >= int(page[1]):
await inter.response.edit_message(message, components=[disnake.ui.Button(label="<<", style=disnake.ButtonStyle.primary, custom_id="Backward"),])
else:
await inter.response.edit_message(message, components=[disnake.ui.Button(label="<<", style=disnake.ButtonStyle.primary, custom_id="Backward"),
disnake.ui.Button(label=">>", style=disnake.ButtonStyle.primary, custom_id="Forward"),])
return
if inter.component.custom_id == "Backward":
message = f"Now Playing: <{queue[0]}>\n"
try:
message = message + f"Up Next: <{queue[1]}>\n"
except IndexError:
pass
message = message + f"Shuffle is currently " + ("off\n" if not shuffle else "on!\n")
res = cur.execute(f"SELECT link FROM queue WHERE hasplayed = false")
links = [x[0] for x in res.fetchall()]
links.insert(0,"dummy")
offset = int((int(page[0]) - 2) * 10)
for i in range(11):
if i == 0:
continue
try:
message = message + f"{int(i+offset)}. <{links[int(i+offset)]}>\n"
except IndexError:
break
message = message + f"{int(page[0])-1} of {math.ceil((len(links)-1)/10) if (len(links)-1)/10 > 1 else 1}"
if (int(page[0])-1) <= 1 and int(int(page[1]) == 1):
await inter.response.edit_message(message)
elif (int(page[0])-1) <= 1 and int(int(page[1]) > 1):
await inter.response.edit_message(message, components=[disnake.ui.Button(label=">>", style=disnake.ButtonStyle.primary, custom_id="Forward"),])
else:
await inter.response.edit_message(message, components=[disnake.ui.Button(label="<<", style=disnake.ButtonStyle.primary, custom_id="Backward"),
disnake.ui.Button(label=">>", style=disnake.ButtonStyle.primary, custom_id="Forward"),])
await inter.response.edit_message(message, components=[])
return
@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")
print(stat.media_state)
if stat.media_state == "OBS_MEDIA_STATE_PLAYING":
obs.trigger_media_input_action("player","OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PAUSE")
elif stat.media_state == "OBS_MEDIA_STATE_PAUSED":
obs.trigger_media_input_action("player","OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PLAY")
await inter.response.send_message("done",ephemeral=True)
@bot.slash_command(
name="skip",
description="skips the current video, do not run this command multiple times",
default_member_permissions=disnake.Permissions(8192),
)
async def skip(inter: disnake.AppCmdInter):
if os.getenv("ALLOW_SKIP","TRUE") == "FALSE":
await inter.response.send_message("the bot owner has disabled skipping", ephemeral=True)
return
await inter.response.defer(ephemeral=False)
loop = asyncio.get_running_loop()
global vidcounter
queue.pop(0)
propagate_queue(1)
skip_list.clear()
if not queue:
obs.set_current_program_scene("waiting")
os.remove(f"{vid_dir}/{vidcounter}.mp4")
vidcounter = vidcounter + 1
await inter.edit_original_response("skipped as sufficient votes were reached")
return
await loop.run_in_executor(None, wait_for_next_video)
print("stopping video")
os.remove(f"{vid_dir}/{vidcounter}.mp4")
vidcounter = vidcounter + 1
nowplayingid = obs.get_scene_item_id("youtube", "nowplaying")
obs.set_input_settings("player", {'playlist': [{'hidden': False, 'selected': False, 'value': f"{str(vid_dir)}"}]}, True)
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")
await inter.edit_original_response("skipped as sufficient votes were reached")
if len(queue) > 1:
await loop.run_in_executor(None, download_video, 1)
@bot.slash_command(
name="voteskip",
description="vote to skip the current video",
)
async def voteskip(inter: disnake.AppCmdInter):
if os.getenv("ALLOW_SKIP","TRUE") == "FALSE":
await inter.response.send_message("the bot owner has disabled skipping", ephemeral=True)
return
await inter.response.defer(ephemeral=False)
if not inter.user.voice:
await inter.edit_original_response("You are not in the voice channel")
return
if inter.user.id in skip_list:
await inter.edit_original_response("You have already voted to skip this video")
return
vc = inter.user.voice.channel.members #this could be better due to potential for abuse, but it's fine for now
print(inter.user.voice.channel.name)
broadcaster = False
for m in vc:
if m.voice.self_video or m.voice.self_stream:
broadcaster = True
break
if not broadcaster:
await inter.edit_original_response("No one is playing video so how can this be the correct vc?")
return
skip_list.append(inter.user.id)
print(len(skip_list))
print(math.floor(len(vc)/2))
print(vc)
if len(skip_list) >= (math.floor(len(vc)/2)):
loop = asyncio.get_running_loop()
global vidcounter
queue.pop(0)
propagate_queue(1)
skip_list.clear()
if not queue:
obs.set_current_program_scene("waiting")
os.remove(f"{vid_dir}/{vidcounter}.mp4")
vidcounter = vidcounter + 1
await inter.edit_original_response("skipped as sufficient votes were reached")
return
await loop.run_in_executor(None, wait_for_next_video)
print("stopping video")
obs.trigger_media_input_action("player", "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PAUSE")
os.remove(f"{vid_dir}/{vidcounter}.mp4")
vidcounter = vidcounter + 1
nowplayingid = obs.get_scene_item_id("youtube", "nowplaying")
obs.set_input_settings("player", {'playlist': [{'hidden': False, 'selected': False, 'value': f"{str(vid_dir)}"}]}, True)
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")
await inter.edit_original_response("skipped as sufficient votes were reached")
if len(queue) > 1:
await loop.run_in_executor(None, download_video, 1)
await inter.edit_original_response(f"**{inter.user.display_name}** has voted to skip the video, {len(skip_list)}/{math.floor(len(vc)/2)}")
@tasks.loop(seconds=5.0)
async def videotimer():
try:
scene = obs.get_current_program_scene()
if scene.scene_name != "youtube":
return
playing = obs.get_media_input_status("player")
if playing.media_cursor >= ((float(os.getenv("MAX_MIN")) * 60000)):
#skip
loop = asyncio.get_running_loop()
global vidcounter
playing = obs.get_media_input_status("player")
if playing.media_state == "OBS_MEDIA_STATE_STOPPED" or playing.media_state == "OBS_MEDIA_STATE_ENDED":
#we are already handling it
print("skip return")
return
queue.pop(0)
propagate_queue(1)
skip_list.clear()
if not queue:
obs.set_current_program_scene("waiting")
os.remove(f"{vid_dir}/{vidcounter}.mp4")
vidcounter = vidcounter + 1
return
await loop.run_in_executor(None, wait_for_next_video)
print("stopping video")
os.remove(f"{vid_dir}/{vidcounter}.mp4")
vidcounter = vidcounter + 1
nowplayingid = obs.get_scene_item_id("youtube", "nowplaying")
obs.set_input_settings("player", {'playlist': [{'hidden': False, 'selected': False, 'value': f"{str(vid_dir)}"}]}, True)
obs.trigger_media_input_action("player", "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_RESTART")
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)
if len(queue) > 1:
await loop.run_in_executor(None, download_video, 1)
except Exception:
pass
return
@tasks.loop(seconds=20)
async def ensurewaiting():
scene = obs.get_current_program_scene()
if scene.scene_name == "waiting" or scene.scene_name == "banned":
return
if scene.scene_name == "error" and len(os.listdir(full_dl_dir)) == 0:
obs.set_current_program_scene("waiting")
return
elif scene.scene_name == "error":
return
stat = obs.get_scene_item_id("youtube", "notice")
enabled = obs.get_scene_item_enabled("youtube", stat.scene_item_id)
if enabled.scene_item_enabled:
return
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 and not downloading: #just in case
loop = asyncio.get_running_loop()
propagate_queue(2)
await loop.run_in_executor(None, download_video, 0)
obs.set_current_program_scene("youtube")
@tasks.loop(seconds=5)
async def titlehandler():
scene = obs.get_current_program_scene()
if scene.scene_name != "youtube":
return
nowplayingid = obs.get_scene_item_id("youtube", "nowplaying")
enabled = obs.get_scene_item_enabled("youtube", nowplayingid.scene_item_id)
if enabled.scene_item_enabled:
await asyncio.sleep(4)
obs.set_scene_item_enabled("youtube", nowplayingid.scene_item_id, False)
return
bot.run(os.getenv("TOKEN"))
print("cleaning up")
temp_dir.cleanup()
download_dir.cleanup()
cur.close()
con.commit()
con.close()
obs.set_current_program_scene("nosignal")
obs.disconnect()
obse.disconnect()

794
obs_scenes.json Normal file
View file

@ -0,0 +1,794 @@
{
"DesktopAudioDevice1": {
"prev_ver": 503447555,
"name": "Desktop Audio",
"uuid": "12ba8a35-1150-4603-bba1-4f08d8caaf9d",
"id": "pulse_output_capture",
"versioned_id": "pulse_output_capture",
"settings": {
"device_id": "default"
},
"mixers": 255,
"sync": 0,
"flags": 0,
"volume": 1.0,
"balance": 0.5,
"enabled": true,
"muted": true,
"push-to-mute": false,
"push-to-mute-delay": 0,
"push-to-talk": false,
"push-to-talk-delay": 0,
"hotkeys": {
"libobs.mute": [],
"libobs.unmute": [],
"libobs.push-to-mute": [],
"libobs.push-to-talk": []
},
"deinterlace_mode": 0,
"deinterlace_field_order": 0,
"monitoring_type": 0,
"private_settings": {}
},
"current_scene": "waiting",
"current_program_scene": "waiting",
"scene_order": [
{
"name": "waiting"
},
{
"name": "youtube"
},
{
"name": "error"
},
{
"name": "screenshare"
},
{
"name": "banned"
}
],
"name": "Untitled",
"groups": [],
"quick_transitions": [
{
"name": "Cut",
"duration": 300,
"hotkeys": [],
"id": 1,
"fade_to_black": false
},
{
"name": "Fade",
"duration": 300,
"hotkeys": [],
"id": 2,
"fade_to_black": false
},
{
"name": "Fade",
"duration": 300,
"hotkeys": [],
"id": 3,
"fade_to_black": true
}
],
"transitions": [],
"saved_projectors": [],
"current_transition": "Fade",
"transition_duration": 200,
"preview_locked": false,
"scaling_enabled": false,
"scaling_level": 0,
"scaling_off_x": 0.0,
"scaling_off_y": 0.0,
"virtual-camera": {
"type2": 1,
"scene": "screenshare"
},
"modules": {
"scripts-tool": [],
"output-timer": {
"streamTimerHours": 0,
"streamTimerMinutes": 0,
"streamTimerSeconds": 30,
"recordTimerHours": 0,
"recordTimerMinutes": 0,
"recordTimerSeconds": 30,
"autoStartStreamTimer": false,
"autoStartRecordTimer": false,
"pauseRecordTimer": true
},
"auto-scene-switcher": {
"interval": 300,
"non_matching_scene": "",
"switch_if_not_matching": false,
"active": false,
"switches": []
}
},
"resolution": {
"x": 1920,
"y": 1080
},
"sources": [
{
"prev_ver": 503447555,
"name": "banned",
"uuid": "10ed3781-690c-4783-80c3-d1ca7b78303a",
"id": "scene",
"versioned_id": "scene",
"settings": {
"id_counter": 1,
"custom_size": false,
"items": [
{
"name": "banned:(",
"source_uuid": "71c4e4fc-becc-4cde-aee3-e9d50d3c3077",
"visible": true,
"locked": false,
"rot": 0.0,
"pos": {
"x": 0.0,
"y": 0.0
},
"scale": {
"x": 1.0,
"y": 1.0
},
"align": 5,
"bounds_type": 2,
"bounds_align": 0,
"bounds_crop": false,
"bounds": {
"x": 1920.0,
"y": 1080.0
},
"crop_left": 0,
"crop_top": 0,
"crop_right": 0,
"crop_bottom": 0,
"id": 1,
"group_item_backup": false,
"scale_filter": "disable",
"blend_method": "default",
"blend_type": "normal",
"show_transition": {
"duration": 0
},
"hide_transition": {
"duration": 0
},
"private_settings": {}
}
]
},
"mixers": 0,
"sync": 0,
"flags": 0,
"volume": 1.0,
"balance": 0.5,
"enabled": true,
"muted": false,
"push-to-mute": false,
"push-to-mute-delay": 0,
"push-to-talk": false,
"push-to-talk-delay": 0,
"hotkeys": {
"OBSBasic.SelectScene": [],
"libobs.show_scene_item.1": [],
"libobs.hide_scene_item.1": []
},
"deinterlace_mode": 0,
"deinterlace_field_order": 0,
"monitoring_type": 0,
"private_settings": {}
},
{
"prev_ver": 503447555,
"name": "banned",
"uuid": "71c4e4fc-becc-4cde-aee3-e9d50d3c3077",
"id": "text_ft2_source",
"versioned_id": "text_ft2_source_v2",
"settings": {
"text": "we got banned from youtube",
"font": {
"face": "Monocraft",
"style": "Regular",
"size": 256,
"flags": 0
}
},
"mixers": 0,
"sync": 0,
"flags": 0,
"volume": 1.0,
"balance": 0.5,
"enabled": true,
"muted": false,
"push-to-mute": false,
"push-to-mute-delay": 0,
"push-to-talk": false,
"push-to-talk-delay": 0,
"hotkeys": {},
"deinterlace_mode": 0,
"deinterlace_field_order": 0,
"monitoring_type": 0,
"private_settings": {}
},
{
"prev_ver": 503447555,
"name": "error",
"uuid": "8c1f123a-2c08-4716-aec6-d59a57225536",
"id": "scene",
"versioned_id": "scene",
"settings": {
"id_counter": 2,
"custom_size": false,
"items": [
{
"name": "Text (FreeType 2)",
"source_uuid": "01d3bab9-cc17-4702-acc6-6e25319188eb",
"visible": true,
"locked": false,
"rot": 0.0,
"pos": {
"x": 0.0,
"y": 0.0
},
"scale": {
"x": 1.0,
"y": 1.0
},
"align": 5,
"bounds_type": 2,
"bounds_align": 0,
"bounds_crop": false,
"bounds": {
"x": 1920.0,
"y": 1080.0
},
"crop_left": 0,
"crop_top": 0,
"crop_right": 0,
"crop_bottom": 0,
"id": 1,
"group_item_backup": false,
"scale_filter": "disable",
"blend_method": "default",
"blend_type": "normal",
"show_transition": {
"duration": 0
},
"hide_transition": {
"duration": 0
},
"private_settings": {}
}
]
},
"mixers": 0,
"sync": 0,
"flags": 0,
"volume": 1.0,
"balance": 0.5,
"enabled": true,
"muted": false,
"push-to-mute": false,
"push-to-mute-delay": 0,
"push-to-talk": false,
"push-to-talk-delay": 0,
"hotkeys": {
"OBSBasic.SelectScene": [],
"libobs.show_scene_item.1": [],
"libobs.hide_scene_item.1": []
},
"deinterlace_mode": 0,
"deinterlace_field_order": 0,
"monitoring_type": 0,
"private_settings": {}
},
{
"prev_ver": 503447555,
"name": "Image",
"uuid": "a9588972-73f5-4176-a837-5492bba04b6a",
"id": "image_source",
"versioned_id": "image_source",
"settings": {
"file": "/your/thumbnail/waitingforvideo.png",
"unload": false
},
"mixers": 0,
"sync": 0,
"flags": 0,
"volume": 1.0,
"balance": 0.5,
"enabled": true,
"muted": false,
"push-to-mute": false,
"push-to-mute-delay": 0,
"push-to-talk": false,
"push-to-talk-delay": 0,
"hotkeys": {},
"deinterlace_mode": 0,
"deinterlace_field_order": 0,
"monitoring_type": 0,
"private_settings": {}
},
{
"prev_ver": 503447555,
"name": "notice",
"uuid": "e4912bae-e9c8-4574-9e7b-898963c4b20e",
"id": "text_ft2_source",
"versioned_id": "text_ft2_source_v2",
"settings": {
"log_mode": false,
"text": "video not ready :(\nplease wait...",
"font": {
"face": "Monocraft",
"style": "Regular",
"size": 100,
"flags": 0
},
"antialiasing": false
},
"mixers": 0,
"sync": 0,
"flags": 0,
"volume": 1.0,
"balance": 0.5,
"enabled": true,
"muted": false,
"push-to-mute": false,
"push-to-mute-delay": 0,
"push-to-talk": false,
"push-to-talk-delay": 0,
"hotkeys": {},
"deinterlace_mode": 0,
"deinterlace_field_order": 0,
"monitoring_type": 0,
"private_settings": {}
},
{
"prev_ver": 503447555,
"name": "nowplaying",
"uuid": "2778ed68-2717-45e4-bf7d-f96c5df940bc",
"id": "text_ft2_source",
"versioned_id": "text_ft2_source_v2",
"settings": {
"font": {
"face": "Noto Sans Mono CJK JP",
"style": "Bold",
"size": 100,
"flags": 1
},
"antialiasing": true,
"color1": 4294967295,
"color2": 4294967295,
"text": "Video by author"
},
"mixers": 0,
"sync": 0,
"flags": 0,
"volume": 1.0,
"balance": 0.5,
"enabled": true,
"muted": false,
"push-to-mute": false,
"push-to-mute-delay": 0,
"push-to-talk": false,
"push-to-talk-delay": 0,
"hotkeys": {},
"deinterlace_mode": 0,
"deinterlace_field_order": 0,
"monitoring_type": 0,
"private_settings": {}
},
{
"prev_ver": 503447555,
"name": "player",
"uuid": "b915c093-dd10-4b86-9047-d6c069da09c9",
"id": "vlc_source",
"versioned_id": "vlc_source",
"settings": {
"playlist": [
{
"hidden": false,
"selected": false,
"value": "Ignore/this/error/the/bot/will/fix/it"
}
],
"loop": false,
"playback_behavior": "always_play",
"subtitle_enable": true,
"subtitle": 1
},
"mixers": 255,
"sync": 0,
"flags": 0,
"volume": 1.0,
"balance": 0.5,
"enabled": true,
"muted": false,
"push-to-mute": false,
"push-to-mute-delay": 0,
"push-to-talk": false,
"push-to-talk-delay": 0,
"hotkeys": {
"libobs.mute": [],
"libobs.unmute": [],
"libobs.push-to-mute": [],
"libobs.push-to-talk": [],
"VLCSource.PlayPause": [],
"VLCSource.Restart": [],
"VLCSource.Stop": [],
"VLCSource.PlaylistNext": [],
"VLCSource.PlaylistPrev": []
},
"deinterlace_mode": 0,
"deinterlace_field_order": 0,
"monitoring_type": 0,
"private_settings": {}
},
{
"prev_ver": 503447555,
"name": "screenshare",
"uuid": "1c5d73c6-f2ed-4468-a78d-80a3cb669298",
"id": "scene",
"versioned_id": "scene",
"settings": {
"id_counter": 10,
"custom_size": false,
"items": [
{
"name": "Wayland output(dmabuf)",
"source_uuid": "4f0ff3e5-ad9d-495b-8c9b-34ad166791e4",
"visible": true,
"locked": false,
"rot": 0.0,
"pos": {
"x": 0.0,
"y": 0.0
},
"scale": {
"x": 1.0,
"y": 1.0
},
"align": 5,
"bounds_type": 0,
"bounds_align": 0,
"bounds_crop": false,
"bounds": {
"x": 0.0,
"y": 0.0
},
"crop_left": 0,
"crop_top": 0,
"crop_right": 0,
"crop_bottom": 0,
"id": 10,
"group_item_backup": false,
"scale_filter": "disable",
"blend_method": "default",
"blend_type": "normal",
"show_transition": {
"duration": 0
},
"hide_transition": {
"duration": 0
},
"private_settings": {}
}
]
},
"mixers": 0,
"sync": 0,
"flags": 0,
"volume": 1.0,
"balance": 0.5,
"enabled": true,
"muted": false,
"push-to-mute": false,
"push-to-mute-delay": 0,
"push-to-talk": false,
"push-to-talk-delay": 0,
"hotkeys": {
"OBSBasic.SelectScene": [],
"libobs.show_scene_item.10": [],
"libobs.hide_scene_item.10": []
},
"deinterlace_mode": 0,
"deinterlace_field_order": 0,
"monitoring_type": 0,
"private_settings": {}
},
{
"prev_ver": 503447555,
"name": "Text (FreeType 2)",
"uuid": "01d3bab9-cc17-4702-acc6-6e25319188eb",
"id": "text_ft2_source",
"versioned_id": "text_ft2_source_v2",
"settings": {
"text": "Video still downloading :(\n OR\nan unhandled error has occurred",
"font": {
"face": "Monocraft",
"style": "Regular",
"size": 100,
"flags": 0
},
"word_wrap": false,
"outline": false,
"drop_shadow": false
},
"mixers": 0,
"sync": 0,
"flags": 0,
"volume": 1.0,
"balance": 0.5,
"enabled": true,
"muted": false,
"push-to-mute": false,
"push-to-mute-delay": 0,
"push-to-talk": false,
"push-to-talk-delay": 0,
"hotkeys": {},
"deinterlace_mode": 0,
"deinterlace_field_order": 0,
"monitoring_type": 0,
"private_settings": {}
},
{
"prev_ver": 503447555,
"name": "waiting",
"uuid": "c7ebc81c-31a1-4ed8-b492-1c5729562465",
"id": "scene",
"versioned_id": "scene",
"settings": {
"id_counter": 2,
"custom_size": false,
"items": [
{
"name": "Image",
"source_uuid": "a9588972-73f5-4176-a837-5492bba04b6a",
"visible": true,
"locked": false,
"rot": 0.0,
"pos": {
"x": 0.0,
"y": 0.0
},
"scale": {
"x": 1.0,
"y": 1.0
},
"align": 5,
"bounds_type": 0,
"bounds_align": 0,
"bounds_crop": false,
"bounds": {
"x": 0.0,
"y": 0.0
},
"crop_left": 0,
"crop_top": 0,
"crop_right": 0,
"crop_bottom": 0,
"id": 2,
"group_item_backup": false,
"scale_filter": "disable",
"blend_method": "default",
"blend_type": "normal",
"show_transition": {
"duration": 0
},
"hide_transition": {
"duration": 0
},
"private_settings": {}
}
]
},
"mixers": 0,
"sync": 0,
"flags": 0,
"volume": 1.0,
"balance": 0.5,
"enabled": true,
"muted": false,
"push-to-mute": false,
"push-to-mute-delay": 0,
"push-to-talk": false,
"push-to-talk-delay": 0,
"hotkeys": {
"OBSBasic.SelectScene": [],
"libobs.show_scene_item.2": [],
"libobs.hide_scene_item.2": []
},
"deinterlace_mode": 0,
"deinterlace_field_order": 0,
"monitoring_type": 0,
"private_settings": {}
},
{
"prev_ver": 503447555,
"name": "Wayland output(dmabuf)",
"uuid": "4f0ff3e5-ad9d-495b-8c9b-34ad166791e4",
"id": "wlrobs-dmabuf",
"versioned_id": "wlrobs-dmabuf",
"settings": {
"output": "HDMI-A-2"
},
"mixers": 0,
"sync": 0,
"flags": 0,
"volume": 1.0,
"balance": 0.5,
"enabled": true,
"muted": false,
"push-to-mute": false,
"push-to-mute-delay": 0,
"push-to-talk": false,
"push-to-talk-delay": 0,
"hotkeys": {},
"deinterlace_mode": 0,
"deinterlace_field_order": 0,
"monitoring_type": 0,
"private_settings": {}
},
{
"prev_ver": 503447555,
"name": "youtube",
"uuid": "8df47719-9e3b-4c24-99c8-fbfa5ee715bf",
"id": "scene",
"versioned_id": "scene",
"settings": {
"id_counter": 13,
"custom_size": false,
"items": [
{
"name": "notice",
"source_uuid": "e4912bae-e9c8-4574-9e7b-898963c4b20e",
"visible": false,
"locked": true,
"rot": 0.0,
"pos": {
"x": 0.0,
"y": 0.0
},
"scale": {
"x": 1.0,
"y": 1.0
},
"align": 5,
"bounds_type": 2,
"bounds_align": 0,
"bounds_crop": false,
"bounds": {
"x": 1920.0,
"y": 1080.0
},
"crop_left": 0,
"crop_top": 0,
"crop_right": 0,
"crop_bottom": 0,
"id": 11,
"group_item_backup": false,
"scale_filter": "disable",
"blend_method": "default",
"blend_type": "normal",
"show_transition": {
"duration": 0
},
"hide_transition": {
"duration": 0
},
"private_settings": {}
},
{
"name": "player",
"source_uuid": "b915c093-dd10-4b86-9047-d6c069da09c9",
"visible": true,
"locked": true,
"rot": 0.0,
"pos": {
"x": 0.0,
"y": 0.0
},
"scale": {
"x": 1.0,
"y": 1.0
},
"align": 5,
"bounds_type": 2,
"bounds_align": 0,
"bounds_crop": false,
"bounds": {
"x": 1920.0,
"y": 1080.0
},
"crop_left": 0,
"crop_top": 0,
"crop_right": 0,
"crop_bottom": 0,
"id": 9,
"group_item_backup": false,
"scale_filter": "disable",
"blend_method": "default",
"blend_type": "normal",
"show_transition": {
"duration": 0
},
"hide_transition": {
"duration": 0
},
"private_settings": {}
},
{
"name": "nowplaying",
"source_uuid": "2778ed68-2717-45e4-bf7d-f96c5df940bc",
"visible": false,
"locked": false,
"rot": 0.0,
"pos": {
"x": 5.0,
"y": 922.0
},
"scale": {
"x": 1.0,
"y": 1.0
},
"align": 5,
"bounds_type": 2,
"bounds_align": 0,
"bounds_crop": false,
"bounds": {
"x": 1920.0,
"y": 159.0
},
"crop_left": 0,
"crop_top": 0,
"crop_right": 0,
"crop_bottom": 0,
"id": 13,
"group_item_backup": false,
"scale_filter": "disable",
"blend_method": "default",
"blend_type": "normal",
"show_transition": {
"duration": 0
},
"hide_transition": {
"duration": 0
},
"private_settings": {}
}
]
},
"mixers": 0,
"sync": 0,
"flags": 0,
"volume": 1.0,
"balance": 0.5,
"enabled": true,
"muted": false,
"push-to-mute": false,
"push-to-mute-delay": 0,
"push-to-talk": false,
"push-to-talk-delay": 0,
"hotkeys": {
"OBSBasic.SelectScene": [],
"libobs.show_scene_item.11": [],
"libobs.hide_scene_item.11": [],
"libobs.show_scene_item.9": [],
"libobs.hide_scene_item.9": [],
"libobs.show_scene_item.13": [],
"libobs.hide_scene_item.13": []
},
"deinterlace_mode": 0,
"deinterlace_field_order": 0,
"monitoring_type": 0,
"private_settings": {}
}
]
}

5
requirements.txt Normal file
View file

@ -0,0 +1,5 @@
disnake
obsws_python
netscape-cookies
blinker==1.7.0
selenium-wire

View file

@ -6,8 +6,12 @@ pkgs.mkShell {
pkgs.python311Packages.selenium
pkgs.python311Packages.discordpy
pkgs.python311Packages.pip
pkgs.python311Packages.yt-dlp
pkgs.python311Packages.python-dotenv
pkgs.obs-studio
pkgs.python311Packages.venvShellHook
pkgs.ffmpeg
pkgs.discord
pkgs.mpv
];
venvDir = "./.venv";
}

5
wm.sh Executable file
View file

@ -0,0 +1,5 @@
Xwayland -geometry 1920x1080 -fullscreen :12 &
sleep 2
DISPLAY=:12 icewm --display=:12 &
env -u WAYLAND_DISPLAY DISPLAY=:12 obs --disable-shutdown-check --startstreaming --startvirtualcam --multi --disable-missing-files-check --scene nosignal &
env DISPLAY=:12 vlc -f srt://127.0.0.1:9999?transtype=live