Récupération du travail de @gigi_dsss sur la branche python
Il semblerait que sa branche n'ai pas été créée avec le contenue de la branche master de base, donc lors de la fusion, la branche master a été écrasée et n'a plus contenu que ce qui était dans la branche python... Ceci n'est qu'une supposition... Bref, tout le crédit du travail sur pyhton revient à @gigi_dsss
This commit is contained in:
parent
8dbc5191a5
commit
3116abd4bb
3 changed files with 497 additions and 0 deletions
413
Plex Discord Activity/pda.py
Normal file
413
Plex Discord Activity/pda.py
Normal file
|
@ -0,0 +1,413 @@
|
|||
import asyncio
|
||||
import datetime
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import plexapi.myplex
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import threading
|
||||
import time
|
||||
import webbrowser
|
||||
|
||||
### SEULEMENT CE QUE VOUS DEVEZ EDITER
|
||||
|
||||
MyDomain = "https://your.plex.dns:32400" #Soit via votre nom de domaine, soit via le domaine de Plex, c'est à dire, https://app.plex.tv
|
||||
Serveur = "MyPlex" #Le nom de votre Serveur Plex, visible dans Paramètres > Général > Nom d'usage
|
||||
NomUser = "username" #Le nom d'utilisateur Plex pour récupérer les métadonnées et les informations de lecture en temps réel
|
||||
MotDePasse = "password" #Votre mot de passe de votre compte Plex
|
||||
MonToken = "your_token" #Votre Token, accessible a la fin de l'url lorsque vous consultez le fichier XML de l'un de vos médias
|
||||
BlackList = ["Musique", "BackingTracks"] #séparée par des virgules avec espace, entourées par des "", entrent 2 balises [], ex: ["bib1", "bib2"]
|
||||
UserOnly = "username" #Si vous avez crée des utilisateurs gérés, pour éviter de partager l'état de lecture d'un autre utilisateur que vous-même, précisez le psuedo du bon utilisateur a priori le même que le 3ème paramètre que vous...
|
||||
InfosSupp = "true" #Ceci partage les métadonnées de votre épisode si c'est sur True, sinon mettez False pour n'afficher que l'état de lecture
|
||||
TempsRestant = "true" #True = Afficher le temps restant de lecture / False = Afficher le nombre de minutes que vous avez déjà consulté depuis le démarrage de l'épisode
|
||||
|
||||
### END DE CE QUE VOUS DEVEZ EDITER
|
||||
|
||||
webbrowser.open(MyDomain)
|
||||
|
||||
class plexConfig:
|
||||
|
||||
extraLogging = (InfosSupp)
|
||||
timeRemaining = (TempsRestant)
|
||||
|
||||
def __init__(self, serverName = "", username = "", password = "", token = "", listenForUser = "", blacklistedLibraries = None, whitelistedLibraries = None, clientID = "413407336082833418"):
|
||||
self.serverName = serverName
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.token = token
|
||||
self.listenForUser = (username if listenForUser == "" else listenForUser).lower()
|
||||
self.blacklistedLibraries = blacklistedLibraries
|
||||
self.whitelistedLibraries = whitelistedLibraries
|
||||
self.clientID = clientID
|
||||
|
||||
plexConfigs = [
|
||||
plexConfig(serverName = (Serveur), username = (NomUser), password = (MotDePasse), token = (MonToken), blacklistedLibraries = (BlackList), listenForUser = (UserOnly))
|
||||
]
|
||||
|
||||
class discordRichPresence:
|
||||
|
||||
def __init__(self, clientID, child):
|
||||
self.IPCPipe = ((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 isLinux else "\\\\?\\pipe\\discord-ipc-0"
|
||||
self.clientID = clientID
|
||||
self.pipeReader = None
|
||||
self.pipeWriter = None
|
||||
self.process = None
|
||||
self.running = False
|
||||
self.child = child
|
||||
|
||||
async def read(self):
|
||||
try:
|
||||
data = await self.pipeReader.read(1024)
|
||||
self.child.log("[READ] " + str(json.loads(data[8:].decode("utf-8"))))
|
||||
except Exception as e:
|
||||
self.child.log("[READ] " + str(e))
|
||||
self.stop()
|
||||
|
||||
def write(self, op, payload):
|
||||
payload = json.dumps(payload)
|
||||
self.child.log("[WRITE] " + str(payload))
|
||||
data = self.pipeWriter.write(struct.pack("<ii", op, len(payload)) + payload.encode("utf-8"))
|
||||
|
||||
async def handshake(self):
|
||||
try:
|
||||
if (isLinux):
|
||||
self.pipeReader, self.pipeWriter = await asyncio.open_unix_connection(self.IPCPipe, loop = self.loop)
|
||||
else:
|
||||
self.pipeReader = asyncio.StreamReader(loop = self.loop)
|
||||
self.pipeWriter, _ = await self.loop.create_pipe_connection(lambda: asyncio.StreamReaderProtocol(self.pipeReader, loop = self.loop), self.IPCPipe)
|
||||
self.write(0, {"v": 1, "client_id": self.clientID})
|
||||
await self.read()
|
||||
self.running = True
|
||||
except Exception as e:
|
||||
self.child.log("[HANDSHAKE] " + str(e))
|
||||
|
||||
def start(self):
|
||||
self.child.log("Opening Discord IPC Pipe")
|
||||
emptyProcessFilePath = tempfile.gettempdir() + ("/" if isLinux else "\\") + "discordRichPresencePlex-emptyProcess.py"
|
||||
if (not os.path.exists(emptyProcessFilePath)):
|
||||
with open(emptyProcessFilePath, "w") as emptyProcessFile:
|
||||
emptyProcessFile.write("import time\n\ntry:\n\twhile (True):\n\t\ttime.sleep(3600)\nexcept:\n\tpass")
|
||||
self.process = subprocess.Popen(["python3" if isLinux else "pythonw", emptyProcessFilePath])
|
||||
self.loop = asyncio.new_event_loop() if isLinux else asyncio.ProactorEventLoop()
|
||||
self.loop.run_until_complete(self.handshake())
|
||||
|
||||
def stop(self):
|
||||
self.child.log("Closing Discord IPC Pipe")
|
||||
self.child.lastState, self.child.lastSessionKey, self.child.lastRatingKey = None, None, None
|
||||
self.process.kill()
|
||||
if (self.child.stopTimer):
|
||||
self.child.stopTimer.cancel()
|
||||
self.child.stopTimer = None
|
||||
if (self.child.stopTimer2):
|
||||
self.child.stopTimer2.cancel()
|
||||
self.child.stopTimer2 = None
|
||||
if (self.pipeWriter):
|
||||
try:
|
||||
self.pipeWriter.close()
|
||||
except:
|
||||
pass
|
||||
self.pipeWriter = None
|
||||
if (self.pipeReader):
|
||||
try:
|
||||
self.loop.run_until_complete(self.pipeReader.read(1024))
|
||||
except:
|
||||
pass
|
||||
self.pipeReader = None
|
||||
try:
|
||||
self.loop.close()
|
||||
except:
|
||||
pass
|
||||
self.running = False
|
||||
|
||||
def send(self, activity):
|
||||
payload = {
|
||||
"cmd": "SET_ACTIVITY",
|
||||
"args": {
|
||||
"activity": activity,
|
||||
"pid": self.process.pid
|
||||
},
|
||||
"nonce": "{0:.20f}".format(time.time())
|
||||
}
|
||||
self.write(1, payload)
|
||||
self.loop.run_until_complete(self.read())
|
||||
|
||||
class discordRichPresencePlex(discordRichPresence):
|
||||
|
||||
productName = "Plex Media Server"
|
||||
stopTimerInterval = 5
|
||||
stopTimer2Interval = 35
|
||||
checkConnectionTimerInterval = 60
|
||||
maximumIgnores = 3
|
||||
|
||||
def __init__(self, plexConfig):
|
||||
self.plexConfig = plexConfig
|
||||
self.instanceID = hashlib.md5(str(id(self)).encode("UTF-8")).hexdigest()[:5]
|
||||
super().__init__(plexConfig.clientID, self)
|
||||
self.plexAccount = None
|
||||
self.plexServer = None
|
||||
self.isServerOwner = False
|
||||
self.plexAlertListener = None
|
||||
self.lastState = None
|
||||
self.lastSessionKey = None
|
||||
self.lastRatingKey = None
|
||||
self.stopTimer = None
|
||||
self.stopTimer2 = None
|
||||
self.checkConnectionTimer = None
|
||||
self.ignoreCount = 0
|
||||
|
||||
def run(self):
|
||||
self.reset()
|
||||
connected = False
|
||||
while (not connected):
|
||||
try:
|
||||
if (self.plexConfig.token):
|
||||
self.plexAccount = plexapi.myplex.MyPlexAccount(self.plexConfig.username, token = self.plexConfig.token)
|
||||
else:
|
||||
self.plexAccount = plexapi.myplex.MyPlexAccount(self.plexConfig.username, self.plexConfig.password)
|
||||
self.log("Logged in as Plex User \"" + self.plexAccount.username + "\"")
|
||||
self.plexServer = None
|
||||
for resource in self.plexAccount.resources():
|
||||
if (resource.product == self.productName and resource.name == self.plexConfig.serverName):
|
||||
self.plexServer = resource.connect()
|
||||
try:
|
||||
self.plexServer.account()
|
||||
self.isServerOwner = True
|
||||
except:
|
||||
pass
|
||||
self.log("Connected to " + self.productName + " \"" + self.plexConfig.serverName + "\"")
|
||||
self.plexAlertListener = self.plexServer.startAlertListener(self.onPlexServerAlert)
|
||||
self.log("Listening for PlaySessionStateNotification alerts from user \"" + self.plexConfig.listenForUser + "\"")
|
||||
if (self.checkConnectionTimer):
|
||||
self.checkConnectionTimer.cancel()
|
||||
self.checkConnectionTimer = None
|
||||
self.checkConnectionTimer = threading.Timer(self.checkConnectionTimerInterval, self.checkConnection)
|
||||
self.checkConnectionTimer.start()
|
||||
connected = True
|
||||
break
|
||||
if (not self.plexServer):
|
||||
self.log(self.productName + " \"" + self.plexConfig.serverName + "\" not found")
|
||||
break
|
||||
except Exception as e:
|
||||
self.log("Failed to connect to Plex: " + str(e))
|
||||
self.log("Reconnecting in 10 seconds")
|
||||
time.sleep(10)
|
||||
|
||||
def reset(self):
|
||||
if (self.running):
|
||||
self.stop()
|
||||
self.plexAccount, self.plexServer = None, None
|
||||
if (self.plexAlertListener):
|
||||
try:
|
||||
self.plexAlertListener.stop()
|
||||
except:
|
||||
pass
|
||||
self.plexAlertListener = None
|
||||
if (self.stopTimer):
|
||||
self.stopTimer.cancel()
|
||||
self.stopTimer = None
|
||||
if (self.stopTimer2):
|
||||
self.stopTimer2.cancel()
|
||||
self.stopTimer2 = None
|
||||
if (self.checkConnectionTimer):
|
||||
self.checkConnectionTimer.cancel()
|
||||
self.checkConnectionTimer = None
|
||||
|
||||
def checkConnection(self):
|
||||
try:
|
||||
self.log("Request for clients list to check connection: " + str(self.plexServer.clients()), extra = True)
|
||||
self.checkConnectionTimer = threading.Timer(self.checkConnectionTimerInterval, self.checkConnection)
|
||||
self.checkConnectionTimer.start()
|
||||
except Exception as e:
|
||||
self.log("Connection to Plex lost: " + str(e))
|
||||
self.log("Reconnecting")
|
||||
self.run()
|
||||
|
||||
def log(self, text, colour = "", extra = False):
|
||||
timestamp = datetime.datetime.now().strftime("%I:%M:%S %p")
|
||||
prefix = "[" + timestamp + "] [" + self.plexConfig.serverName + "/" + self.instanceID + "] "
|
||||
lock.acquire()
|
||||
if (extra):
|
||||
if (self.plexConfig.extraLogging):
|
||||
print(prefix + colourText(str(text), colour))
|
||||
else:
|
||||
print(prefix + colourText(str(text), colour))
|
||||
lock.release()
|
||||
|
||||
def onPlexServerAlert(self, data):
|
||||
if (not self.plexServer):
|
||||
return
|
||||
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.log("Received Update: " + colourText(sessionData, "yellow").replace("'", "\""), extra = True)
|
||||
metadata = self.plexServer.fetchItem(ratingKey)
|
||||
libraryName = metadata.section().title
|
||||
if (isinstance(self.plexConfig.blacklistedLibraries, list)):
|
||||
if (libraryName in self.plexConfig.blacklistedLibraries):
|
||||
self.log("Library \"" + libraryName + "\" is blacklisted, ignoring", "yellow", True)
|
||||
return
|
||||
if (isinstance(self.plexConfig.whitelistedLibraries, list)):
|
||||
if (libraryName not in self.plexConfig.whitelistedLibraries):
|
||||
self.log("Library \"" + libraryName + "\" is not whitelisted, ignoring", "yellow", True)
|
||||
return
|
||||
if (self.lastSessionKey == sessionKey and self.lastRatingKey == ratingKey):
|
||||
if (self.stopTimer2):
|
||||
self.stopTimer2.cancel()
|
||||
self.stopTimer2 = None
|
||||
if (self.lastState == state):
|
||||
if (self.ignoreCount == self.maximumIgnores):
|
||||
self.ignoreCount = 0
|
||||
else:
|
||||
self.log("Nothing changed, ignoring", "yellow", True)
|
||||
self.ignoreCount += 1
|
||||
self.stopTimer2 = threading.Timer(self.stopTimer2Interval, self.stopOnNoUpdate)
|
||||
self.stopTimer2.start()
|
||||
return
|
||||
elif (state == "stopped"):
|
||||
self.lastState, self.lastSessionKey, self.lastRatingKey = None, None, None
|
||||
self.stopTimer = threading.Timer(self.stopTimerInterval, self.stop)
|
||||
self.stopTimer.start()
|
||||
self.log("Started stopTimer", "yellow", True)
|
||||
return
|
||||
elif (state == "stopped"):
|
||||
self.log("\"stopped\" state update from unknown session key, ignoring", "yellow", True)
|
||||
return
|
||||
if (self.isServerOwner):
|
||||
self.log("Checking Sessions for Session Key " + colourText(sessionKey, "yellow"), extra = True)
|
||||
plexServerSessions = self.plexServer.sessions()
|
||||
if (len(plexServerSessions) < 1):
|
||||
self.log("Empty session list, ignoring", "red", True)
|
||||
return
|
||||
for session in plexServerSessions:
|
||||
self.log(str(session) + ", Session Key: " + colourText(session.sessionKey, "yellow") + ", Users: " + colourText(session.usernames, "yellow").replace("'", "\""), extra = True)
|
||||
sessionFound = False
|
||||
if (session.sessionKey == sessionKey):
|
||||
sessionFound = True
|
||||
self.log("Session found", "green", True)
|
||||
if (session.usernames[0].lower() == self.plexConfig.listenForUser):
|
||||
self.log("Username \"" + session.usernames[0].lower() + "\" matches \"" + self.plexConfig.listenForUser + "\", continuing", "green", True)
|
||||
break
|
||||
else:
|
||||
self.log("Username \"" + session.usernames[0].lower() + "\" doesn't match \"" + self.plexConfig.listenForUser + "\", ignoring", "red", True)
|
||||
return
|
||||
if (not sessionFound):
|
||||
self.log("No matching session found", "red", True)
|
||||
return
|
||||
if (self.stopTimer):
|
||||
self.stopTimer.cancel()
|
||||
self.stopTimer = None
|
||||
if (self.stopTimer2):
|
||||
self.stopTimer2.cancel()
|
||||
self.stopTimer2 = threading.Timer(self.stopTimer2Interval, self.stopOnNoUpdate)
|
||||
self.stopTimer2.start()
|
||||
self.lastState, self.lastSessionKey, self.lastRatingKey = state, sessionKey, ratingKey
|
||||
mediaType = metadata.type
|
||||
if (state != "playing"):
|
||||
extra = secondsToText(viewOffset / 1000, ":") + "/" + secondsToText(metadata.duration / 1000, ":")
|
||||
else:
|
||||
extra = secondsToText(metadata.duration / 1000)
|
||||
if (mediaType == "movie"):
|
||||
title = metadata.title + " (" + str(metadata.year) + ")"
|
||||
extra = extra + " · " + ", ".join([genre.tag for genre in metadata.genres[:3]])
|
||||
largeText = "Watching a Movie"
|
||||
elif (mediaType == "episode"):
|
||||
title = metadata.grandparentTitle
|
||||
extra = extra + " · S" + str(metadata.parentIndex) + " · E" + str(metadata.index) + " - " + metadata.title
|
||||
largeText = "Watching a TV Show"
|
||||
elif (mediaType == "track"):
|
||||
title = metadata.title
|
||||
artist = metadata.originalTitle
|
||||
if (not artist):
|
||||
artist = metadata.grandparentTitle
|
||||
extra = artist + " · " + metadata.parentTitle
|
||||
largeText = "Listening to Music"
|
||||
else:
|
||||
self.log("Unsupported media type \"" + mediaType + "\", ignoring", "red", True)
|
||||
return
|
||||
activity = {
|
||||
"details": title,
|
||||
"state": extra,
|
||||
"assets": {
|
||||
"large_text": largeText,
|
||||
"large_image": "logo",
|
||||
"small_text": state.capitalize(),
|
||||
"small_image": state
|
||||
},
|
||||
}
|
||||
if (state == "playing"):
|
||||
currentTimestamp = int(time.time())
|
||||
if (self.plexConfig.timeRemaining):
|
||||
activity["timestamps"] = {"end": round(currentTimestamp + ((metadata.duration - viewOffset) / 1000))}
|
||||
else:
|
||||
activity["timestamps"] = {"start": round(currentTimestamp - (viewOffset / 1000))}
|
||||
if (not self.running):
|
||||
self.start()
|
||||
if (self.running):
|
||||
self.send(activity)
|
||||
else:
|
||||
self.stop()
|
||||
except Exception as e:
|
||||
self.log("onPlexServerAlert Error: " + str(e))
|
||||
|
||||
def stopOnNoUpdate(self):
|
||||
self.log("No updates from session key " + str(self.lastSessionKey) + ", stopping", "red", True)
|
||||
self.stop()
|
||||
|
||||
isLinux = sys.platform in ["linux", "darwin"]
|
||||
lock = threading.Semaphore(value = 1)
|
||||
|
||||
os.system("clear" if isLinux else "cls")
|
||||
|
||||
if (len(plexConfigs) == 0):
|
||||
print("Error: plexConfigs list is empty")
|
||||
sys.exit()
|
||||
|
||||
colours = {
|
||||
"red": "91",
|
||||
"green": "92",
|
||||
"yellow": "93",
|
||||
"blue": "94",
|
||||
"magenta": "96",
|
||||
"cyan": "97"
|
||||
}
|
||||
|
||||
def colourText(text, colour = ""):
|
||||
prefix = ""
|
||||
suffix = ""
|
||||
colour = colour.lower()
|
||||
if (colour in colours):
|
||||
prefix = "\033[" + colours[colour] + "m"
|
||||
suffix = "\033[0m"
|
||||
return prefix + str(text) + suffix
|
||||
|
||||
def secondsToText(seconds, joiner = ""):
|
||||
seconds = round(seconds)
|
||||
text = {"h": seconds // 3600, "m": seconds // 60 % 60, "s": seconds % 60}
|
||||
if (joiner == ""):
|
||||
text = [str(v) + k for k, v in text.items() if v > 0]
|
||||
else:
|
||||
if (text["h"] == 0):
|
||||
del text["h"]
|
||||
text = [str(v).rjust(2, "0") for k, v in text.items()]
|
||||
return joiner.join(text)
|
||||
|
||||
discordRichPresencePlexInstances = []
|
||||
for config in plexConfigs:
|
||||
discordRichPresencePlexInstances.append(discordRichPresencePlex(config))
|
||||
try:
|
||||
for discordRichPresencePlexInstance in discordRichPresencePlexInstances:
|
||||
discordRichPresencePlexInstance.run()
|
||||
while (True):
|
||||
time.sleep(3600)
|
||||
except KeyboardInterrupt:
|
||||
for discordRichPresencePlexInstance in discordRichPresencePlexInstances:
|
||||
discordRichPresencePlexInstance.reset()
|
||||
except Exception as e:
|
||||
print("Error: " + str(e))
|
Loading…
Add table
Add a link
Reference in a new issue