From b5fbc7e1a07e0aa586a5aca619dd913b31732d8e Mon Sep 17 00:00:00 2001 From: Joshua Arulsamy Date: Thu, 25 Jun 2020 22:05:47 -0600 Subject: [PATCH 1/6] :heavy_plus_sign: Added missing dependencies --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index cc52a74..b231446 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,4 @@ fuzzywuzzy==0.18.0 python-Levenshtein==0.12.0 pynacl==1.4.0 ffmpeg==1.4 +PyYAML==5.3.1 From 59a32e688e446e6df100dea705170afccda063ca Mon Sep 17 00:00:00 2001 From: Joshua Arulsamy Date: Sun, 26 Jul 2020 22:06:51 -0600 Subject: [PATCH 2/6] :see_no_evil: Add more config files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 75162b2..872eb7d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Project specific config.yaml +config/ # Byte-compiled / optimized / DLL files __pycache__/ From 4043c972c07cde1b1119f994eb1b72acaa8d217c Mon Sep 17 00:00:00 2001 From: Joshua Arulsamy Date: Sun, 26 Jul 2020 22:07:37 -0600 Subject: [PATCH 3/6] :sparkles: Add prelimary docker files --- Dockerfile | 17 +++++++++++++++++ docker-compose.yml | 13 +++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 Dockerfile create mode 100644 docker-compose.yml diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f330945 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,17 @@ +# Python 3.7 +FROM python:3.7 + +# All source code +WORKDIR /src + +# Copy of dependency manifest +COPY requirements.txt . + +# Install all dependencies. +RUN pip install -r requirements.txt + +# Copy PlexBot over to src. +COPY PlexBot/ PlexBot + +# Run the bot +CMD ["python", "-OO", "-m", "PlexBot"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..7746aaa --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,13 @@ +version: "3" +services: + plex-bot: + container_name: "PlexBot" + build: . + environment: + - PUID=1000 + - PGID=1000 + - TZ=America/Denver + # Required dir for configuration files + volumes: + - "./config:/config:ro" + restart: unless-stopped From ac25330f70ee6f9fd19a538322cfdeadfe0a6dd4 Mon Sep 17 00:00:00 2001 From: Joshua Arulsamy Date: Sun, 26 Jul 2020 22:08:00 -0600 Subject: [PATCH 4/6] :loud_sound: Add basic logging --- PlexBot/__init__.py | 34 +++++++++++++++++++++++++++++++--- PlexBot/__main__.py | 10 ++++++++++ PlexBot/bot.py | 31 ++++++++++++++++++++++++++----- sample-config.yaml | 4 ++++ 4 files changed, 71 insertions(+), 8 deletions(-) diff --git a/PlexBot/__init__.py b/PlexBot/__init__.py index b809c0b..e7b1f22 100644 --- a/PlexBot/__init__.py +++ b/PlexBot/__init__.py @@ -1,8 +1,36 @@ import yaml +from pathlib import Path +from typing import Dict +import logging +import sys + +FORMAT = "%(asctime)s %(levelname)s: [%(filename)s:%(lineno)s - %(funcName)20s() ] %(message)s" + +logging.basicConfig(format=FORMAT) +logger = logging.getLogger("PlexBot") -def load_config(filename: str) -> None: - with open(filename, "r") as f: - config = yaml.safe_load(f) +def load_config(filename: str) -> Dict[str, str]: + + # All config files should be in /config + # for docker deployment. + filename = Path("/config", filename) + try: + with open(filename, "r") as f: + config = yaml.safe_load(f) + except FileNotFoundError: + logging.fatal("Configuration file not found.") + sys.exit(-1) + + # Convert str level type to logging constant + levels = { + "DEBUG": logging.DEBUG, + "INFO": logging.INFO, + "WARNING": logging.WARNING, + "ERROR": logging.ERROR, + "CRITICAL": logging.CRITICAL, + } + level = config["general"]["log_level"] + config["general"]["log_level"] = levels[level.upper()] return config diff --git a/PlexBot/__main__.py b/PlexBot/__main__.py index 5e365a4..8901ad7 100644 --- a/PlexBot/__main__.py +++ b/PlexBot/__main__.py @@ -3,7 +3,11 @@ from discord.ext.commands import Bot from .bot import General from .bot import Plex from PlexBot import load_config +from . import FORMAT +import logging + +# Load config from file config = load_config("config.yaml") BOT_PREFIX = config["discord"]["prefix"] @@ -12,6 +16,12 @@ TOKEN = config["discord"]["token"] BASE_URL = config["plex"]["base_url"] PLEX_TOKEN = config["plex"]["token"] LIBRARY_NAME = config["plex"]["library_name"] +LOG_LEVEL = config["general"]["log_level"] + +# Set appropiate log level +logger = logging.getLogger("PlexBot") +logging.basicConfig(format=FORMAT) +logger.setLevel(LOG_LEVEL) bot = Bot(command_prefix=BOT_PREFIX) bot.add_cog(General(bot)) diff --git a/PlexBot/bot.py b/PlexBot/bot.py index 8484647..8e1c06f 100644 --- a/PlexBot/bot.py +++ b/PlexBot/bot.py @@ -7,6 +7,10 @@ from discord.ext.commands import command from fuzzywuzzy import fuzz from plexapi.server import PlexServer +import logging + +logger = logging.getLogger("PlexBot") + class General(commands.Cog): def __init__(self, bot): @@ -16,6 +20,7 @@ class General(commands.Cog): async def kill(self, ctx): await ctx.send(f"Stopping upon the request of {ctx.author.mention}") await self.bot.close() + logger.info(f"Stopping upon the request of {ctx.author.mention}") class Plex(commands.Cog): @@ -29,7 +34,11 @@ class Plex(commands.Cog): self.music = self.pms.library.section(self.library_name) self.vc = None + self.current_track = None self.play_queue = Queue() + + logger.info("Started bot successfully") + # self.callback_ctx = None def _search_tracks(self, title): @@ -51,10 +60,14 @@ class Plex(commands.Cog): await ctx.send(f"Hello {member}") async def _after_callback(self, error=None): - track = self.play_queue.get() - audio_stream = FFmpegPCMAudio(track.getStreamURL()) - self.vc.play(audio_stream) - await self.callback_ctx.send(f"Playing {track.title}") + if self.play_queue.empty(): + self.current_track = None + else: + track = self.play_queue.get() + audio_stream = FFmpegPCMAudio(track.getStreamURL()) + self.vc.play(audio_stream) + self.current_track = track + await self.callback_ctx.send(f"Playing {track.title}") @command() async def play(self, ctx, *args): @@ -67,16 +80,21 @@ class Plex(commands.Cog): return if not self.vc: self.vc = await ctx.author.voice.channel.connect() + logger.debug("Connected to vc") if self.vc.is_playing(): self.play_queue.put(track) self.callback_ctx = ctx await ctx.send(f"Added {track.title} to queue.") + logger.debug(f"Added {track.title} to queue.") else: audio_stream = FFmpegPCMAudio(track_url) self.vc.play(audio_stream, after=self._after_callback) + self.current_track = track + logger.debug(f"Playing {track.title}") await ctx.send(f"Playing {track.title}") else: + logger.debug(f"{title} was not found.") await ctx.send("Song not found!") @command() @@ -85,7 +103,6 @@ class Plex(commands.Cog): self.vc.stop() await self.vc.disconnect() self.vc = None - await ctx.send("Stopped") @command() @@ -106,3 +123,7 @@ class Plex(commands.Cog): await self.vc.stop() if not self.play_queue.empty(): await self._after_callback() + + @command() + async def np(self, ctx): + await ctx.send(f"Currently playing: {self.current_track.title}") diff --git a/sample-config.yaml b/sample-config.yaml index b01ad15..22273fd 100644 --- a/sample-config.yaml +++ b/sample-config.yaml @@ -1,3 +1,7 @@ +general: + # Options: debug, info, warning, error, critical + log_level: "info" + discord: prefix: "?" token: "" From 7085349b192cf7b13bb7b7410a887104944380c9 Mon Sep 17 00:00:00 2001 From: Joshua Arulsamy Date: Tue, 4 Aug 2020 03:59:10 -0600 Subject: [PATCH 5/6] :bug: Fix broken playback from missing dependency --- Dockerfile | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index f330945..b1501b2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,12 @@ # Python 3.7 FROM python:3.7 +# Update system +RUN apt-get -y update +RUN apt-get -y upgrade +# Install ffmpeg +RUN apt-get install -y ffmpeg + # All source code WORKDIR /src @@ -14,4 +20,5 @@ RUN pip install -r requirements.txt COPY PlexBot/ PlexBot # Run the bot -CMD ["python", "-OO", "-m", "PlexBot"] +# CMD ["python", "-OO", "-m", "PlexBot"] +CMD ["python", "-m", "PlexBot"] From 4c75d4c27f7c5a2727e7eab76d8920aacaa23e83 Mon Sep 17 00:00:00 2001 From: Joshua Arulsamy Date: Tue, 4 Aug 2020 03:59:48 -0600 Subject: [PATCH 6/6] :bug: Polished queue issues Queue now actually works, async callback is properly awaited. --- PlexBot/__init__.py | 7 ++++--- PlexBot/bot.py | 32 +++++++++++++++++++++----------- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/PlexBot/__init__.py b/PlexBot/__init__.py index e7b1f22..e3b062f 100644 --- a/PlexBot/__init__.py +++ b/PlexBot/__init__.py @@ -1,8 +1,9 @@ -import yaml -from pathlib import Path -from typing import Dict import logging import sys +from pathlib import Path +from typing import Dict + +import yaml FORMAT = "%(asctime)s %(levelname)s: [%(filename)s:%(lineno)s - %(funcName)20s() ] %(message)s" diff --git a/PlexBot/bot.py b/PlexBot/bot.py index 8e1c06f..f2c05c8 100644 --- a/PlexBot/bot.py +++ b/PlexBot/bot.py @@ -1,3 +1,4 @@ +import logging from queue import Queue import discord @@ -5,10 +6,9 @@ from discord import FFmpegPCMAudio from discord.ext import commands from discord.ext.commands import command from fuzzywuzzy import fuzz +from plexapi.exceptions import Unauthorized from plexapi.server import PlexServer -import logging - logger = logging.getLogger("PlexBot") @@ -30,7 +30,12 @@ class Plex(commands.Cog): self.plex_token = plex_token self.library_name = lib_name - self.pms = PlexServer(self.base_url, self.plex_token) + try: + self.pms = PlexServer(self.base_url, self.plex_token) + except Unauthorized: + logger.fatal("Invalid Plex token, stopping...") + raise Unauthorized("Invalid Plex token") + self.music = self.pms.library.section(self.library_name) self.vc = None @@ -39,8 +44,6 @@ class Plex(commands.Cog): logger.info("Started bot successfully") - # self.callback_ctx = None - def _search_tracks(self, title): tracks = self.music.searchTracks() score = [None, -1] @@ -60,17 +63,24 @@ class Plex(commands.Cog): await ctx.send(f"Hello {member}") async def _after_callback(self, error=None): + logger.debug("After callbacked") if self.play_queue.empty(): self.current_track = None + logger.debug("No tracks left in queue, returning") else: track = self.play_queue.get() audio_stream = FFmpegPCMAudio(track.getStreamURL()) - self.vc.play(audio_stream) self.current_track = track + logger.debug(f"Started playing next song in queue: {track.title}") + self.vc.play(audio_stream) await self.callback_ctx.send(f"Playing {track.title}") @command() async def play(self, ctx, *args): + if not len(args): + await ctx.send(f"Usage: {BOT_PREFIX}play TITLE_OF_SONG") + return + title = " ".join(args) track = self._search_tracks(title) if track: @@ -80,7 +90,7 @@ class Plex(commands.Cog): return if not self.vc: self.vc = await ctx.author.voice.channel.connect() - logger.debug("Connected to vc") + logger.debug("Connected to vc.") if self.vc.is_playing(): self.play_queue.put(track) @@ -95,7 +105,7 @@ class Plex(commands.Cog): await ctx.send(f"Playing {track.title}") else: logger.debug(f"{title} was not found.") - await ctx.send("Song not found!") + await ctx.send(f"{title} was not found.") @command() async def stop(self, ctx): @@ -119,10 +129,10 @@ class Plex(commands.Cog): @command() async def skip(self, ctx): + logger.debug("Skip") if self.vc: - await self.vc.stop() - if not self.play_queue.empty(): - await self._after_callback() + self.vc.stop() + await self._after_callback() @command() async def np(self, ctx):