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
* `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
@ -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
```
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

View file

@ -8,7 +8,7 @@ plexClientID = "discord-rich-presence-plex"
discordClientID = "413407336082833418"
dataDirectoryPath = "data"
configFilePathRoot = os.path.join(dataDirectoryPath, "config")
configFilePathBase = os.path.join(dataDirectoryPath, "config")
cacheFilePath = os.path.join(dataDirectoryPath, "cache.json")
logFilePath = os.path.join(dataDirectoryPath, "console.log")
@ -16,4 +16,5 @@ isUnix = sys.platform in ["linux", "darwin"]
processID = os.getpid()
isInteractive = sys.stdin and sys.stdin.isatty()
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.logging import logger
import json
@ -36,12 +36,12 @@ def loadConfig() -> None:
global configFileExtension, configFileType, configFilePath
doesFileExist = False
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
if doesFileExist or isFirstItem:
configFileExtension = fileExtension
configFileType = fileType
configFilePath = f"{configFilePathRoot}.{configFileExtension}"
configFilePath = f"{configFilePathBase}.{configFileExtension}"
if doesFileExist:
break
if doesFileExist:
@ -52,7 +52,7 @@ def loadConfig() -> None:
else:
loadedConfig = json.load(configFile) or {}
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.")
else:
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 utils.logging import logger
import asyncio
@ -10,17 +10,15 @@ import time
class DiscordIpcService:
def __init__(self, ipcPipeNumber: Optional[int]):
ipcPipeNumber = ipcPipeNumber or -1
ipcPipeNumbers = range(10) if ipcPipeNumber == -1 else [ipcPipeNumber]
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.ipcPipes: list[str] = []
for ipcPipeNumber in ipcPipeNumbers:
pipeFilename = f"discord-ipc-{ipcPipeNumber}"
self.ipcPipes.append(os.path.join(ipcPipeBase, pipeFilename))
if ipcPipeBase == os.environ.get("XDG_RUNTIME_DIR"):
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))
def __init__(self, pipeNumber: Optional[int]):
pipeNumber = pipeNumber or -1
pipeNumbers = range(10) if pipeNumber == -1 else [pipeNumber]
self.pipes: list[str] = []
for pipeNumber in pipeNumbers:
pipeFilename = f"discord-ipc-{pipeNumber}"
self.pipes.append(os.path.join(ipcPipeBase, pipeFilename))
self.pipes.append(os.path.join(ipcPipeBase, "app", "com.discordapp.Discord", pipeFilename))
self.pipes.append(os.path.join(ipcPipeBase, ".flatpak", "com.discordapp.Discord", "xdg-run", pipeFilename))
self.loop: Optional[asyncio.AbstractEventLoop] = None
self.pipeReader: Optional[asyncio.StreamReader] = None
self.pipeWriter: Optional[asyncio.StreamWriter] = None
@ -29,24 +27,24 @@ class DiscordIpcService:
async def handshake(self) -> None:
if not self.loop:
return
for ipcPipe in self.ipcPipes:
for pipe in self.pipes:
try:
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:
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 })
if await self.read():
self.connected = True
logger.info(f"Connected to Discord IPC pipe {ipcPipe}")
logger.info(f"Connected to Discord IPC pipe {pipe}")
break
except FileNotFoundError:
pass
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:
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]:
if not self.pipeReader:

23
main.py
View file

@ -1,10 +1,11 @@
from config.constants import isInContainer, runtimeDirectory
from utils.logging import logger
import os
import sys
if isInContainer:
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)
statResult = os.stat(runtimeDirectory)
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():
if packageName not in installedPackages:
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)
except Exception as e:
import traceback
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")
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")
from config.constants import dataDirectoryPath, logFilePath, name, version, isInteractive
from core.config import config, loadConfig, saveConfig
@ -35,15 +34,15 @@ from core.discord import DiscordIpcService
from core.plex import PlexAlertListener, initiateAuth, getAuthToken
from typing import Optional
from utils.cache import loadCache
from utils.logging import logger, formatter
from utils.logging import formatter
from utils.text import formatSeconds
import logging
import models.config
import time
def init() -> None:
if not os.path.exists(dataDirectoryPath):
os.mkdir(dataDirectoryPath)
if not os.path.isdir(dataDirectoryPath):
os.makedirs(dataDirectoryPath)
for oldFilePath in ["config.json", "cache.json", "console.log"]:
if os.path.isfile(oldFilePath):
os.rename(oldFilePath, os.path.join(dataDirectoryPath, oldFilePath))
@ -90,7 +89,7 @@ def authNewUser() -> Optional[models.config.User]:
authToken = getAuthToken(id, code)
if authToken:
logger.info("Authentication successful")
serverName = os.environ.get("PLEX_SERVER_NAME")
serverName = os.environ.get("DRPP_PLEX_SERVER_NAME_INPUT")
if not 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 }] }
@ -98,10 +97,10 @@ def authNewUser() -> Optional[models.config.User]:
else:
logger.info(f"Authentication timed out ({formatSeconds(180)})")
def testIpc(ipcPipeNumber: int) -> None:
def testIpc(pipeNumber: int) -> None:
init()
logger.info("Testing Discord IPC connection")
discordIpcService = DiscordIpcService(ipcPipeNumber)
discordIpcService = DiscordIpcService(pipeNumber)
discordIpcService.connect()
discordIpcService.setActivity({
"details": "details",
@ -124,6 +123,6 @@ if __name__ == "__main__":
elif mode == "test-ipc":
testIpc(int(sys.argv[2]) if len(sys.argv) > 2 else -1)
else:
print(f"Invalid mode: {mode}")
logger.error(f"Invalid mode: {mode}")
except KeyboardInterrupt:
pass