-
-
Save blacktwin/e1d199d98b258d6f2658dd9991c88ca0 to your computer and use it in GitHub Desktop.
''' | |
fetch function from https://gist.github.com/Hellowlol/ee47b6534410b1880e19 | |
PlexPy > Settings > Notification Agents > Scripts > Bell icon: | |
[X] Notify on pause | |
PlexPy > Settings > Notification Agents > Scripts > Gear icon: | |
Playback Pause: create_wait_kill_all.py | |
PlexPy > Settings > Notifications > Script > Script Arguments: | |
{session_key} | |
create_wait_kill_all.py creates a new file with the session_id (sub_script) as it's name. | |
PlexPy will timeout create_wait_kill_all.py after 30 seconds (default) but sub_script.py will continue. | |
sub_script will check if the stream's session_id is still pause or if playing as restarted. | |
If playback is restarted then sub_script will stop and delete itself. | |
If stream remains paused then it will be killed and sub_script will stop and delete itself. | |
Set TIMEOUT to max time before killing stream | |
Set INTERVAL to how often you want to check the stream status | |
''' | |
import os | |
import platform | |
import subprocess | |
import sys | |
from uuid import getnode | |
import unicodedata | |
import requests | |
## EDIT THESE SETTINGS ## | |
PLEX_HOST = '' | |
PLEX_PORT = 32400 | |
PLEX_SSL = '' # s or '' | |
PLEX_TOKEN = '' | |
PLEXPY_APIKEY = 'xxxxxxx' # Your PlexPy API key | |
PLEXPY_URL = 'http://localhost:8181/' # Your PlexPy URL | |
TIMEOUT = 120 | |
INTERVAL = 20 | |
REASON = 'Because....' | |
ignore_lst = ('test') | |
class Activity(object): | |
def __init__(self, data=None): | |
d = data or {} | |
self.video_decision = d['video_decision'] | |
self.state = d['state'] | |
self.session_key = d['session_key'] | |
def get_get_activity(): | |
# Get the user IP list from PlexPy | |
payload = {'apikey': PLEXPY_APIKEY, | |
'cmd': 'get_activity'} | |
try: | |
r = requests.get(PLEXPY_URL.rstrip('/') + '/api/v2', params=payload) | |
response = r.json() | |
res_data = response['response']['data']['sessions'] | |
return [Activity(data=d) for d in res_data] | |
except Exception as e: | |
sys.stderr.write("PlexPy API 'get_get_activity' request failed: {0}.".format(e)) | |
def fetch(path, t='GET'): | |
url = 'http%s://%s:%s/' % (PLEX_SSL, PLEX_HOST, PLEX_PORT) | |
headers = {'X-Plex-Token': PLEX_TOKEN, | |
'Accept': 'application/json', | |
'X-Plex-Provides': 'controller', | |
'X-Plex-Platform': platform.uname()[0], | |
'X-Plex-Platform-Version': platform.uname()[2], | |
'X-Plex-Product': 'Plexpy script', | |
'X-Plex-Version': '0.9.5', | |
'X-Plex-Device': platform.platform(), | |
'X-Plex-Client-Identifier': str(hex(getnode())) | |
} | |
try: | |
if t == 'GET': | |
r = requests.get(url + path, headers=headers, verify=False) | |
elif t == 'POST': | |
r = requests.post(url + path, headers=headers, verify=False) | |
elif t == 'DELETE': | |
r = requests.delete(url + path, headers=headers, verify=False) | |
if r and len(r.content): # incase it dont return anything | |
return r.json() | |
else: | |
return r.content | |
except Exception as e: | |
print e | |
def kill_stream(sessionId, message, xtime, ntime, user, title, sessionKey): | |
headers = {'X-Plex-Token': PLEX_TOKEN} | |
params = {'sessionId': sessionId, | |
'reason': message} | |
activity = get_get_activity() | |
for a in activity: | |
if a.session_key == sessionKey: | |
if a.state == 'paused' and xtime == ntime: | |
sys.stdout.write("Killing {user}'s paused stream of {title}".format(user=user, title=title)) | |
requests.get('http{}://{}:{}/status/sessions/terminate'.format(PLEX_SSL, PLEX_HOST, PLEX_PORT), | |
headers=headers, params=params) | |
return ntime | |
elif a.state in ('playing', 'buffering'): | |
sys.stdout.write("{user}'s stream of {title} is now {state}".format(user=user, title=title, | |
state=a.state)) | |
return None | |
else: | |
return xtime | |
def find_sessionID(response): | |
sessions = [] | |
for s in response['MediaContainer']['Video']: | |
if s['sessionKey'] == sys.argv[1]: | |
sess_id = s['Session']['id'] | |
user = s['User']['title'] | |
sess_key = s['sessionKey'] | |
title = (s['grandparentTitle'] + ' - ' if s['type'] == 'episode' else '') + s['title'] | |
title = unicodedata.normalize('NFKD', title).encode('ascii','ignore') | |
sessions.append((sess_id, user, title, sess_key)) | |
else: | |
pass | |
for session in sessions: | |
if session[1] not in ignore_lst: | |
return session | |
else: | |
print("{}'s stream of {} is ignored.".format(session[1], session[2])) | |
return None | |
if __name__ == '__main__': | |
startupinfo = None | |
if os.name == 'nt': | |
startupinfo = subprocess.STARTUPINFO() | |
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW | |
response = fetch('status/sessions') | |
fileDir = fileDir = os.path.dirname(os.path.realpath(__file__)) | |
try: | |
if find_sessionID(response): | |
stream_info = find_sessionID(response) | |
file_name = "{}.py".format(stream_info[0]) | |
full_path = os.path.join(fileDir, file_name) | |
file = "from time import sleep\n" \ | |
"import sys, os\n" \ | |
"from {script} import kill_stream \n" \ | |
"message = '{REASON}'\n" \ | |
"sessionID = os.path.basename(sys.argv[0])[:-3]\n" \ | |
"x = 0\n" \ | |
"n = {ntime}\n" \ | |
"try:\n" \ | |
" while x < n and x is not None:\n" \ | |
" sleep({xtime})\n" \ | |
" x += kill_stream(sessionID, message, {xtime}, n, '{user}', '{title}', '{sess_key}')\n" \ | |
" kill_stream(sessionID, message, {ntime}, n, '{user}', '{title}', '{sess_key}')\n" \ | |
" os.remove(sys.argv[0])\n" \ | |
"except TypeError as e:\n" \ | |
" os.remove(sys.argv[0])".format(script=os.path.basename(__file__)[:-3], | |
ntime=TIMEOUT, xtime=INTERVAL, REASON=REASON, | |
user=stream_info[1], title=stream_info[2], | |
sess_key=stream_info[3]) | |
with open(full_path, "w+") as output: | |
output.write(file) | |
subprocess.Popen([sys.executable, full_path], startupinfo=startupinfo) | |
exit(0) | |
except TypeError as e: | |
print(e) | |
pass |
@christronyxyocum The script timeout in PlexPy is unrelated as you've found out. The whole reason for creating the sub-script is to bypass PlexPy's timeout. Do you have the latest version of this script? I've been updating it for the past couple days. If you want to try and run it manually insert this into line 152
print([x['sessionKey'] for x in response['MediaContainer']['Video'] if x['Player']['state'] == 'paused'])
then just run the script create_wait_kill_all.py
and the sessionkey will show up followed by an error that there was no argument. Then create_wait_kill_all.py {sessionkey}
to try and kill that paused stream. At least thats how I was testing it.
Haven't had a chance to test the line you provided above, but the most recent rev of the script seems to be trying to kill paused streams in just a few minutes. Users asked me about a message the received in regards to killing their stream, but the stream never died. I have the TIMEOUT set to 60 and the interval set to 10. Is this minutes or seconds? I assume minutes, but I could be wrong. Regardless, still doesn't seem to kill the stream, but is sending the message to users.
@christronyxyocum huh? If they are seeing the message then the stream was killed or at least that's how it should work. Interval and Timeout vars are seconds not minutes.
I know it sounds crazy, but it's true. Maybe it's because I had everything set as minutes and not seconds so it was really stepping on itself because it was happening so frequently. I'm going to try it out again tonight. For your test code, do I replace line 152 or push the existing code down on line and then paste in the above code?
Just had a successful test. Paused a stream, script set to check every 5 mins and term after 30. Message was displayed and stream was killed.
Sorry for being such a pain and thanks so much for all of your time and effort with this project and helping me.
No worries. Glad you got it working.
I did, however, just find a bug; if the video name/title string has a single quote in it, it breaks the script and needs to be wrapped in double quotes instead. Here's an example:
PlexPy Notifiers :: Script error: File "s1i2c6d0s6o9xixstiozf8w4.py", line 11 x += kill_stream(sessionID, message, 300, n, 'Ichigokiwi', 'Salem - Wednesday's Child', '6') ^ SyntaxError: invalid syntax
Here, the string is Salem - Wednesday's Child, so when it's wrapped in single quotes, the string only becomes Salem - Wednesday and it breaks because the rest of the title is now perceived as a command, I believe. Have to wrap the {title} variable in double quotes, at least with lines 162 & 163. I did this, tested it, and it works now.
@christronyxyocum thanks. Fixed
import unicodedata
title = unicodedata.normalize('NFKD', title).encode('ascii','ignore')
Awesome, thanks! Do you have any idea if it's possible to make the message window display for a longer time period than the ~2 seconds it does now?
@christronyxyocum i don't think that is possible.
Sorry, but that didn't fix the issue with titles that contain apostrophes. Temp script is still broken with a syntax error since you wind up with 'The Boy's Computer','20' for example.
Maybe a simple string.replace("'", "") would work? Never done anything in Python so don't know if it's possible to replace with "blank"..
@christronyxyocum You're using the most up-to-date version?
Yes, and it still doesn't do anything with the single quotes around the title that includes an apostrophe so it breaks the script. Here's a screenshot with it open in my editor so you can see it with syntax highlighting:
Wrapping the title in double quotes seems to syntacically work, but I cannot figure out how to achieve that.
Edit: Ok, I believe I fixed it. Changed the double quotes to single quotes for lines 164 & 165, and then the single quotes around the three variables within those two lines to double quotes which results in the values of those three variables being wrapped in double quotes within the temp script.
Result:
No syntax error and I can see the temp script is still running in the background so I believe it is now working with titles that include an apostrophe.
Edit #2: It works.
@christronyxyocum thanks! Updated with the correction.
No problem! Glad I finally figured it out. Thanks for all your work on this and for dealing with me for so long, haha.
Hello,
I just manually tested this script on a paused stream after it didn't trigger for my 5 minute timeout. I got the following error:
PlexPy Notifiers :: Script error:
Traceback (most recent call last):
File "/opt/plexpy/scripts/create_wait_kill_all.py", line 25, in
import requests
ImportError: No module named requests
Am I missing a prerequisite of some kind? This is the only plexpy script I've deployed.
@MostDefiantly you need to install requests
I've been getting this error. Any ideas?
PlexPy Notifiers :: Script error:
Traceback (most recent call last):
File "C:\Scripts\create_wait_kill_all.py", line 155, in
if find_sessionID(response):
File "C:\Scripts\create_wait_kill_all.py", line 128, in find_sessionID
if s['sessionKey'] == sys.argv[1]:
IndexError: list index out of range
@Foebik I've updated the script.
I tried it again, the first error is gone, but now Im seeing these.
I think the first one was me manually killing it, but can't be sure.
Aug 21, 2017 22:44:08.912 | ERROR | Session 10423758 terminated
Aug 21, 2017 22:41:28.512 | ERROR | Had trouble breaking 1503369683807
Aug 21, 2017 22:41:28.512 | ERROR | ERROR: Parsing request failed.
Aug 21, 2017 22:41:28.512 | ERROR | Error parsing HTTP request: PT8Iibcd92di0sZIVoygTBAMWnKEBX5OHJlHGZSYUDc6doDegpl8azCm%252bsdPSOiIBzaXt9cdORVRH4sJI6ruJupn%252f6jmw1%252fXZ4fjQqQSm0F8s%252bHylG8mOl%252bC5wjyBDCUKESyXXgGkaTqcUTdJw9cjJExYwdfOb7waLM%252bRud0Nldi7xcCy%252byZmT009jVWmjRGHtfE6u1ga6S6ZXMGsOqbmSYMtE03fV6tXs751hWvLeEj2e2%252fjGwUUkTj2RC%252btT2E6; snatched_view=list; soon_view=thumb; suggest_view=thumb; late_view=list; session_id=a967bb956592885a9067535986fd19926449be3a
Hello,
I'm getting a different error then what's above.
PlexPy Notifiers :: Script error: Traceback (most recent call last): File "/scripts/kill_trans_pause.py", line 135, in if find_sessionID(response): File "/scripts/kill_trans_pause.py", line 104, in find_sessionID if video['sessionKey'] == sys.argv[1] and video['Player']['state'] == 'paused' \ IndexError: list index out of range |
---|
@ajhong2 @Foebik I've updated this script to match the most recent updates found in the JBOPS repo. Please use the repo to report any errors found.
I am getting:
2017-09-07 14:14:38 | INFO | PlexPy Notifiers :: Script notification sent. |
---|---|---|
2017-09-07 14:14:38 | DEBUG | PlexPy Notifiers :: Script returned: string indices must be integers, not str |
2017-09-07 14:14:37 | DEBUG | PlexPy Notifiers :: Executing script in a new thread. |
2017-09-07 14:14:37 | DEBUG | PlexPy Notifiers :: Full script is: ['python', u'/opt/plexpy/plexpy-custom-scripts/create_wait_kill_all.py', '{session_key}'] |
2017-09-07 14:14:37 | DEBUG | PlexPy Notifiers :: Trying to run notify script, action: test, arguments: {session_key} |
My initial testing is working flawlessly on my unRAID server with Plex and Plexpy running in their own dockers.
Slack is currently setup to report concurrent streams from the same user and when users are streaming from a new device.
I was wondering if there was a way to have it send a message to my slack notification client upon killing a stream. This would be awesome.
Thanks for your great project!
@derailius You need to actually pass an argument for the script to work. Using the Test Script button will not work unless you know the session_key and enter it into the argument field in the Test Script section.
@blurb2m Yes you add that functionality. Check out this notify script for using PlexPy's send_notification call. Let me know if you need any help.
All please use the JBOPS repo for issues as gists don't ping me when you create a comment.
I had a file that had a title with an apostrophe in it, so I had to modify the title on the output to file portion:
file = "from time import sleep\n" \
"import sys, os\n" \
"from {script} import kill_stream \n" \
"message = '{REASON}'\n" \
"sessionID = os.path.basename(sys.argv[0])[:-3]\n" \
"x = 0\n" \
"n = {ntime}\n" \
"try:\n" \
" while x < n and x is not None:\n" \
" sleep({xtime})\n" \
" x += kill_stream(sessionID, message, {xtime}, n, '{user}', '''{title}''', '{sess_key}')\n" \
" kill_stream(sessionID, message, {ntime}, n, '{user}', '''{title}''', '{sess_key}')\n" \
" os.remove(sys.argv[0])\n" \
"except TypeError as e:\n" \
" os.remove(sys.argv[0])".format(script=os.path.basename(__file__)[:-3],
ntime=TIMEOUT, xtime=INTERVAL, REASON=REASON,
user=stream_info[1], title=stream_info[2],
sess_key=stream_info[3])
I was stuck trying to get this script to work for awhile but since I'm using Tautulli I needed the newer scripts from https://github.com/blacktwin/JBOPS/tree/master/killstream
@royalchinook
thanks for that. i was just searching around, figuring there had to be a new one.
So since I saw the repeated script timeouts, I doubled the timeout, so 60 seconds, and it's still happening (this was a manual run with the session key):
2017-06-27 18:31:26 WARNING PlexPy Notifiers :: Script exceeded timeout limit of 60 seconds. Script killed.
Despite that, I see the following message:
2017-06-27 18:32:30 DEBUG PlexPy Notifiers :: Script returned:
Killing tronyx's stream of The Pink Panther Strikes Again
I saw the temp Python script get removed from the dir where I'm keeping the scripts, but the stream remains paused and I did not see any message get displayed. Then, upon checking the logs again, I saw this message:
2017-06-27 18:34:21 ERROR PlexPy Notifiers :: Script error:
Traceback (most recent call last):
File "jcmrqzde2439p4zsi90tgzta.py", line 12, in
WindowsError: [Error 2] The system cannot find the file specified: 'jcmrqzde2439p4zsi90tgzta.py'
Seems like it deleted the temp script too soon? That being said, it's the first and only time I'm seeing that in the logs.
Ran the script test again, using the same session key as the stream was still open, I see the temp script get created, I see the script timeout message in the logs again, ~3 minutes later I see it wanting to kill the stream, I see the temp script get removed the dir
Here are the logs for this 2nd test run:
2017-06-27 18:36:19 DEBUG Sending test Scripts notification.
2017-06-27 18:36:19 DEBUG PlexPy Notifiers :: Trying to run notify script, action: test, arguments: 45
2017-06-27 18:36:19 DEBUG PlexPy Notifiers :: Full script is: ['python', 'C:\Program Files\PlexPy\Scripts\create_wait_kill_all.py', '45']
2017-06-27 18:36:19 DEBUG PlexPy Notifiers :: Executing script in a new thread.
2017-06-27 18:37:19 WARNING PlexPy Notifiers :: Script exceeded timeout limit of 60 seconds. Script killed.
2017-06-27 18:40:15 DEBUG PlexPy Notifiers :: Script returned:
Killing tronyx's stream of The Pink Panther Strikes Again
This time I waited another 10 minutes and didn't see any additional log entries, IE: the one regarding the missing temp script, and the stream remained actively paused.