mirror of
https://github.com/phin05/discord-rich-presence-plex
synced 2024-11-21 17:13:04 +00:00
Minor refactor and automatic installation of required packages
This commit is contained in:
parent
d77558eaed
commit
a6fdb596db
18 changed files with 200 additions and 178 deletions
|
@ -3,5 +3,7 @@
|
|||
.github
|
||||
.gitignore
|
||||
CONTRIBUTING.md
|
||||
data
|
||||
Dockerfile
|
||||
pyrightconfig.json
|
||||
Showcase.psd
|
||||
|
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
|
@ -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
|
||||
|
|
|
@ -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"]
|
||||
|
|
18
config/constants.py
Normal file
18
config/constants.py
Normal file
|
@ -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", "")
|
0
core/__init__.py
Normal file
0
core/__init__.py
Normal file
|
@ -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:
|
|
@ -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("<ii", op, len(payload)) + payload.encode("utf-8"))
|
||||
except:
|
||||
logger.exception("An unexpected error occured during a RPC write operation")
|
||||
logger.exception("An unexpected error occured during an IPC write operation")
|
||||
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())
|
||||
|
||||
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:
|
|
@ -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")
|
|
@ -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")
|
152
main.py
152
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()
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
from .DiscordRpcService import DiscordRpcService as DiscordRpcService
|
||||
from .PlexAlertListener import PlexAlertListener as PlexAlertListener
|
|
@ -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.")
|
|
@ -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()
|
16
test.py
16
test.py
|
@ -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)
|
29
utils/cache.py
Normal file
29
utils/cache.py
Normal file
|
@ -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.")
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in a new issue