diff --git a/stream_limiter_ban_email.py b/stream_limiter_ban_email.py new file mode 100644 index 0000000..eb997a5 --- /dev/null +++ b/stream_limiter_ban_email.py @@ -0,0 +1,316 @@ +""" +Share functions from https://gist.github.com/JonnyWong16/f8139216e2748cb367558070c1448636 + +Once user stream count hits LIMIT they are unshared from libraries expect for banned library. +Once user stream count is below LIMIT and banned library video is watched their shares are restored. +Once violation count have been reached ban permanently. + +Caveats: + Unsharing doesn't pause the stream. + After unsharing user can pause but not skip ahead or skip back. + Going from unshared to shared while playing still kills the connection to the server. + Effected user will need to refresh browser/app or restart app to reconnect. + User watch record stop when unshare is executed. + If user finishes a movie/show while unshared they will not have that record. + PlexPy will not have that record. + +Adding to PlexPy + +PlexPy > Settings > Notification Agents > Scripts > Bell icon: + [X] Notify on playback start + [X] Notify on watched + +PlexPy > Settings > Notification Agents > Scripts > Gear icon: + Playback Start: stream_limiter_ban_email.py + Playback Watched: stream_limiter_ban_email.py + +If used in PlexPy: +PlexPy will continue displaying that user is watching after unshare is executed in ACTIVITY. +PlexPy will update after ~5 minutes and no longer display user's stream in ACTIVITY. +PlexPy will think that user has stopped. + + +Create new library with one video. +Name library and video whatever you want. + +My suggestion: + Video: https://www.youtube.com/watch?v=dQw4w9WgXcQ + Video Summary: "You have been banned for one reason or another. Please watch this video to become un-banned. + The entire video must be watched in order to trigger un-ban." +Change Video Poster and Background. +Entire video needs to be watched to trigger normal sharing. + +This is indented to restrict a user to the LIMIT amount of concurrent streams. User will be warned, punished, and +banned completely if violations continue. + +Clear user history for banned video to remove violation counts and run manually to share again. + +Concurrent stream count is the trigger. Trigger can be anything you want. + +""" + + +import requests +import sys +from xml.dom import minidom +from email.mime.text import MIMEText +import email.utils +import smtplib + + +## EDIT THESE SETTINGS ### + +PLEXPY_APIKEY = 'XXXXXX' # Your PlexPy API key +PLEXPY_URL = 'http://localhost:8181/' # Your PlexPy URL +PLEX_TOKEN = "" +SERVER_ID = "XXXXX" # Example: https://i.imgur.com/EjaMTUk.png + +# Get the User IDs and Library IDs from +# https://plex.tv/api/servers/SERVER_ID/shared_servers +# Example: https://i.imgur.com/yt26Uni.png +# Enter the User IDs and Library IDs in this format below: +# {UserID1: [LibraryID1, LibraryID2], +# UserID2: [LibraryID1, LibraryID2]} + +USER_LIBRARIES = {123456: [123456, 123456, 123456, 123456, 123456, 123456]} +BAN_LIBRARY = {123456: [123456]} # {UserID1: [LibraryID1], UserID2: [LibraryID1]} +BAN_RATING = 123456 # Banned rating_key to check if it's been watched. + +LIMIT = 3 +VIOLATION_LIMIT = 3 + +FIRST_WARN = 'Please watch this video to become un-banned. The whole video must be watched in order to trigger un-ban.' +FINAL_WARN = '3 Strikes you are out! You are permanently banned from my Plex.' + +SUBJECT_TEXT = "Plex rules have been violated." +BODY_TEXT = """\ + + + +

Hello,
+
{f}, you have been banned from my Plex and unshared from most of my libraries. + This is your violation {v} of {vt}.
+
{m}
+

+

+ + +""" + +# Email settings +name = '' # Your name +sender = '' # From email address +email_server = 'smtp.gmail.com' # Email server (Gmail: smtp.gmail.com) +email_port = 587 # Email port (Gmail: 587) +email_username = '' # Your email username +email_password = '' # Your email password + + +## DO NOT EDIT BELOW ## + +class Activity(object): + def __init__(self, data=None): + d = data or {} + self.rating_key = d['rating_key'] + self.title = d['full_title'] + self.user = d['user'] + self.user_id = d['user_id'] + self.video_decision = d['video_decision'] + self.transcode_decision = d['transcode_decision'] + self.transcode_key = d['transcode_key'] + self.state = d['state'] + +class Users(object): + def __init__(self, data=None): + d = data or {} + self.email = d['email'] + self.user_id = d['user_id'] + self.friendly_name = d['friendly_name'] + +def get_get_user(user_id): + # Get the user list from PlexPy. + payload = {'apikey': PLEXPY_APIKEY, + 'cmd': 'get_user', + 'user_id': int(user_id)} + + try: + r = requests.get(PLEXPY_URL.rstrip('/') + '/api/v2', params=payload) + response = r.json() + email = response['response']['data']['email'] + friend_name = response['response']['data']['friendly_name'] + return [email, friend_name] + + except Exception as e: + sys.stderr.write("PlexPy API 'get_get_user' request failed: {0}.".format(e)) + + +def get_get_history(user_id, bankey): + # Get the user history from PlexPy. Length matters! + payload = {'apikey': PLEXPY_APIKEY, + 'cmd': 'get_history', + 'rating_key': bankey, + 'user_id': user_id, + 'length': 10000} + + try: + r = requests.get(PLEXPY_URL.rstrip('/') + '/api/v2', params=payload) + response = r.json() + rec_filtered = response['response']['data']['recordsFiltered'] + # grow this out how you will + if rec_filtered < VIOLATION_LIMIT: + return rec_filtered + elif rec_filtered >= VIOLATION_LIMIT: + return 'ban' + + except Exception as e: + sys.stderr.write("PlexPy API 'get_get_history' request failed: {0}.".format(e)) + +def share(user_id, ban): + + headers = {"X-Plex-Token": PLEX_TOKEN, + "Accept": "application/json"} + + url = "https://plex.tv/api/servers/" + SERVER_ID + "/shared_servers" + + if ban == 1: + library_ids = BAN_LIBRARY[user_id] + else: + library_ids = USER_LIBRARIES[user_id] + + payload = {"server_id": SERVER_ID, + "shared_server": {"library_section_ids": library_ids, + "invited_id": user_id} + } + + r = requests.post(url, headers=headers, json=payload) + + if r.status_code == 401: + print("Invalid Plex token") + return + + elif r.status_code == 400: + print(r.content) + return r.status_code + + elif r.status_code == 200: + print("Shared libraries with user %s" % str(user_id)) + return + + return + +def unshare(user_id): + + headers = {"X-Plex-Token": PLEX_TOKEN, + "Accept": "application/json"} + + url = "https://plex.tv/api/servers/" + SERVER_ID + "/shared_servers" + r = requests.get(url, headers=headers) + + if r.status_code == 401: + print("Invalid Plex token") + return + + elif r.status_code == 400: + print r.content + return + + elif r.status_code == 200: + response_xml = minidom.parseString(r.content) + MediaContainer = response_xml.getElementsByTagName("MediaContainer")[0] + SharedServer = MediaContainer.getElementsByTagName("SharedServer") + + shared_servers = {int(s.getAttribute("userID")): int(s.getAttribute("id")) + for s in SharedServer} + + server_id = shared_servers.get(user_id) + + if server_id: + url = "https://plex.tv/api/servers/" + SERVER_ID + "/shared_servers/" + str(server_id) + r = requests.delete(url, headers=headers) + + if r.status_code == 200: + print("Unshared libraries with user %s" % str(user_id)) + + else: + print("No libraries shared with user %s" % str(user_id)) + + return + +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 send_notification(to=None, friendly=None, val_cnt=None, val_tot=None, mess=None): + # Format notification text + try: + email_subject = SUBJECT_TEXT + body = BODY_TEXT.format(f=friendly, v=val_cnt, vt=val_tot, m=mess) + + message = MIMEText(body, 'html') + message['Subject'] = email_subject + message['From'] = email.utils.formataddr((name, sender)) + + mailserver = smtplib.SMTP(email_server, email_port) + mailserver.ehlo() + mailserver.starttls() + mailserver.ehlo() + mailserver.login(email_username, email_password) + mailserver.sendmail(sender, to, message.as_string()) + mailserver.quit() + print('Email sent') + except Exception as e: + sys.stderr.write("Email Failure: {0}.".format(e)) + + +if __name__ == "__main__": + + activity = get_get_activity() + + act_lst = [a.user_id for a in activity] + user_lst = [key for key, value in USER_LIBRARIES.iteritems()] + + BAN = 1 + UNBAN = 0 + + for i in user_lst: + history = get_get_history(i, BAN_RATING) + mail_add, friendly = get_get_user(i) + + try: + if act_lst.count(i) >= LIMIT: + # Trigger for first and next violation + unshare(i) # Remove libraries + share(i, BAN) # Share banned library + sys.stdout.write("Shared BAN_LIBRARY with user {0}".format(i)) + if type(history) is int: + # Next violation, history of banned video. + send_notification(mail_add, friendly, history, VIOLATION_LIMIT, FIRST_WARN) + else: + # First violation, no history of banned video. + send_notification(mail_add, friendly, 1, VIOLATION_LIMIT, FIRST_WARN) + # email address, friendly name, violation number, violation limit, message + elif type(history) is int: + # Trigger to share + if share(i, UNBAN) == 400: + exit() # User has history of watching banned video but libraries are already restored. + else: + unshare(i) # Remove banned library + share(i, UNBAN) # Restore libraries + elif history == 'ban': + # Trigger for ban + unshare(i) + send_notification(mail_add, friendly, VIOLATION_LIMIT, VIOLATION_LIMIT, FINAL_WARN) + # email address, friendly name, violation number, violation limit, message + sys.stdout.write("User {0} has been banned".format(i)) + except Exception as e: + sys.stderr.write("Share_unshare failed: {0}.".format(e))