From a6fdb596db914aaa6550be099a0c4bb1dc0de8fe Mon Sep 17 00:00:00 2001 From: Phin <59180111+phin05@users.noreply.github.com> Date: Sat, 4 Nov 2023 23:43:42 +0530 Subject: [PATCH] Minor refactor and automatic installation of required packages --- .dockerignore | 2 + .github/workflows/release.yml | 2 +- Dockerfile | 2 +- {store => config}/__init__.py | 0 config/constants.py | 18 +++ core/__init__.py | 0 {services => core}/config.py | 6 +- .../DiscordRpcService.py => core/discord.py | 53 +++--- {services => core}/imgur.py | 6 +- services/PlexAlertListener.py => core/plex.py | 37 +++-- main.py | 152 +++++++++++------- services/__init__.py | 2 - services/cache.py | 28 ---- store/constants.py | 16 -- test.py | 16 -- utils/cache.py | 29 ++++ utils/dict.py | 6 +- utils/logging.py | 3 +- 18 files changed, 200 insertions(+), 178 deletions(-) rename {store => config}/__init__.py (100%) create mode 100644 config/constants.py create mode 100644 core/__init__.py rename {services => core}/config.py (90%) rename services/DiscordRpcService.py => core/discord.py (51%) rename {services => core}/imgur.py (84%) rename services/PlexAlertListener.py => core/plex.py (91%) delete mode 100644 services/__init__.py delete mode 100644 services/cache.py delete mode 100644 store/constants.py delete mode 100644 test.py create mode 100644 utils/cache.py diff --git a/.dockerignore b/.dockerignore index 00baf31..06a98b5 100644 --- a/.dockerignore +++ b/.dockerignore @@ -3,5 +3,7 @@ .github .gitignore CONTRIBUTING.md +data Dockerfile pyrightconfig.json +Showcase.psd diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b513139..bf2437a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -55,7 +55,7 @@ jobs: uses: docker/build-push-action@v4 with: context: . - platforms: linux/amd64,linux/arm64 + platforms: linux/amd64,linux/arm64,linux/arm/v7 push: true provenance: false tags: ${{ env.DOCKER_IMAGE_NAME }}:${{ env.VERSION }},${{ env.DOCKER_IMAGE_NAME }}:latest diff --git a/Dockerfile b/Dockerfile index 0e1cdec..f8599b4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,5 +7,5 @@ WORKDIR /app COPY requirements.txt . RUN pip install -r requirements.txt --no-cache-dir COPY . . -ENV IN_CONTAINER true +ENV DRPP_CONTAINER_DEMOTION_UID=$USER_UID CMD ["python", "main.py"] diff --git a/store/__init__.py b/config/__init__.py similarity index 100% rename from store/__init__.py rename to config/__init__.py diff --git a/config/constants.py b/config/constants.py new file mode 100644 index 0000000..e3fded3 --- /dev/null +++ b/config/constants.py @@ -0,0 +1,18 @@ +import os +import sys + +name = "Discord Rich Presence for Plex" +version = "2.3.5" + +plexClientID = "discord-rich-presence-plex" +discordClientID = "413407336082833418" + +dataDirectoryPath = "data" +configFilePath = os.path.join(dataDirectoryPath, "config.json") +cacheFilePath = os.path.join(dataDirectoryPath, "cache.json") +logFilePath = os.path.join(dataDirectoryPath, "console.log") + +isUnix = sys.platform in ["linux", "darwin"] +processID = os.getpid() +isInteractive = sys.stdin and sys.stdin.isatty() +containerDemotionUid = os.environ.get("DRPP_CONTAINER_DEMOTION_UID", "") diff --git a/core/__init__.py b/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/services/config.py b/core/config.py similarity index 90% rename from services/config.py rename to core/config.py index af65b63..c500807 100644 --- a/services/config.py +++ b/core/config.py @@ -1,5 +1,5 @@ -from store.constants import configFilePath -from utils.dict import merge +from config.constants import configFilePath +from utils.dict import copyDict from utils.logging import logger import json import models.config @@ -32,7 +32,7 @@ def loadConfig() -> None: os.rename(configFilePath, configFilePath.replace(".json", f"-{time.time():.0f}.json")) logger.exception("Failed to parse the application's config file. A new one will be created.") else: - merge(loadedConfig, config) + copyDict(loadedConfig, config) saveConfig() def saveConfig() -> None: diff --git a/services/DiscordRpcService.py b/core/discord.py similarity index 51% rename from services/DiscordRpcService.py rename to core/discord.py index 2fc6c4a..73c0a67 100644 --- a/services/DiscordRpcService.py +++ b/core/discord.py @@ -1,6 +1,4 @@ -# pyright: reportOptionalMemberAccess=none - -from store.constants import discordClientID, isUnix, processID +from config.constants import discordClientID, isUnix, processID from typing import Any, Optional from utils.logging import logger import asyncio @@ -10,36 +8,27 @@ import os import struct import time -class DiscordRpcService: +class DiscordIpcService: - ipcPipe = (((os.path.isdir("/run/app") and "/run/app") or os.environ.get("XDG_RUNTIME_DIR", None) or os.environ.get("TMPDIR", None) or os.environ.get("TMP", None) or os.environ.get("TEMP", None) or "/tmp") + "/discord-ipc-0") if isUnix else r"\\?\pipe\discord-ipc-0" - - def __init__(self) -> None: - self.loop: Optional[asyncio.AbstractEventLoop] = None - self.pipeReader: Optional[asyncio.StreamReader] = None - self.pipeWriter: Optional[Any] = None + def __init__(self): + self.ipcPipe = ("/run/app" if os.path.isdir("/run/app") else os.environ.get("XDG_RUNTIME_DIR", os.environ.get("TMPDIR", os.environ.get("TMP", os.environ.get("TEMP", "/tmp"))))) if isUnix else r"\\?\pipe\discord-ipc-0" + self.loop: asyncio.AbstractEventLoop = None # pyright: ignore[reportGeneralTypeIssues] + self.pipeReader: asyncio.StreamReader = None # pyright: ignore[reportGeneralTypeIssues] + self.pipeWriter: asyncio.StreamWriter = None # pyright: ignore[reportGeneralTypeIssues] self.connected = False - def connect(self) -> None: - if self.connected: - logger.debug("Attempt to connect Discord IPC Pipe while already connected") - return - logger.info("Connecting Discord IPC Pipe") - self.loop = asyncio.new_event_loop() - self.loop.run_until_complete(self.handshake()) - async def handshake(self) -> None: try: if isUnix: - self.pipeReader, self.pipeWriter = await asyncio.open_unix_connection(self.ipcPipe) # type: ignore + self.pipeReader, self.pipeWriter = await asyncio.open_unix_connection(self.ipcPipe) # pyright: ignore[reportGeneralTypeIssues,reportUnknownMemberType] else: self.pipeReader = asyncio.StreamReader() - self.pipeWriter, _ = await self.loop.create_pipe_connection(lambda: asyncio.StreamReaderProtocol(self.pipeReader), self.ipcPipe) # type: ignore + self.pipeWriter = (await self.loop.create_pipe_connection(lambda: asyncio.StreamReaderProtocol(self.pipeReader), self.ipcPipe))[0] # pyright: ignore[reportGeneralTypeIssues,reportUnknownMemberType] self.write(0, { "v": 1, "client_id": discordClientID }) if await self.read(): self.connected = True except: - logger.exception("An unexpected error occured during a RPC handshake operation") + logger.exception("An unexpected error occured during an IPC handshake operation") async def read(self) -> Optional[Any]: try: @@ -48,7 +37,7 @@ class DiscordRpcService: logger.debug("[READ] %s", data) return data except: - logger.exception("An unexpected error occured during a RPC read operation") + logger.exception("An unexpected error occured during an IPC read operation") self.connected = False def write(self, op: int, payload: Any) -> None: @@ -57,26 +46,34 @@ class DiscordRpcService: payload = json.dumps(payload) self.pipeWriter.write(struct.pack(" None: + if self.connected: + logger.debug("Attempt to connect Discord IPC pipe while already connected") + return + logger.info("Connecting Discord IPC pipe") + self.loop = asyncio.new_event_loop() + self.loop.run_until_complete(self.handshake()) + def disconnect(self) -> None: if not self.connected: - logger.debug("Attempt to disconnect Discord IPC Pipe while not connected") + logger.debug("Attempt to disconnect Discord IPC pipe while not connected") return - logger.info("Disconnecting Discord IPC Pipe") + logger.info("Disconnecting Discord IPC pipe") try: self.pipeWriter.close() except: - logger.exception("An unexpected error occured while closing an IPC pipe writer") + logger.exception("An unexpected error occured while closing the IPC pipe writer") try: self.loop.run_until_complete(self.pipeReader.read()) except: - logger.exception("An unexpected error occured while closing an IPC pipe reader") + logger.exception("An unexpected error occured while closing the IPC pipe reader") try: self.loop.close() except: - logger.exception("An unexpected error occured while closing an asyncio event loop") + logger.exception("An unexpected error occured while closing the asyncio event loop") self.connected = False def setActivity(self, activity: models.discord.Activity) -> None: diff --git a/services/imgur.py b/core/imgur.py similarity index 84% rename from services/imgur.py rename to core/imgur.py index 255dbb5..0df80dc 100644 --- a/services/imgur.py +++ b/core/imgur.py @@ -1,10 +1,10 @@ -from services.config import config +from .config import config from typing import Optional from utils.logging import logger import models.imgur import requests -def uploadImage(url: str) -> Optional[str]: +def uploadToImgur(url: str) -> Optional[str]: try: data: models.imgur.UploadResponse = requests.post( "https://api.imgur.com/3/image", @@ -15,4 +15,4 @@ def uploadImage(url: str) -> Optional[str]: raise Exception(data["data"]["error"]) return data["data"]["link"] except: - logger.exception("An unexpected error occured while uploading an image") + logger.exception("An unexpected error occured while uploading an image to Imgur") diff --git a/services/PlexAlertListener.py b/core/plex.py similarity index 91% rename from services/PlexAlertListener.py rename to core/plex.py index 645c3c1..141bffe 100644 --- a/services/PlexAlertListener.py +++ b/core/plex.py @@ -1,17 +1,16 @@ -# pyright: reportTypedDictNotRequiredAccess=none,reportUnknownArgumentType=none,reportUnknownMemberType=none +# pyright: reportUnknownArgumentType=none,reportUnknownMemberType=none,reportUnknownVariableType=none -from .DiscordRpcService import DiscordRpcService -from .cache import getKey, setKey from .config import config -from .imgur import uploadImage +from .discord import DiscordIpcService +from .imgur import uploadToImgur from plexapi.alert import AlertListener from plexapi.base import Playable, PlexPartialObject from plexapi.media import Genre, GuidTag from plexapi.myplex import MyPlexAccount, PlexServer from typing import Optional +from utils.cache import getCacheKey, setCacheKey from utils.logging import LoggerWithPrefix from utils.text import formatSeconds -import hashlib import models.config import models.discord import models.plex @@ -30,8 +29,8 @@ class PlexAlertListener(threading.Thread): self.daemon = True self.token = token self.serverConfig = serverConfig - self.logger = LoggerWithPrefix(f"[{self.serverConfig['name']}/{hashlib.md5(str(id(self)).encode('UTF-8')).hexdigest()[:5].upper()}] ") - self.discordRpcService = DiscordRpcService() + self.logger = LoggerWithPrefix(f"[{self.serverConfig['name']}] ") # pyright: ignore[reportTypedDictNotRequiredAccess] + self.discordIpcService = DiscordIpcService() self.updateTimeoutTimer: Optional[threading.Timer] = None self.connectionTimeoutTimer: Optional[threading.Timer] = None self.account: Optional[MyPlexAccount] = None @@ -48,7 +47,7 @@ class PlexAlertListener(threading.Thread): self.logger.info("Signing into Plex") self.account = MyPlexAccount(token = self.token) self.logger.info("Signed in as Plex user \"%s\"", self.account.username) - self.listenForUser = self.serverConfig.get("listenForUser", self.account.username) + self.listenForUser = self.serverConfig.get("listenForUser", "") or self.account.username self.server = None for resource in self.account.resources(): if resource.product == self.productName and resource.name.lower() == self.serverConfig["name"].lower(): @@ -71,7 +70,7 @@ class PlexAlertListener(threading.Thread): self.logger.error("%s \"%s\" not found", self.productName, self.serverConfig["name"]) break except Exception as e: - self.logger.error("Failed to connect to %s \"%s\": %s", self.productName, self.serverConfig["name"], e) + self.logger.error("Failed to connect to %s \"%s\": %s", self.productName, self.serverConfig["name"], e) # pyright: ignore[reportTypedDictNotRequiredAccess] self.logger.error("Reconnecting in 10 seconds") time.sleep(10) @@ -93,7 +92,7 @@ class PlexAlertListener(threading.Thread): def disconnectRpc(self) -> None: self.lastState, self.lastSessionKey, self.lastRatingKey = "", 0, 0 - self.discordRpcService.disconnect() + self.discordIpcService.disconnect() self.cancelTimers() def cancelTimers(self) -> None: @@ -210,11 +209,11 @@ class PlexAlertListener(threading.Thread): return thumbUrl = "" if thumb and config["display"]["posters"]["enabled"]: - thumbUrl = getKey(thumb) + thumbUrl = getCacheKey(thumb) if not thumbUrl: - self.logger.debug("Uploading image") - thumbUrl = uploadImage(self.server.url(thumb, True)) - setKey(thumb, thumbUrl) + self.logger.debug("Uploading image to Imgur") + thumbUrl = uploadToImgur(self.server.url(thumb, True)) + setCacheKey(thumb, thumbUrl) activity: models.discord.Activity = { "details": title[:128], "state": stateText[:128], @@ -260,9 +259,9 @@ class PlexAlertListener(threading.Thread): activity["timestamps"] = {"end": round(currentTimestamp + ((item.duration - viewOffset) / 1000))} else: activity["timestamps"] = {"start": round(currentTimestamp - (viewOffset / 1000))} - if not self.discordRpcService.connected: - self.discordRpcService.connect() - if self.discordRpcService.connected: - self.discordRpcService.setActivity(activity) + if not self.discordIpcService.connected: + self.discordIpcService.connect() + if self.discordIpcService.connected: + self.discordIpcService.setActivity(activity) except: - self.logger.exception("An unexpected error occured in the alert handler") + self.logger.exception("An unexpected error occured in the Plex alert handler") diff --git a/main.py b/main.py index a79dacb..fc98ac9 100644 --- a/main.py +++ b/main.py @@ -1,31 +1,44 @@ -from store.constants import isUnix +from config.constants import isUnix, containerDemotionUid import os - -if isUnix and os.environ.get("IN_CONTAINER", "") == "true": - uid = 10000 - os.system(f"chown -R {uid}:{uid} /app") - os.setuid(uid) - -from services import PlexAlertListener -from services.cache import loadCache -from services.config import config, loadConfig, saveConfig -from store.constants import dataFolderPath, logFilePath, name, plexClientID, version -from utils.logging import formatter, logger -import logging -import requests +import subprocess import sys + +if isUnix and containerDemotionUid: + uid = int(containerDemotionUid) + os.system(f"chown -R {uid}:{uid} /app") + os.setuid(uid) # pyright: ignore[reportGeneralTypeIssues,reportUnknownMemberType] +else: + def parsePipPackages(packagesStr: str) -> dict[str, str]: + return { packageSplit[0]: packageSplit[1] for packageSplit in [package.split("==") for package in packagesStr.splitlines()] } + pipFreezeResult = subprocess.run([sys.executable, "-m", "pip", "freeze"], capture_output = True, text = True) + installedPackages = parsePipPackages(pipFreezeResult.stdout) + with open("requirements.txt", "r", encoding = "UTF-8") as requirementsFile: + requiredPackages = parsePipPackages(requirementsFile.read()) + for packageName, packageVersion in requiredPackages.items(): + if packageName not in installedPackages: + print(f"Required package '{packageName}' not found, installing...") + subprocess.run([sys.executable, "-m", "pip", "install", f"{packageName}=={packageVersion}"], check = True) + +from config.constants import dataDirectoryPath, logFilePath, name, plexClientID, version, isInteractive +from core.config import config, loadConfig, saveConfig +from core.discord import DiscordIpcService +from core.plex import PlexAlertListener +from typing import Optional +from utils.cache import loadCache +from utils.logging import formatter, logger +from utils.text import formatSeconds +import logging +import models.config +import requests import time import urllib.parse -isInteractive = sys.stdin and sys.stdin.isatty() -plexAlertListeners: list[PlexAlertListener] = [] - -try: - if not os.path.exists(dataFolderPath): - os.mkdir(dataFolderPath) +def main() -> None: + if not os.path.exists(dataDirectoryPath): + os.mkdir(dataDirectoryPath) for oldFilePath in ["config.json", "cache.json", "console.log"]: if os.path.isfile(oldFilePath): - os.rename(oldFilePath, os.path.join(dataFolderPath, oldFilePath)) + os.rename(oldFilePath, os.path.join(dataDirectoryPath, oldFilePath)) loadConfig() if config["logging"]["debug"]: logger.setLevel(logging.DEBUG) @@ -33,46 +46,71 @@ try: fileHandler = logging.FileHandler(logFilePath) fileHandler.setFormatter(formatter) logger.addHandler(fileHandler) - os.system("clear" if isUnix else "cls") logger.info("%s - v%s", name, version) loadCache() - if len(config["users"]) == 0: + if not config["users"]: logger.info("No users found in the config file. Initiating authentication flow.") - response = requests.post("https://plex.tv/api/v2/pins.json?strong=true", headers = { - "X-Plex-Product": name, + user = authUser() + if not user: + exit() + config["users"].append(user) + saveConfig() + plexAlertListeners = [PlexAlertListener(user["token"], server) for user in config["users"] for server in user["servers"]] + try: + if isInteractive: + while True: + userInput = input() + if userInput in ["exit", "quit"]: + raise KeyboardInterrupt + else: + while True: + time.sleep(3600) + except KeyboardInterrupt: + for plexAlertListener in plexAlertListeners: + plexAlertListener.disconnect() + +def authUser() -> Optional[models.config.User]: + response = requests.post("https://plex.tv/api/v2/pins.json?strong=true", headers = { + "X-Plex-Product": name, + "X-Plex-Client-Identifier": plexClientID, + }).json() + logger.info("Open the below URL in your web browser and sign in:") + logger.info("https://app.plex.tv/auth#?clientID=%s&code=%s&context%%5Bdevice%%5D%%5Bproduct%%5D=%s", plexClientID, response["code"], urllib.parse.quote(name)) + time.sleep(5) + for i in range(35): + logger.info(f"Checking whether authentication is successful... ({formatSeconds((i + 1) * 5)})") + authCheckResponse = requests.get(f"https://plex.tv/api/v2/pins/{response['id']}.json?code={response['code']}", headers = { "X-Plex-Client-Identifier": plexClientID, }).json() - logger.info("Open the below URL in your web browser and sign in:") - logger.info("https://app.plex.tv/auth#?clientID=%s&code=%s&context%%5Bdevice%%5D%%5Bproduct%%5D=%s", plexClientID, response["code"], urllib.parse.quote(name)) + if authCheckResponse["authToken"]: + logger.info("Authentication successful") + serverName = os.environ.get("PLEX_SERVER_NAME") + if not serverName: + serverName = input("Enter the name of the Plex Media Server you wish to connect to: ") if isInteractive else "ServerName" + return { "token": authCheckResponse["authToken"], "servers": [{ "name": serverName }] } time.sleep(5) - logger.info("Checking whether authentication is successful...") - for _ in range(120): - authCheckResponse = requests.get(f"https://plex.tv/api/v2/pins/{response['id']}.json?code={response['code']}", headers = { - "X-Plex-Client-Identifier": plexClientID, - }).json() - if authCheckResponse["authToken"]: - logger.info("Authentication successful.") - serverName = os.environ.get("PLEX_SERVER_NAME") - if not serverName: - serverName = input("Enter the name of the Plex Media Server you wish to connect to: ") if isInteractive else "ServerName" - config["users"].append({ "token": authCheckResponse["authToken"], "servers": [{ "name": serverName }] }) - saveConfig() - break - time.sleep(5) - else: - logger.info("Authentication failed.") - exit() - plexAlertListeners = [PlexAlertListener(user["token"], server) for user in config["users"] for server in user["servers"]] - if isInteractive: - while True: - userInput = input() - if userInput in ["exit", "quit"]: - raise KeyboardInterrupt else: - while True: - time.sleep(3600) -except KeyboardInterrupt: - for plexAlertListener in plexAlertListeners: - plexAlertListener.disconnect() -except: - logger.exception("An unexpected error occured") + logger.info(f"Authentication timed out ({formatSeconds(180)})") + +def testIpc() -> None: + logger.info("Testing Discord IPC connection") + discordIpcService = DiscordIpcService() + discordIpcService.connect() + discordIpcService.setActivity({ + "details": "details", + "state": "state", + "assets": { + "large_text": "large_text", + "large_image": "logo", + "small_text": "small_text", + "small_image": "playing", + }, + }) + time.sleep(15) + discordIpcService.disconnect() + +if __name__ == "__main__": + if len(sys.argv) > 1 and sys.argv[1] == "test-ipc": + testIpc() + else: + main() diff --git a/services/__init__.py b/services/__init__.py deleted file mode 100644 index 8f05e7a..0000000 --- a/services/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .DiscordRpcService import DiscordRpcService as DiscordRpcService -from .PlexAlertListener import PlexAlertListener as PlexAlertListener diff --git a/services/cache.py b/services/cache.py deleted file mode 100644 index 2988116..0000000 --- a/services/cache.py +++ /dev/null @@ -1,28 +0,0 @@ -from store.constants import cacheFilePath -from typing import Any -from utils.logging import logger -import json -import os -import time - -cache: dict[str, Any] = {} - -def loadCache() -> None: - if os.path.isfile(cacheFilePath): - try: - with open(cacheFilePath, "r", encoding = "UTF-8") as cacheFile: - cache.update(json.load(cacheFile)) - except: - os.rename(cacheFilePath, cacheFilePath.replace(".json", f"-{time.time():.0f}.json")) - logger.exception("Failed to parse the application's cache file. A new one will be created.") - -def getKey(key: str) -> Any: - return cache.get(key) - -def setKey(key: str, value: Any) -> None: - cache[key] = value - try: - with open(cacheFilePath, "w", encoding = "UTF-8") as cacheFile: - json.dump(cache, cacheFile, separators = (",", ":")) - except: - logger.exception("Failed to write to the application's cache file.") diff --git a/store/constants.py b/store/constants.py deleted file mode 100644 index 1524ecd..0000000 --- a/store/constants.py +++ /dev/null @@ -1,16 +0,0 @@ -import os -import sys - -name = "Discord Rich Presence for Plex" -version = "2.3.5" - -plexClientID = "discord-rich-presence-plex" -discordClientID = "413407336082833418" - -dataFolderPath = "data" -configFilePath = os.path.join(dataFolderPath, "config.json") -cacheFilePath = os.path.join(dataFolderPath, "cache.json") -logFilePath = os.path.join(dataFolderPath, "console.log") - -isUnix = sys.platform in ["linux", "darwin"] -processID = os.getpid() diff --git a/test.py b/test.py deleted file mode 100644 index 2e4f0f4..0000000 --- a/test.py +++ /dev/null @@ -1,16 +0,0 @@ -from services import DiscordRpcService -import time - -discordRpcService = DiscordRpcService() -discordRpcService.connect() -discordRpcService.setActivity({ - "details": "details", - "state": "state", - "assets": { - "large_text": "large_text", - "large_image": "logo", - "small_text": "small_text", - "small_image": "playing", - }, -}) -time.sleep(60) diff --git a/utils/cache.py b/utils/cache.py new file mode 100644 index 0000000..59e799a --- /dev/null +++ b/utils/cache.py @@ -0,0 +1,29 @@ +from .logging import logger +from config.constants import cacheFilePath +from typing import Any +import json +import os +import time + +cache: dict[str, Any] = {} + +def loadCache() -> None: + if not os.path.isfile(cacheFilePath): + return + try: + with open(cacheFilePath, "r", encoding = "UTF-8") as cacheFile: + cache.update(json.load(cacheFile)) + except: + os.rename(cacheFilePath, cacheFilePath.replace(".json", f"-{time.time():.0f}.json")) + logger.exception("Failed to parse the application's cache file. A new one will be created.") + +def getCacheKey(key: str) -> Any: + return cache.get(key) + +def setCacheKey(key: str, value: Any) -> None: + cache[key] = value + try: + with open(cacheFilePath, "w", encoding = "UTF-8") as cacheFile: + json.dump(cache, cacheFile, separators = (",", ":")) + except: + logger.exception("Failed to write to the application's cache file.") diff --git a/utils/dict.py b/utils/dict.py index fd39ee7..81fbf8c 100644 --- a/utils/dict.py +++ b/utils/dict.py @@ -1,8 +1,8 @@ from typing import Any -def merge(source: Any, target: Any) -> None: +def copyDict(source: Any, target: Any) -> None: for key, value in source.items(): if isinstance(value, dict): - merge(value, target.setdefault(key, {})) + copyDict(value, target.setdefault(key, {})) else: - target[key] = source[key] + target[key] = value diff --git a/utils/logging.py b/utils/logging.py index 3bf5701..65895a7 100644 --- a/utils/logging.py +++ b/utils/logging.py @@ -1,7 +1,8 @@ +from config.constants import name from typing import Any, Callable import logging -logger = logging.getLogger("discord-rich-presence-plex") +logger = logging.getLogger(name) logger.setLevel(logging.INFO) formatter = logging.Formatter("[%(asctime)s] [%(levelname)s] %(message)s", datefmt = "%d-%m-%Y %I:%M:%S %p") streamHandler = logging.StreamHandler()