Skip to content

Instantly share code, notes, and snippets.

@blacktwin
Last active June 11, 2018 16:43
Show Gist options
  • Save blacktwin/e1d199d98b258d6f2658dd9991c88ca0 to your computer and use it in GitHub Desktop.
Save blacktwin/e1d199d98b258d6f2658dd9991c88ca0 to your computer and use it in GitHub Desktop.
Receive session_key from PlexPy when paused. Use session_id to create sub-script to wait for X time then check if still paused. If paused kill.
'''
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
@tronyx
Copy link

tronyx commented Jun 28, 2017

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.

@blacktwin
Copy link
Author

@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.

@tronyx
Copy link

tronyx commented Jun 29, 2017

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?

@tronyx
Copy link

tronyx commented Jun 29, 2017

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.

@blacktwin
Copy link
Author

blacktwin commented Jun 30, 2017

No worries. Glad you got it working.

@tronyx
Copy link

tronyx commented Jun 30, 2017

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.

@blacktwin
Copy link
Author

@christronyxyocum thanks. Fixed

import unicodedata
title = unicodedata.normalize('NFKD', title).encode('ascii','ignore')

@tronyx
Copy link

tronyx commented Jun 30, 2017

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?

@blacktwin
Copy link
Author

@christronyxyocum i don't think that is possible.

@tronyx
Copy link

tronyx commented Jul 1, 2017

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.

@csy7550
Copy link

csy7550 commented Jul 2, 2017

Maybe a simple string.replace("'", "") would work? Never done anything in Python so don't know if it's possible to replace with "blank"..

@blacktwin
Copy link
Author

@christronyxyocum You're using the most up-to-date version?

@tronyx
Copy link

tronyx commented Jul 7, 2017

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:

Imgur

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.

Imgur

Result:

Imgur

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.

@blacktwin
Copy link
Author

@christronyxyocum thanks! Updated with the correction.

@tronyx
Copy link

tronyx commented Jul 8, 2017

No problem! Glad I finally figured it out. Thanks for all your work on this and for dealing with me for so long, haha.

@MostDefiantly
Copy link

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.

@blacktwin
Copy link
Author

@Foebik
Copy link

Foebik commented Aug 18, 2017

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

@blacktwin
Copy link
Author

blacktwin commented Aug 21, 2017

@Foebik I've updated the script.

@Foebik
Copy link

Foebik commented Aug 22, 2017

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

@PokeFoundry
Copy link

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

@blacktwin
Copy link
Author

@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.

@derailius
Copy link

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}

@blurb2m
Copy link

blurb2m commented Sep 10, 2017

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!

@blacktwin
Copy link
Author

blacktwin commented Sep 13, 2017

@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.

@cnewcome
Copy link

cnewcome commented Dec 7, 2017

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])

@royalchinook
Copy link

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

@BombshellBill
Copy link

@royalchinook
thanks for that. i was just searching around, figuring there had to be a new one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment