From 9d84129e130bab336c4d78a1da090bd9b6aff67b Mon Sep 17 00:00:00 2001 From: Jason Date: Thu, 18 Apr 2019 02:30:20 -0400 Subject: [PATCH 1/4] Added PGUI configs --- config.ini | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/config.ini b/config.ini index 40ed3a66..23bfff07 100644 --- a/config.ini +++ b/config.ini @@ -35,6 +35,21 @@ CommandToken = ! ; The number of commands to store in the command history tracker [Must be an integer] CommandHistoryLimit = 25 +[PGUI_Settings] +; Limitations: https://doc.qt.io/qt-5/richtext-html-subset.html +; Default Canvas BG Color (https://doc.qt.io/qt-5/qcolor.html#setNamedColor) +CanvasBGColor = black +; Default Canvas Alignment +CanvasAlignment = center +; Default Canvas Border +CanvasBorder = 0 +; Default Canvas Text Color +CanvasTextColor = white +; Default Header Text Color +HeaderTextColor = red +; Default Index Text Color +IndexTextColor = cyan + [Bot_Information] BotVersion = v1.8.0_p1 AboutText =
JJMumbleBot is a plugin-based python3 mumble bot client.
https://github.com/DuckBoss/JJMumbleBot
From f5f914a9b9c5a7da05096c30467d58995a70aec8 Mon Sep 17 00:00:00 2001 From: Jason Date: Thu, 18 Apr 2019 02:30:43 -0400 Subject: [PATCH 2/4] Updated bot version to v1.8.1 --- config.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.ini b/config.ini index 23bfff07..e7b3bddd 100644 --- a/config.ini +++ b/config.ini @@ -51,6 +51,6 @@ HeaderTextColor = red IndexTextColor = cyan [Bot_Information] -BotVersion = v1.8.0_p1 +BotVersion = v1.8.1 AboutText =
JJMumbleBot is a plugin-based python3 mumble bot client.
https://github.com/DuckBoss/JJMumbleBot
KnownBugs =
#### Known Bugs ####
From 5b9d608a8c1a59131cbca756fd40cb27f3e028da Mon Sep 17 00:00:00 2001 From: Jason Date: Thu, 18 Apr 2019 02:40:16 -0400 Subject: [PATCH 3/4] Updated scripts to use PGUI configs - Updated all built-in plugins to use the PGUI configs unless overriden - Added version information to !about command outputs - Left justified some PGUI elements - Removed some unused code - Updated privileges python file to use python3.6+ string formatting --- JJMumbleBot.py | 10 ++-- pgui.py | 30 ++++++++++-- plugins/bot_commands/bot_commands.py | 10 ++-- plugins/help/help.py | 1 - plugins/images/images.py | 8 ++-- plugins/sound_board/sound_board.py | 10 ++-- plugins/youtube/youtube.py | 4 +- privileges.py | 69 +++++++++++++--------------- 8 files changed, 79 insertions(+), 63 deletions(-) diff --git a/JJMumbleBot.py b/JJMumbleBot.py index 763af86d..18767764 100644 --- a/JJMumbleBot.py +++ b/JJMumbleBot.py @@ -355,9 +355,9 @@ def process_core_commands(self, command, text): elif command == "aliases": if not pv.plugin_privilege_checker(self.mumble, text, command, self.priv_path): return - cur_text = "Registered Aliases:" + cur_text = f"Registered Aliases:" for i, alias in enumerate(aliases.aliases): - cur_text += f"
[{alias}] - [{BeautifulSoup(aliases.aliases[alias], 'html.parser').get_text()}]" + cur_text += f"
[{alias}] - [{BeautifulSoup(aliases.aliases[alias], 'html.parser').get_text()}]" if i % 50 == 0 and i != 0: # utils.echo(utils.get_my_channel(self.mumble), cur_text) GM.gui.quick_gui( @@ -478,7 +478,7 @@ def process_core_commands(self, command, text): return # utils.echo(utils.get_my_channel(self.mumble), utils.get_about()) GM.gui.quick_gui( - utils.get_about(), + f"{utils.get_about()}
{utils.get_bot_name()} is on version {utils.get_version()}", text_type='header', box_align='left') return @@ -486,9 +486,9 @@ def process_core_commands(self, command, text): elif command == "history": if not pv.plugin_privilege_checker(self.mumble, text, command, self.priv_path): return - cur_text = "Command History:" + cur_text = f"Command History:" for i, item in enumerate(self.cmd_history.queue_storage): - cur_text += f"
[{i}]: {item}" + cur_text += f"
[{i}] - {item}" if i % 50 == 0 and i != 0: # utils.echo(utils.get_my_channel(self.mumble), cur_text) GM.gui.quick_gui( diff --git a/pgui.py b/pgui.py index 564cb38d..70db56ff 100644 --- a/pgui.py +++ b/pgui.py @@ -1,5 +1,6 @@ from helpers.gui_helper import GUIHelper from helpers.global_access import debug_print, reg_print +from helpers.global_access import GlobalMods as GM import utils @@ -13,22 +14,39 @@ def __init__(self, mumble): self.mumble = mumble debug_print("Pseudo-GUI initialized.") - def quick_gui(self, content, text_type="data", text_color='white', text_font='Calibri', text_align="center", bgcolor="black", border="0", box_align="center", row_align="center", cellspacing="5", channel=None, user=None): + def quick_gui(self, content, text_type="data", text_color=None, text_font='Calibri', text_align="center", bgcolor=None, border=None, box_align=None, row_align="center", cellpadding="5", cellspacing="5", channel=None, user=None): if self.box_open: return False if channel is None: channel = utils.get_my_channel(self.mumble) - self.open_box(bgcolor=bgcolor, border=border, align=box_align, cellspacing=cellspacing) + if bgcolor is None: + bgcolor = GM.cfg['PGUI_Settings']['CanvasBGColor'] + print(bgcolor) + if box_align is None: + box_align = GM.cfg['PGUI_Settings']['CanvasAlignment'] + if border is None: + border = GM.cfg['PGUI_Settings']['CanvasBorder'] + if text_color is None: + text_color = GM.cfg['PGUI_Settings']['CanvasTextColor'] + + self.open_box(bgcolor=bgcolor, border=border, align=box_align, cellspacing=cellspacing, cellpadding=cellpadding) content = self.make_content(content, text_type=text_type, text_color=text_color, text_font=text_font, text_align=text_align) self.append_row(content, align=row_align) self.close_box() self.display_box(channel=channel, user=user) self.clear_display() - def open_box(self, bgcolor="black", border="0", align="center", cellspacing="5"): + def open_box(self, bgcolor=None, border=None, align=None, cellspacing="5", cellpadding="5"): if self.box_open: return False - self.content = f'' + if bgcolor is None: + bgcolor = GM.cfg['PGUI_Settings']['CanvasBGColor'] + if align is None: + align = GM.cfg['PGUI_Settings']['CanvasAlignment'] + if border is None: + border = GM.cfg['PGUI_Settings']['CanvasBorder'] + + self.content = f'
' self.box_open = True return True @@ -51,9 +69,11 @@ def append_content(self, content): self.content += content return True - def make_content(self, text, text_type="data", text_color='white', text_font='Calibri', text_align="center"): + def make_content(self, text, text_type="data", text_color=None, text_font='Calibri', text_align="center"): if not self.box_open: return None + if text_color is None: + text_color = GM.cfg['PGUI_Settings']['CanvasTextColor'] new_content = GUIHelper.content(text, tt=text_type, tc=text_color, tf=text_font, ta=text_align) return new_content diff --git a/plugins/bot_commands/bot_commands.py b/plugins/bot_commands/bot_commands.py index 0525affe..06d93e4e 100644 --- a/plugins/bot_commands/bot_commands.py +++ b/plugins/bot_commands/bot_commands.py @@ -123,7 +123,7 @@ def process_command(self, mumble, text): return # utils.echo(utils.get_my_channel(mumble), # f"{pv.get_all_privileges()}") - GM.gui.quick_gui(f"{pv.get_all_privileges()}", text_type='header', box_align='center') + GM.gui.quick_gui(f"{pv.get_all_privileges()}", text_type='header', box_align='left', text_align='left') return elif command == "setprivileges": @@ -179,13 +179,13 @@ def process_command(self, mumble, text): # utils.echo(utils.get_my_channel(mumble), # "
User: {parameter} added to the blacklist.
Reason: {reason}") GM.gui.quick_gui(f"User: {parameter} added to the blacklist.
Reason: {reason}", text_type='header', - box_align='left') + box_align='left', text_align='left') GM.logger.info(f"
Blacklisted user: {parameter}
Reason: {reason}") except IndexError: - utils.echo(utils.get_my_channel(mumble), - pv.get_blacklist()) + # utils.echo(utils.get_my_channel(mumble), + # pv.get_blacklist()) GM.gui.quick_gui(pv.get_blacklist(), text_type='header', - box_align='center') + box_align='left', text_align='left') return elif command == "whitelist": diff --git a/plugins/help/help.py b/plugins/help/help.py index f411c95f..aa63ba88 100644 --- a/plugins/help/help.py +++ b/plugins/help/help.py @@ -15,7 +15,6 @@ class Plugin(PluginBase): !images_help/!img_help: Displays the images plugin help screen.
\ !randomizer_help: Displays the randomizer plugin help screen.
\ !uptime_help Displays the uptime plugin help screen.
" - plugin_prepend_text = "
Plugin Version: " plugin_version = "1.8.0" priv_path = "help/help_privileges.csv" bot_plugins = {} diff --git a/plugins/images/images.py b/plugins/images/images.py index 5780cd48..d5ca3119 100644 --- a/plugins/images/images.py +++ b/plugins/images/images.py @@ -70,10 +70,10 @@ def process_command(self, mumble, text): for file_item in os.listdir(utils.get_permanent_media_dir() + "images/"): if file_item.endswith(".jpg"): internal_list.append( - f"
[{file_counter}]: {file_item}") + f"
[{file_counter}] - {file_item}") file_counter += 1 - cur_text = "Local Image Files" + cur_text = f"Local Image Files:" if len(internal_list) == 0: cur_text += "
There are no local image files available." GM.gui.quick_gui(cur_text, text_type='header', box_align='left', user=mumble.users[text.actor]['name']) @@ -101,10 +101,10 @@ def process_command(self, mumble, text): for file_item in os.listdir(utils.get_permanent_media_dir() + "images/"): if file_item.endswith(".jpg"): internal_list.append( - f"
[{file_counter}]: {file_item}") + f"
[{file_counter}] - {file_item}") file_counter += 1 - cur_text = "Local Image Files" + cur_text = f"Local Image Files:" if len(internal_list) == 0: cur_text += "
There are no local image files available." GM.gui.quick_gui(cur_text, text_type='header', box_align='left') diff --git a/plugins/sound_board/sound_board.py b/plugins/sound_board/sound_board.py index 2c01d472..b7a7e233 100644 --- a/plugins/sound_board/sound_board.py +++ b/plugins/sound_board/sound_board.py @@ -66,13 +66,13 @@ def process_command(self, mumble, text): for file_item in os.listdir(utils.get_permanent_media_dir()+"sound_board/"): if file_item.endswith(".wav"): - internal_list.append(f"
[{file_counter}]: {file_item}") + internal_list.append(f"
[{file_counter}] - {file_item}") file_counter += 1 - cur_text = "Local Sound Board Files" + cur_text = f"Local Sound Board Files:" if len(internal_list) == 0: cur_text += "
There are no local sound board files available." - GM.gui.quick_gui(cur_text, text_type='header', box_align='left') + GM.gui.quick_gui(cur_text, text_type='header', box_align='left', user=mumble.users[text.actor]['name']) GM.logger.info("Displayed a list of all local sound board files.") return @@ -97,10 +97,10 @@ def process_command(self, mumble, text): for file_item in os.listdir(utils.get_permanent_media_dir()+"sound_board/"): if file_item.endswith(".wav"): - internal_list.append(f"
[{file_counter}]: {file_item}") + internal_list.append(f"
[{file_counter}] - {file_item}") file_counter += 1 - cur_text = "
Local Sound Board Files" + cur_text = f"Local Sound Board Files:" if len(internal_list) == 0: cur_text += "
There are no local sound board files available." GM.gui.quick_gui(cur_text, text_type='header', box_align='left') diff --git a/plugins/youtube/youtube.py b/plugins/youtube/youtube.py index e97f286e..c063b444 100644 --- a/plugins/youtube/youtube.py +++ b/plugins/youtube/youtube.py @@ -467,10 +467,10 @@ def get_vid_list(self, search): return search_results_list def get_choices(self, all_searches): - list_urls = "Search Results:
" + list_urls = f"Search Results:
" for i in range(10): completed_url = "https://www.youtube.com" + all_searches[i]['href'] - list_urls += f"[{i}]: [{all_searches[i]['title']}]
" + list_urls += f"[{i}] - [{all_searches[i]['title']}]
" return list_urls def clear_queue(self): diff --git a/privileges.py b/privileges.py index a8dfdf0a..5ce46288 100644 --- a/privileges.py +++ b/privileges.py @@ -3,6 +3,7 @@ import utils import logging from helpers.global_access import debug_print, reg_print +from helpers.global_access import GlobalMods as GM users = {} @@ -17,12 +18,12 @@ class Privileges(Enum): def setup_privileges(): - with open("%s/privileges/privileges.csv" % utils.get_main_dir(), mode='r') as csvf: + with open(f"{utils.get_main_dir()}/privileges/privileges.csv", mode='r') as csvf: csvr = csv.DictReader(csvf) debug_print("Setting up user privileges...") for i, row in enumerate(csvr): users[row['user']] = int(row['level']) - logging.info("Added [%s-%s] to the user privilege list." % (row['user'], row['level'])) + logging.info(f"Added [{row['user']}-{row['level']}] to the user privilege list.") def privileges_check(user): @@ -30,23 +31,23 @@ def privileges_check(user): return int(users[user['name']]) priv_path = "privileges/privileges.csv" - with open("%s/%s" % (utils.get_main_dir(), priv_path), mode='r') as csvf: + with open(f"{utils.get_main_dir()}/{priv_path}", mode='r') as csvf: csvr = csv.DictReader(csvf) for i, row in enumerate(csvr): if row['user'] == user['name']: users[user['name']] = int(row['level']) return int(users[user['name']]) - with open("%s/%s" % (utils.get_main_dir(), priv_path), mode='a', newline='') as csvf: + with open(f"{utils.get_main_dir()}/{priv_path}", mode='a', newline='') as csvf: headers = ['user', 'level'] csvw = csv.DictWriter(csvf, fieldnames=headers) csvw.writerow({'user': user['name'], 'level': 1}) users[user['name']] = 1 - debug_print("Added [%s-%s] to the user list." % (user['name'], 1)) + debug_print(f"Added [{user['name']}-{user['level']}] to the user list.") return int(users[user['name']]) def plugin_privileges_check(command, priv_path): - with open("%s/%s" % (utils.get_plugin_dir(), priv_path), mode='r') as csvf: + with open(f"{utils.get_plugin_dir()}/{priv_path}", mode='r') as csvf: csvr = csv.DictReader(csvf) for i, row in enumerate(csvr): if row['command'] == command: @@ -56,38 +57,38 @@ def plugin_privileges_check(command, priv_path): def plugin_privilege_checker(mumble, text, command, priv_path): if not privileges_check(mumble.users[text.actor]) >= plugin_privileges_check(command, priv_path): - reg_print("User [%s] does not have the user privileges to use this command: [%s]" % ( - mumble.users[text.actor]['name'], command)) - utils.echo(mumble.channels[mumble.users.myself['channel_id']], - "User [%s] does not have the user privileges to use this command: [%s]" % ( - mumble.users[text.actor]['name'], command)) + reg_print(f"User [{mumble.users[text.actor]['name']}] does not have the user privileges to use this command: [{command}]") + # utils.echo(mumble.channels[mumble.users.myself['channel_id']], + # f"User [{mumble.users[text.actor]['name']}] does not have the user privileges to use this command: [{command}]") + GM.gui.quick_gui(f"User [{mumble.users[text.actor]['name']}] does not have the user privileges to use this command: [{command}]", text_type='header', box_align='left') return False return True def get_all_privileges(): - priv_text = "
All user privileges:
" - with open("%s/privileges/privileges.csv" % utils.get_main_dir(), mode='r') as csvf: + priv_text = f"All User Privileges:" + with open(f"{utils.get_main_dir()}/privileges/privileges.csv", mode='r') as csvf: csvr = csv.DictReader(csvf) for i, row in enumerate(csvr): - priv_text += "[%s]: %s
" % ( - row['user'], row['level']) + priv_text += f"
[{row['user']}] - {row['level']}" return priv_text def get_all_active_privileges(): - priv_text = "
All user privileges:
" + priv_text = f"All User Privileges:" for i, user in enumerate(users.keys()): - priv_text += "[%s]: %s
" % (row['user'], row['level']) + priv_text += f"
[{row['user']}] - {row['level']}" return priv_text def get_blacklist(): - blklist_txt = "
Blacklist:
" + blklist_txt = f"Blacklisted Users:" + counter = 0 for i, user in enumerate(users.keys()): if users[user] == 0: - blklist_txt += "[%d]: %s
" % (i, user) - if blklist_txt == "
Blacklist:
": + blklist_txt += f"
[{counter}] - {user}" + counter += 1 + if blklist_txt == f"Blacklisted Users:": blklist_txt += "The blacklist is empty!" return blklist_txt @@ -96,16 +97,14 @@ def add_to_blacklist(username, sender): if username in users.keys(): if users[username] == 0: return False - with open("%s/privileges/privileges.csv" % utils.get_main_dir(), mode='r') as csvf: + with open(f"{utils.get_main_dir()}/privileges/privileges.csv", mode='r') as csvf: csvr = csv.reader(csvf) content = list(csvr) ind = [(i, j.index(username)) for i, j in enumerate(content) if username in j] if int(content[ind[0][0]][1]) >= 4: if privileges_check(sender) == 4: - debug_print("This administrator: [%s] tried to blacklist another administrator: [%s]" % ( - sender['name'], username)) - logging.warning("This administrator: [%s] tried to blacklist another administrator: [%s]" % ( - sender['name'], username)) + debug_print(f"This administrator: [{sender['name']}] tried to blacklist another administrator: [{username}]") + logging.warning(f"This administrator: [{sender['name']}] tried to blacklist another administrator: [{username}]") return content[ind[0][0]][1] = 0 users[username] = 0 @@ -116,7 +115,7 @@ def add_to_blacklist(username, sender): def remove_from_blacklist(username): if username in users.keys(): if users[username] == 0: - with open("%s/privileges/privileges.csv" % utils.get_main_dir(), mode='r') as csvf: + with open(f"{utils.get_main_dir()}/privileges/privileges.csv", mode='r') as csvf: csvr = csv.reader(csvf) content = list(csvr) ind = [(i, j.index(username)) for i, j in enumerate(content) if username in j] @@ -131,23 +130,21 @@ def remove_from_blacklist(username): def set_privileges(username, val, sender): if username in users.keys(): if username == sender['name']: - debug_print("This user: [%s] tried to modify their own user privileges. Modification denied." % (username)) + debug_print(f"This user: [{username}] tried to modify their own user privileges. Modification denied.") logging.warning( - "This user: [%s] tried to modify their own user privileges. Modification denied." % (username)) + f"This user: [{username}] tried to modify their own user privileges. Modification denied.") return - with open("%s/privileges/privileges.csv" % utils.get_main_dir(), mode='r') as csvf: + with open(f"{utils.get_main_dir()}/privileges/privileges.csv", mode='r') as csvf: csvr = csv.reader(csvf) content = list(csvr) ind = [(i, j.index(username)) for i, j in enumerate(content) if username in j] if int(content[ind[0][0]][1]) >= privileges_check(sender): if privileges_check(sender) == 4: debug_print( - "This administrator: [%s] tried to modify privileges for a user with equal/higher privileges: [%s]" % ( - sender['name'], username)) + f"This administrator: [{sender['name']}] tried to modify privileges for a user with equal/higher privileges: [{username}]") logging.warning( - "This administrator: [%s] tried to modify privileges for a user with equal/higher privileges: [%s]" % ( - sender['name'], username)) + f"This administrator: [{sender['name']}] tried to modify privileges for a user with equal/higher privileges: [{username}]") return content[ind[0][0]][1] = val users[username] = val @@ -157,18 +154,18 @@ def set_privileges(username, val, sender): def add_to_privileges(username, level): - with open("%s/privileges/privileges.csv" % utils.get_main_dir(), mode='a', newline='') as csvf: + with open(f"{utils.get_main_dir()}/privileges/privileges.csv", mode='a', newline='') as csvf: headers = ['user', 'level'] csvw = csv.DictWriter(csvf, fieldnames=headers) csvw.writerow({'user': username, 'level': level}) users[username] = level - debug_print("Added [%s-%s] to the user list." % (username, level)) + debug_print(f"Added [{username}-{level}] to the user list.") return True def overwrite_privileges(content): try: - with open("%s/privileges/privileges.csv" % utils.get_main_dir(), mode='w', newline='') as csvf: + with open(f"{utils.get_main_dir()}/privileges/privileges.csv", mode='w', newline='') as csvf: csvr = csv.writer(csvf) csvr.writerows(content) return True From 731606f605aa0c6f6ddc51f35d48b22cae6f790b Mon Sep 17 00:00:00 2001 From: Jason Date: Thu, 18 Apr 2019 02:45:44 -0400 Subject: [PATCH 4/4] Updated built-in plugins to v1.8.1 --- plugins/bot_commands/bot_commands.py | 2 +- plugins/help/help.py | 2 +- plugins/images/images.py | 2 +- plugins/sound_board/sound_board.py | 2 +- plugins/youtube/youtube.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/bot_commands/bot_commands.py b/plugins/bot_commands/bot_commands.py index 06d93e4e..c93aeea1 100644 --- a/plugins/bot_commands/bot_commands.py +++ b/plugins/bot_commands/bot_commands.py @@ -28,7 +28,7 @@ class Plugin(PluginBase): !reboot/!restart: Completely stops the bot and restarts it.
\ !about: Displays the bots about screen.
\ !spam_test: Spams 10 test messages in the channel. This is an admin-only command.
" - plugin_version = "1.8.0" + plugin_version = "1.8.1" priv_path = "bot_commands/bot_commands_privileges.csv" def __init__(self): diff --git a/plugins/help/help.py b/plugins/help/help.py index aa63ba88..c23f036e 100644 --- a/plugins/help/help.py +++ b/plugins/help/help.py @@ -15,7 +15,7 @@ class Plugin(PluginBase): !images_help/!img_help: Displays the images plugin help screen.
\ !randomizer_help: Displays the randomizer plugin help screen.
\ !uptime_help Displays the uptime plugin help screen.
" - plugin_version = "1.8.0" + plugin_version = "1.8.1" priv_path = "help/help_privileges.csv" bot_plugins = {} diff --git a/plugins/images/images.py b/plugins/images/images.py index d5ca3119..d8694f94 100644 --- a/plugins/images/images.py +++ b/plugins/images/images.py @@ -18,7 +18,7 @@ class Plugin(PluginBase): !post 'image_url': Posts the image from the url in the channel chat.
\ !img 'image_name': Posts locally hosted images in the channel chat. The image must be a jpg.
\ !imglist: Lists all locally hosted images." - plugin_version = "1.8.0" + plugin_version = "1.8.1" priv_path = "images/images_privileges.csv" def __init__(self): diff --git a/plugins/sound_board/sound_board.py b/plugins/sound_board/sound_board.py index b7a7e233..f59f4d84 100644 --- a/plugins/sound_board/sound_board.py +++ b/plugins/sound_board/sound_board.py @@ -22,7 +22,7 @@ class Plugin(PluginBase): !sbstop/!sbs: Stops the currently playing sound board track.
\ !sbdownload 'youtube_url' 'file_name': Downloads a sound clip from a youtube link and adds it to the sound board.
\ !sbdelete 'file_name.wav': Deletes a clip from the sound board storage. Be sure to specify the .wav extension." - plugin_version = "1.8.0" + plugin_version = "1.8.1" priv_path = "sound_board/sound_board_privileges.csv" exit_flag = False diff --git a/plugins/youtube/youtube.py b/plugins/youtube/youtube.py index c063b444..3e7d0f12 100644 --- a/plugins/youtube/youtube.py +++ b/plugins/youtube/youtube.py @@ -26,7 +26,7 @@ class Plugin(PluginBase): !queue/!q: Displays the youtube queue.
\ !song: Shows currently playing track.
\ !clear: Clears the current youtube queue.
" - plugin_version = "1.8.0" + plugin_version = "1.8.1" priv_path = "youtube/youtube_privileges.csv" ydl_opts = {