mirror of
https://github.com/phin05/discord-rich-presence-plex
synced 2024-11-22 01:23:02 +00:00
Cleanup and tweaks
This commit is contained in:
parent
05b6a5e672
commit
86d59fa16a
5 changed files with 95 additions and 91 deletions
2
main.py
2
main.py
|
@ -15,7 +15,7 @@ if config["logging"]["debug"]:
|
|||
PlexAlertListener.useRemainingTime = config["display"]["useRemainingTime"]
|
||||
|
||||
if len(config["users"]) == 0:
|
||||
logger.info("No users in config. Initiating authorisation flow. ! TBD !") # TODO
|
||||
logger.info("No users found in the config file. Initiating authorisation flow. ! TBD !") # TODO
|
||||
exit()
|
||||
|
||||
plexAlertListeners: list[PlexAlertListener] = []
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
from datetime import datetime
|
||||
from models.config import Config
|
||||
from utils.logs import logger
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
|
||||
class ConfigService:
|
||||
|
||||
|
@ -15,7 +15,7 @@ class ConfigService:
|
|||
with open(self.configFilePath, "r", encoding = "UTF-8") as configFile:
|
||||
self.config = json.load(configFile)
|
||||
except:
|
||||
os.rename(configFilePath, configFilePath.replace(".json", f"-{datetime.now().timestamp():.0f}.json"))
|
||||
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.")
|
||||
self.resetConfig()
|
||||
else:
|
||||
|
@ -24,12 +24,12 @@ class ConfigService:
|
|||
def resetConfig(self) -> None:
|
||||
self.config = {
|
||||
"logging": {
|
||||
"debug": True
|
||||
"debug": True,
|
||||
},
|
||||
"display": {
|
||||
"useRemainingTime": False
|
||||
"useRemainingTime": False,
|
||||
},
|
||||
"users": []
|
||||
"users": [],
|
||||
}
|
||||
self.saveConfig()
|
||||
|
||||
|
|
|
@ -20,6 +20,9 @@ class DiscordRpcService:
|
|||
self.connected = False
|
||||
|
||||
def connect(self):
|
||||
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() if isUnix else asyncio.ProactorEventLoop()
|
||||
self.loop.run_until_complete(self.handshake())
|
||||
|
@ -45,7 +48,7 @@ class DiscordRpcService:
|
|||
return data
|
||||
except:
|
||||
logger.exception("An unexpected error occured during a RPC read operation")
|
||||
self.disconnect()
|
||||
self.connected = False
|
||||
|
||||
def write(self, op, payload):
|
||||
try:
|
||||
|
@ -54,22 +57,21 @@ class DiscordRpcService:
|
|||
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")
|
||||
self.disconnect()
|
||||
self.connected = False
|
||||
|
||||
def disconnect(self):
|
||||
if not self.connected:
|
||||
logger.debug("Attempt to disconnect Discord IPC Pipe while not connected")
|
||||
return
|
||||
logger.info("Disconnecting Discord IPC Pipe")
|
||||
if (self.pipeWriter):
|
||||
try:
|
||||
self.pipeWriter.close()
|
||||
except:
|
||||
logger.exception("An unexpected error occured while closing an IPC pipe writer")
|
||||
self.pipeWriter = None
|
||||
if (self.pipeReader):
|
||||
try:
|
||||
self.loop.run_until_complete(self.pipeReader.read())
|
||||
except:
|
||||
logger.exception("An unexpected error occured while closing an IPC pipe reader")
|
||||
self.pipeReader = None
|
||||
try:
|
||||
self.pipeWriter.close()
|
||||
except:
|
||||
logger.exception("An unexpected error occured while closing an 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")
|
||||
try:
|
||||
self.loop.close()
|
||||
except:
|
||||
|
@ -84,7 +86,7 @@ class DiscordRpcService:
|
|||
"pid": processID,
|
||||
"activity": activity,
|
||||
},
|
||||
"nonce": "{0:.20f}".format(time.time())
|
||||
"nonce": "{0:.2f}".format(time.time()),
|
||||
}
|
||||
self.write(1, payload)
|
||||
self.loop.run_until_complete(self.read())
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
# type: ignore
|
||||
|
||||
from plexapi.alert import AlertListener
|
||||
from plexapi.myplex import MyPlexAccount
|
||||
from services import DiscordRpcService
|
||||
from utils.logs import LoggerWithPrefix
|
||||
|
@ -11,9 +12,9 @@ import time
|
|||
class PlexAlertListener:
|
||||
|
||||
productName = "Plex Media Server"
|
||||
updateTimeoutTimerInterval = 35
|
||||
updateTimeoutTimerInterval = 30
|
||||
connectionTimeoutTimerInterval = 60
|
||||
maximumIgnores = 3
|
||||
maximumIgnores = 2
|
||||
useRemainingTime = False
|
||||
|
||||
def __init__(self, username, token, serverConfig):
|
||||
|
@ -39,13 +40,13 @@ class PlexAlertListener:
|
|||
|
||||
def connect(self):
|
||||
connected = False
|
||||
while (not connected):
|
||||
while not connected:
|
||||
try:
|
||||
self.plexAccount = MyPlexAccount(self.username, token = self.token)
|
||||
self.logger.info("Logged in as Plex User \"%s\"", self.plexAccount.username)
|
||||
self.plexServer = None
|
||||
for resource in self.plexAccount.resources():
|
||||
if (resource.product == self.productName and resource.name == self.serverConfig["name"]):
|
||||
if resource.product == self.productName and resource.name == self.serverConfig["name"]:
|
||||
self.logger.info("Connecting to %s \"%s\"", self.productName, self.serverConfig["name"])
|
||||
self.plexServer = resource.connect()
|
||||
try:
|
||||
|
@ -54,13 +55,14 @@ class PlexAlertListener:
|
|||
except:
|
||||
pass
|
||||
self.logger.info("Connected to %s \"%s\"", self.productName, self.serverConfig["name"])
|
||||
self.plexAlertListener = self.plexServer.startAlertListener(self.handlePlexAlert)
|
||||
self.plexAlertListener = AlertListener(self.plexServer, self.handlePlexAlert, self.reconnect)
|
||||
self.plexAlertListener.start()
|
||||
self.logger.info("Listening for alerts from user \"%s\"", self.username)
|
||||
self.connectionTimeoutTimer = threading.Timer(self.connectionTimeoutTimerInterval, self.connectionTimeout)
|
||||
self.connectionTimeoutTimer.start()
|
||||
connected = True
|
||||
break
|
||||
if (not self.plexServer):
|
||||
if not self.plexServer:
|
||||
self.logger.error("%s \"%s\" not found", self.productName, self.serverConfig["name"])
|
||||
break
|
||||
except Exception as e:
|
||||
|
@ -69,67 +71,65 @@ class PlexAlertListener:
|
|||
time.sleep(10)
|
||||
|
||||
def disconnect(self):
|
||||
self.disconnectRpc()
|
||||
self.discordRpcService.disconnect()
|
||||
self.cancelTimers()
|
||||
if (self.plexAlertListener):
|
||||
try:
|
||||
self.plexAlertListener.stop()
|
||||
except:
|
||||
pass
|
||||
try:
|
||||
self.plexAlertListener.stop()
|
||||
except:
|
||||
pass
|
||||
self.reset()
|
||||
self.logger.info("Stopped listening for alerts")
|
||||
|
||||
def disconnectRpc(self):
|
||||
if (self.discordRpcService.connected):
|
||||
self.discordRpcService.disconnect()
|
||||
def reconnect(self, exception):
|
||||
self.logger.error("Connection to Plex lost: %s", exception)
|
||||
self.disconnect()
|
||||
self.logger.error("Reconnecting")
|
||||
self.connect()
|
||||
|
||||
def cancelTimers(self):
|
||||
if (self.updateTimeoutTimer):
|
||||
if self.updateTimeoutTimer:
|
||||
self.updateTimeoutTimer.cancel()
|
||||
self.updateTimeoutTimer = None
|
||||
if (self.connectionTimeoutTimer):
|
||||
if self.connectionTimeoutTimer:
|
||||
self.connectionTimeoutTimer.cancel()
|
||||
self.connectionTimeoutTimer = None
|
||||
|
||||
def updateTimeout(self):
|
||||
self.logger.debug("No recent updates from session key %s", self.lastSessionKey)
|
||||
self.disconnectRpc()
|
||||
self.discordRpcService.disconnect()
|
||||
self.cancelTimers()
|
||||
|
||||
def connectionTimeout(self):
|
||||
try:
|
||||
self.logger.debug("Request for list of clients to check connection: %s", self.plexServer.clients())
|
||||
except Exception as e:
|
||||
self.logger.error("Connection to Plex lost: %s", e)
|
||||
self.disconnect()
|
||||
self.logger.error("Reconnecting")
|
||||
self.connect()
|
||||
self.reconnect(e)
|
||||
else:
|
||||
self.connectionTimeoutTimer = threading.Timer(self.connectionTimeoutTimerInterval, self.connectionTimeout)
|
||||
self.connectionTimeoutTimer.start()
|
||||
|
||||
def handlePlexAlert(self, data):
|
||||
try:
|
||||
if (data["type"] == "playing" and "PlaySessionStateNotification" in data):
|
||||
sessionData = data["PlaySessionStateNotification"][0]
|
||||
state = sessionData["state"]
|
||||
sessionKey = int(sessionData["sessionKey"])
|
||||
ratingKey = int(sessionData["ratingKey"])
|
||||
viewOffset = int(sessionData["viewOffset"])
|
||||
self.logger.debug("Received alert: %s", sessionData)
|
||||
metadata = self.plexServer.fetchItem(ratingKey)
|
||||
libraryName = metadata.section().title
|
||||
if ("blacklistedLibraries" in self.serverConfig and libraryName in self.serverConfig["blacklistedLibraries"]):
|
||||
if data["type"] == "playing" and "PlaySessionStateNotification" in data:
|
||||
alert = data["PlaySessionStateNotification"][0]
|
||||
state = alert["state"]
|
||||
sessionKey = int(alert["sessionKey"])
|
||||
ratingKey = int(alert["ratingKey"])
|
||||
viewOffset = int(alert["viewOffset"])
|
||||
self.logger.debug("Received alert: %s", alert)
|
||||
item = self.plexServer.fetchItem(ratingKey)
|
||||
libraryName = item.section().title
|
||||
if "blacklistedLibraries" in self.serverConfig and libraryName in self.serverConfig["blacklistedLibraries"]:
|
||||
self.logger.debug("Library \"%s\" is blacklisted, ignoring", libraryName)
|
||||
return
|
||||
if ("whitelistedLibraries" in self.serverConfig and libraryName not in self.serverConfig["whitelistedLibraries"]):
|
||||
if "whitelistedLibraries" in self.serverConfig and libraryName not in self.serverConfig["whitelistedLibraries"]:
|
||||
self.logger.debug("Library \"%s\" is not whitelisted, ignoring", libraryName)
|
||||
return
|
||||
if (self.lastSessionKey == sessionKey and self.lastRatingKey == ratingKey):
|
||||
if (self.updateTimeoutTimer):
|
||||
if self.lastSessionKey == sessionKey and self.lastRatingKey == ratingKey:
|
||||
if self.updateTimeoutTimer:
|
||||
self.updateTimeoutTimer.cancel()
|
||||
self.updateTimeoutTimer = None
|
||||
if (self.lastState == state and self.ignoreCount < self.maximumIgnores):
|
||||
if self.lastState == state and self.ignoreCount < self.maximumIgnores:
|
||||
self.logger.debug("Nothing changed, ignoring")
|
||||
self.ignoreCount += 1
|
||||
self.updateTimeoutTimer = threading.Timer(self.updateTimeoutTimerInterval, self.updateTimeout)
|
||||
|
@ -137,26 +137,26 @@ class PlexAlertListener:
|
|||
return
|
||||
else:
|
||||
self.ignoreCount = 0
|
||||
if (state == "stopped"):
|
||||
if state == "stopped":
|
||||
self.lastState, self.lastSessionKey, self.lastRatingKey = None, None, None
|
||||
self.disconnectRpc()
|
||||
self.discordRpcService.disconnect()
|
||||
self.cancelTimers()
|
||||
return
|
||||
elif (state == "stopped"):
|
||||
elif state == "stopped":
|
||||
self.logger.debug("Received \"stopped\" state alert from unknown session key, ignoring")
|
||||
return
|
||||
if (self.isServerOwner):
|
||||
if self.isServerOwner:
|
||||
self.logger.debug("Searching sessions for session key %s", sessionKey)
|
||||
plexServerSessions = self.plexServer.sessions()
|
||||
if (len(plexServerSessions) < 1):
|
||||
if len(plexServerSessions) < 1:
|
||||
self.logger.debug("Empty session list, ignoring")
|
||||
return
|
||||
for session in plexServerSessions:
|
||||
self.logger.debug("%s, Session Key: %s, Usernames: %s", session, session.sessionKey, session.usernames)
|
||||
if (session.sessionKey == sessionKey):
|
||||
if session.sessionKey == sessionKey:
|
||||
self.logger.debug("Session found")
|
||||
sessionUsername = session.usernames[0].lower()
|
||||
if (sessionUsername == self.username):
|
||||
if sessionUsername == self.username:
|
||||
self.logger.debug("Username \"%s\" matches \"%s\", continuing", sessionUsername, self.username)
|
||||
break
|
||||
else:
|
||||
|
@ -165,33 +165,35 @@ class PlexAlertListener:
|
|||
else:
|
||||
self.logger.debug("No matching session found, ignoring")
|
||||
return
|
||||
if (self.updateTimeoutTimer):
|
||||
if self.updateTimeoutTimer:
|
||||
self.updateTimeoutTimer.cancel()
|
||||
self.updateTimeoutTimer = threading.Timer(self.updateTimeoutTimerInterval, self.updateTimeout)
|
||||
self.updateTimeoutTimer.start()
|
||||
self.lastState, self.lastSessionKey, self.lastRatingKey = state, sessionKey, ratingKey
|
||||
mediaType = metadata.type
|
||||
if (state != "playing"):
|
||||
stateText = f"{formatSeconds(viewOffset / 1000, ':')}/{formatSeconds(metadata.duration / 1000, ':')}"
|
||||
if state != "playing":
|
||||
stateText = f"{formatSeconds(viewOffset / 1000, ':')}/{formatSeconds(item.duration / 1000, ':')}"
|
||||
else:
|
||||
stateText = formatSeconds(metadata.duration / 1000)
|
||||
if (mediaType == "movie"):
|
||||
title = f"{metadata.title} ({metadata.year})"
|
||||
if len(metadata.genres) > 0:
|
||||
stateText += f" · {', '.join(genre.tag for genre in metadata.genres[:3])}"
|
||||
stateText = formatSeconds(item.duration / 1000)
|
||||
mediaType = item.type
|
||||
if mediaType == "movie":
|
||||
title = f"{item.title} ({item.year})"
|
||||
if len(item.genres) > 0:
|
||||
stateText += f" · {', '.join(genre.tag for genre in item.genres[:3])}"
|
||||
largeText = "Watching a movie"
|
||||
elif (mediaType == "episode"):
|
||||
title = metadata.grandparentTitle
|
||||
stateText += f" · S{metadata.parentIndex:02}E{metadata.index:02} - {metadata.title}"
|
||||
# self.logger.debug("Poster: %s", item.thumbUrl)
|
||||
elif mediaType == "episode":
|
||||
title = item.grandparentTitle
|
||||
stateText += f" · S{item.parentIndex:02}E{item.index:02} - {item.title}"
|
||||
largeText = "Watching a TV show"
|
||||
elif (mediaType == "track"):
|
||||
print(metadata)
|
||||
title = metadata.title
|
||||
artist = metadata.originalTitle
|
||||
if (not artist):
|
||||
artist = metadata.grandparentTitle
|
||||
stateText = f"{artist} - {metadata.parentTitle}"
|
||||
# self.logger.debug("Poster: %s", self.plexServer.url(item.grandparentThumb, True))
|
||||
elif mediaType == "track":
|
||||
title = item.title
|
||||
artist = item.originalTitle
|
||||
if not artist:
|
||||
artist = item.grandparentTitle
|
||||
stateText = f"{artist} - {item.parentTitle}"
|
||||
largeText = "Listening to music"
|
||||
# self.logger.debug("Album Art: %s", item.thumbUrl)
|
||||
else:
|
||||
self.logger.debug("Unsupported media type \"%s\", ignoring", mediaType)
|
||||
return
|
||||
|
@ -202,18 +204,18 @@ class PlexAlertListener:
|
|||
"large_text": largeText,
|
||||
"large_image": "logo",
|
||||
"small_text": state.capitalize(),
|
||||
"small_image": state
|
||||
"small_image": state,
|
||||
},
|
||||
}
|
||||
if (state == "playing"):
|
||||
if state == "playing":
|
||||
currentTimestamp = int(time.time())
|
||||
if (self.useRemainingTime):
|
||||
activity["timestamps"] = {"end": round(currentTimestamp + ((metadata.duration - viewOffset) / 1000))}
|
||||
if self.useRemainingTime:
|
||||
activity["timestamps"] = {"end": round(currentTimestamp + ((item.duration - viewOffset) / 1000))}
|
||||
else:
|
||||
activity["timestamps"] = {"start": round(currentTimestamp - (viewOffset / 1000))}
|
||||
if (not self.discordRpcService.connected):
|
||||
if not self.discordRpcService.connected:
|
||||
self.discordRpcService.connect()
|
||||
if (self.discordRpcService.connected):
|
||||
if self.discordRpcService.connected:
|
||||
self.discordRpcService.sendActivity(activity)
|
||||
except:
|
||||
self.logger.exception("An unexpected error occured in the alert handler")
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
def formatSeconds(seconds: int, joiner: str = "") -> str:
|
||||
seconds = round(seconds)
|
||||
timeValues = {"h": seconds // 3600, "m": seconds // 60 % 60, "s": seconds % 60}
|
||||
if (not joiner):
|
||||
if not joiner:
|
||||
return "".join(str(v) + k for k, v in timeValues.items() if v > 0)
|
||||
if (timeValues["h"] == 0):
|
||||
if timeValues["h"] == 0:
|
||||
del timeValues["h"]
|
||||
return joiner.join(str(v).rjust(2, "0") for v in timeValues.values())
|
||||
|
|
Loading…
Reference in a new issue