Minor refactoring and tweaks

This commit is contained in:
Phin 2024-02-07 01:40:41 +05:30
parent 1c72a08041
commit cf19147b6c
5 changed files with 36 additions and 38 deletions

View file

@ -165,7 +165,7 @@ The "Display current activity as a status message" setting must be enabled in Di
## Configuration - Environment Variables ## Configuration - Environment Variables
* `PLEX_SERVER_NAME` - Name of the Plex Media Server you wish to connect to. Used only during the initial setup (when there are no users in the config) for adding a server to the config after authentication. If this isn't set, in interactive environments, the user is prompted for an input, and in non-interactive environments, "ServerName" is used as a placeholder, which can later be changed by editing the config file and restarting the script. * `DRPP_PLEX_SERVER_NAME_INPUT` - This is used only during the initial setup (when there are no users in the config) as the name of the Plex server to be added to the config file after user authentication. If this isn't set, in interactive environments, the user is prompted for an input, and in non-interactive environments, "ServerName" is used as a placeholder, which can later be changed by editing the config file and restarting the script.
## Run with Docker ## Run with Docker
@ -201,7 +201,7 @@ For example, if the environment variable `XDG_RUNTIME_DIR` is set to `/run/user/
docker run -v ./drpp:/app/data -v /run/user/1000:/run/app:ro -d --restart unless-stopped --name drpp ghcr.io/phin05/discord-rich-presence-plex:latest docker run -v ./drpp:/app/data -v /run/user/1000:/run/app:ro -d --restart unless-stopped --name drpp ghcr.io/phin05/discord-rich-presence-plex:latest
``` ```
If you're running the container for the first time (when there are no users in the config), make sure that the `PLEX_SERVER_NAME` environment variable is set (see the [environment variables](#configuration---environment-variables) section above), and check the container logs for the authentication link. If you're running the container for the first time (when there are no users in the config), make sure that the `DRPP_PLEX_SERVER_NAME_INPUT` environment variable is set (see the [environment variables](#configuration---environment-variables) section above), and check the container logs for the authentication link.
### Containerised Discord ### Containerised Discord

View file

@ -8,7 +8,7 @@ plexClientID = "discord-rich-presence-plex"
discordClientID = "413407336082833418" discordClientID = "413407336082833418"
dataDirectoryPath = "data" dataDirectoryPath = "data"
configFilePathRoot = os.path.join(dataDirectoryPath, "config") configFilePathBase = os.path.join(dataDirectoryPath, "config")
cacheFilePath = os.path.join(dataDirectoryPath, "cache.json") cacheFilePath = os.path.join(dataDirectoryPath, "cache.json")
logFilePath = os.path.join(dataDirectoryPath, "console.log") logFilePath = os.path.join(dataDirectoryPath, "console.log")
@ -16,4 +16,5 @@ isUnix = sys.platform in ["linux", "darwin"]
processID = os.getpid() processID = os.getpid()
isInteractive = sys.stdin and sys.stdin.isatty() isInteractive = sys.stdin and sys.stdin.isatty()
isInContainer = os.environ.get("DRPP_IS_IN_CONTAINER", "") == "true" isInContainer = os.environ.get("DRPP_IS_IN_CONTAINER", "") == "true"
runtimeDirectory = "/run/app" runtimeDirectory = "/run/app" if isInContainer else os.environ.get("XDG_RUNTIME_DIR", os.environ.get("TMPDIR", os.environ.get("TMP", os.environ.get("TEMP", "/tmp"))))
ipcPipeBase = runtimeDirectory if isUnix else r"\\?\pipe"

View file

@ -1,4 +1,4 @@
from config.constants import configFilePathRoot from config.constants import configFilePathBase
from utils.dict import copyDict from utils.dict import copyDict
from utils.logging import logger from utils.logging import logger
import json import json
@ -36,12 +36,12 @@ def loadConfig() -> None:
global configFileExtension, configFileType, configFilePath global configFileExtension, configFileType, configFilePath
doesFileExist = False doesFileExist = False
for i, (fileExtension, fileType) in enumerate(supportedConfigFileExtensions.items()): for i, (fileExtension, fileType) in enumerate(supportedConfigFileExtensions.items()):
doesFileExist = os.path.isfile(f"{configFilePathRoot}.{fileExtension}") doesFileExist = os.path.isfile(f"{configFilePathBase}.{fileExtension}")
isFirstItem = i == 0 isFirstItem = i == 0
if doesFileExist or isFirstItem: if doesFileExist or isFirstItem:
configFileExtension = fileExtension configFileExtension = fileExtension
configFileType = fileType configFileType = fileType
configFilePath = f"{configFilePathRoot}.{configFileExtension}" configFilePath = f"{configFilePathBase}.{configFileExtension}"
if doesFileExist: if doesFileExist:
break break
if doesFileExist: if doesFileExist:
@ -52,7 +52,7 @@ def loadConfig() -> None:
else: else:
loadedConfig = json.load(configFile) or {} loadedConfig = json.load(configFile) or {}
except: except:
os.rename(configFilePath, f"{configFilePathRoot}-{time.time():.0f}.{configFileExtension}") os.rename(configFilePath, f"{configFilePathBase}-{time.time():.0f}.{configFileExtension}")
logger.exception("Failed to parse the config file. A new one will be created.") logger.exception("Failed to parse the config file. A new one will be created.")
else: else:
copyDict(loadedConfig, config) copyDict(loadedConfig, config)

View file

@ -1,4 +1,4 @@
from config.constants import discordClientID, isUnix, processID, runtimeDirectory from config.constants import discordClientID, isUnix, processID, ipcPipeBase
from typing import Any, Optional from typing import Any, Optional
from utils.logging import logger from utils.logging import logger
import asyncio import asyncio
@ -10,17 +10,15 @@ import time
class DiscordIpcService: class DiscordIpcService:
def __init__(self, ipcPipeNumber: Optional[int]): def __init__(self, pipeNumber: Optional[int]):
ipcPipeNumber = ipcPipeNumber or -1 pipeNumber = pipeNumber or -1
ipcPipeNumbers = range(10) if ipcPipeNumber == -1 else [ipcPipeNumber] pipeNumbers = range(10) if pipeNumber == -1 else [pipeNumber]
ipcPipeBase = (runtimeDirectory if os.path.isdir(runtimeDirectory) 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" self.pipes: list[str] = []
self.ipcPipes: list[str] = [] for pipeNumber in pipeNumbers:
for ipcPipeNumber in ipcPipeNumbers: pipeFilename = f"discord-ipc-{pipeNumber}"
pipeFilename = f"discord-ipc-{ipcPipeNumber}" self.pipes.append(os.path.join(ipcPipeBase, pipeFilename))
self.ipcPipes.append(os.path.join(ipcPipeBase, pipeFilename)) self.pipes.append(os.path.join(ipcPipeBase, "app", "com.discordapp.Discord", pipeFilename))
if ipcPipeBase == os.environ.get("XDG_RUNTIME_DIR"): self.pipes.append(os.path.join(ipcPipeBase, ".flatpak", "com.discordapp.Discord", "xdg-run", pipeFilename))
self.ipcPipes.append(os.path.join(ipcPipeBase, "app", "com.discordapp.Discord", pipeFilename))
self.ipcPipes.append(os.path.join(ipcPipeBase, ".flatpak", "com.discordapp.Discord", "xdg-run", pipeFilename))
self.loop: Optional[asyncio.AbstractEventLoop] = None self.loop: Optional[asyncio.AbstractEventLoop] = None
self.pipeReader: Optional[asyncio.StreamReader] = None self.pipeReader: Optional[asyncio.StreamReader] = None
self.pipeWriter: Optional[asyncio.StreamWriter] = None self.pipeWriter: Optional[asyncio.StreamWriter] = None
@ -29,24 +27,24 @@ class DiscordIpcService:
async def handshake(self) -> None: async def handshake(self) -> None:
if not self.loop: if not self.loop:
return return
for ipcPipe in self.ipcPipes: for pipe in self.pipes:
try: try:
if isUnix: if isUnix:
self.pipeReader, self.pipeWriter = await asyncio.open_unix_connection(ipcPipe) # pyright: ignore[reportGeneralTypeIssues,reportUnknownMemberType] self.pipeReader, self.pipeWriter = await asyncio.open_unix_connection(pipe) # pyright: ignore[reportGeneralTypeIssues,reportUnknownMemberType]
else: else:
self.pipeReader = asyncio.StreamReader() self.pipeReader = asyncio.StreamReader()
self.pipeWriter = (await self.loop.create_pipe_connection(lambda: asyncio.StreamReaderProtocol(self.pipeReader), ipcPipe))[0] # pyright: ignore[reportGeneralTypeIssues,reportUnknownMemberType] self.pipeWriter = (await self.loop.create_pipe_connection(lambda: asyncio.StreamReaderProtocol(self.pipeReader), pipe))[0] # pyright: ignore[reportGeneralTypeIssues,reportUnknownMemberType]
self.write(0, { "v": 1, "client_id": discordClientID }) self.write(0, { "v": 1, "client_id": discordClientID })
if await self.read(): if await self.read():
self.connected = True self.connected = True
logger.info(f"Connected to Discord IPC pipe {ipcPipe}") logger.info(f"Connected to Discord IPC pipe {pipe}")
break break
except FileNotFoundError: except FileNotFoundError:
pass pass
except: except:
logger.exception(f"An unexpected error occured while connecting to Discord IPC pipe {ipcPipe}") logger.exception(f"An unexpected error occured while connecting to Discord IPC pipe {pipe}")
if not self.connected: if not self.connected:
logger.error(f"Discord IPC pipe not found (attempted pipes: {', '.join(self.ipcPipes)})") logger.error(f"Discord IPC pipe not found (attempted pipes: {', '.join(self.pipes)})")
async def read(self) -> Optional[Any]: async def read(self) -> Optional[Any]:
if not self.pipeReader: if not self.pipeReader:

23
main.py
View file

@ -1,10 +1,11 @@
from config.constants import isInContainer, runtimeDirectory from config.constants import isInContainer, runtimeDirectory
from utils.logging import logger
import os import os
import sys import sys
if isInContainer: if isInContainer:
if not os.path.isdir(runtimeDirectory): if not os.path.isdir(runtimeDirectory):
print(f"Runtime directory does not exist. Make sure that it is mounted into the container at {runtimeDirectory}") logger.error(f"Runtime directory does not exist. Ensure that it is mounted into the container at {runtimeDirectory}")
exit(1) exit(1)
statResult = os.stat(runtimeDirectory) statResult = os.stat(runtimeDirectory)
os.system(f"chown -R {statResult.st_uid}:{statResult.st_gid} {os.path.dirname(os.path.realpath(__file__))}") os.system(f"chown -R {statResult.st_uid}:{statResult.st_gid} {os.path.dirname(os.path.realpath(__file__))}")
@ -22,12 +23,10 @@ else:
for packageName, packageVersion in requiredPackages.items(): for packageName, packageVersion in requiredPackages.items():
if packageName not in installedPackages: if packageName not in installedPackages:
package = f"{packageName}{f'=={packageVersion}' if packageVersion else ''}" package = f"{packageName}{f'=={packageVersion}' if packageVersion else ''}"
print(f"Installing missing dependency: {package}") logger.info(f"Installing missing dependency: {package}")
subprocess.run([sys.executable, "-m", "pip", "install", "-U", package], check = True) subprocess.run([sys.executable, "-m", "pip", "install", "-U", package], check = True)
except Exception as e: except Exception as e:
import traceback logger.exception("An unexpected error occured during automatic installation of dependencies. Install them manually by running the following command: python -m pip install -U -r requirements.txt")
traceback.print_exception(e)
print("An unexpected error occured during automatic installation of dependencies. Install them manually by running the following command: python -m pip install -U -r requirements.txt")
from config.constants import dataDirectoryPath, logFilePath, name, version, isInteractive from config.constants import dataDirectoryPath, logFilePath, name, version, isInteractive
from core.config import config, loadConfig, saveConfig from core.config import config, loadConfig, saveConfig
@ -35,15 +34,15 @@ from core.discord import DiscordIpcService
from core.plex import PlexAlertListener, initiateAuth, getAuthToken from core.plex import PlexAlertListener, initiateAuth, getAuthToken
from typing import Optional from typing import Optional
from utils.cache import loadCache from utils.cache import loadCache
from utils.logging import logger, formatter from utils.logging import formatter
from utils.text import formatSeconds from utils.text import formatSeconds
import logging import logging
import models.config import models.config
import time import time
def init() -> None: def init() -> None:
if not os.path.exists(dataDirectoryPath): if not os.path.isdir(dataDirectoryPath):
os.mkdir(dataDirectoryPath) os.makedirs(dataDirectoryPath)
for oldFilePath in ["config.json", "cache.json", "console.log"]: for oldFilePath in ["config.json", "cache.json", "console.log"]:
if os.path.isfile(oldFilePath): if os.path.isfile(oldFilePath):
os.rename(oldFilePath, os.path.join(dataDirectoryPath, oldFilePath)) os.rename(oldFilePath, os.path.join(dataDirectoryPath, oldFilePath))
@ -90,7 +89,7 @@ def authNewUser() -> Optional[models.config.User]:
authToken = getAuthToken(id, code) authToken = getAuthToken(id, code)
if authToken: if authToken:
logger.info("Authentication successful") logger.info("Authentication successful")
serverName = os.environ.get("PLEX_SERVER_NAME") serverName = os.environ.get("DRPP_PLEX_SERVER_NAME_INPUT")
if not serverName: if not serverName:
serverName = input("Enter the name of the Plex Media Server you wish to connect to: ") if isInteractive else "ServerName" serverName = input("Enter the name of the Plex Media Server you wish to connect to: ") if isInteractive else "ServerName"
return { "token": authToken, "servers": [{ "name": serverName }] } return { "token": authToken, "servers": [{ "name": serverName }] }
@ -98,10 +97,10 @@ def authNewUser() -> Optional[models.config.User]:
else: else:
logger.info(f"Authentication timed out ({formatSeconds(180)})") logger.info(f"Authentication timed out ({formatSeconds(180)})")
def testIpc(ipcPipeNumber: int) -> None: def testIpc(pipeNumber: int) -> None:
init() init()
logger.info("Testing Discord IPC connection") logger.info("Testing Discord IPC connection")
discordIpcService = DiscordIpcService(ipcPipeNumber) discordIpcService = DiscordIpcService(pipeNumber)
discordIpcService.connect() discordIpcService.connect()
discordIpcService.setActivity({ discordIpcService.setActivity({
"details": "details", "details": "details",
@ -124,6 +123,6 @@ if __name__ == "__main__":
elif mode == "test-ipc": elif mode == "test-ipc":
testIpc(int(sys.argv[2]) if len(sys.argv) > 2 else -1) testIpc(int(sys.argv[2]) if len(sys.argv) > 2 else -1)
else: else:
print(f"Invalid mode: {mode}") logger.error(f"Invalid mode: {mode}")
except KeyboardInterrupt: except KeyboardInterrupt:
pass pass