Parental Control Channel for PMS

Hello, after yosemite update when I try to unlock content I get this:

“unl0ck.command” can’t be opened because CoreServicesUIAgent is not allowed to open documents in Terminal.

EDIT:

Ok, seems that it works now, not sure why it happened but now it works fine! Thank you for that plugin! Very useful! 

 

Zarquon, I have included your tips and tricks  ;)

NB: I found that I had to check both for Client.Product 'Web Client' and 'Plex Web'.

[After deleting all my plugins and restarting PMS, I arrived indeed on a dashboard with a layout that I never saw before...

And the Product was 'Web Client'...]

if Client.Product and Client.Product in ('Plex Web', 'Web Client') :
DoSomething
else :
DoSomethingElse

So:

- I use now the "current path" in all the shell scripts.

- the plugin detects if it's a web client or not, offering the most "user friendly experience" depending on the Client Product   :)

- I have set a default delay in a Defaults.json file + used the API Prefs to pass the value to the script ShortUnl0ck....

- and added a settings icon to change that value  ;)

 

I have also improved a bit (IMHO) the user experience : If one types a wrong password when trying to unlock, no need to come back to the previous screen to retry.

This is especially convenient for the web client as one can retype immediately in the search box. Within PHT, you won't spare a hit.

Unfortunately, I ended by putting a mess in the plugin with the @routes...

In the Web Client, everything worked fine, but in PHT, the "Lock" button didn't work anymore...

The only solution I found was to remove the route on the function 'L0ck' ... I wonder how to fix this issue properly.

It's most probably because I call that method from various "routes" ? (I am not yet comfortable with that principle)...

Next step:

- if possible, return to the home/main screen directly after unlocking/locking (without navigating back to quite the plugin) <= still didn't find how to do that

iSh0w, if you could validate my change... I am really not yet comfortable with the Plex API and would benefit of some feedback.

By the way, shouldn't I post in the Dev thread instead ?   :wacko:   :huh:

 

So your __init__py file was not working for the short unlock and it was due to adding the delay pref to the BASEPATHSHORTUNL0CK and then it getting modified (replace " " with "\ ") Attached is the changes I made to the file.

I also changed the script files a bit.  First I do some checking to see if sqlite3 is installed and call that instance instead of the one supplied. Second I use a relative path to the DB file as that should work on more systems for people. Lastly I added logging to the files so it would be easier to debug.

Now on calling the script file for ShortUnlock with a parameter to pass in the delay I wonder if there would be a way the script could just read the delay setting from:

Plug-in Support/Preferences/com.plexapp.plugins.l0ckunl0ck.xml?

Some thing like this:

#!/bin/bash

cd “${0%/*}”
delay=$(grep -oPm1 “(?<=)[^<]+” …/…/…/…/…/Plug-in\ Support/Preferences/com.plexapp.plugins.l0ckunl0ck.xml)
sh ./unl0ck.sh
(sleep ${1:-${delay:-120}} ; sh ./l0ck.sh ) & 

exit;

EDIT: Updated Attachment to include above idea as it works fine.  Now if we added pref fields for the lock and inlock SQL statements then the plugin could be fully customized from the settings and not require custom versions for each install.  As least for the UN*X versions.  I don't think the windows versions would work with out maybe including something like http://win-bash.sourceforge.net/

Excellent!

I first thought it was not a good idea to read settings from the scripts because you take a dependency on plex's  architecture which can be changed without further notice (in this case, the reading of the settings).

But I am now considering the advantage of isolating the customized part (paths depending on the platform, etc...) from the core of iShow's plugin...

[EDIT]: I think iShow mentioned that he decided to use a local copy of sqlite because not all systems have the right version installed, which is the case on my NAS. S, I had to rollback this change from your script... 

Well from an architecture point of view I think it would still be better to pass the customization into the script files.  This way the functionality could still platform independent and only the script files on Windows( .bat) and UN*X systems (.sh) would be different but have the same basic functionality.

I haven't seen the .bat files but if it used: 

PUSHD "%~dp0"

This would be the equivalent of the .sh command

cd "${0%/*}"

The sleep could be accomplished with the following.  You would want to put it in a separate bat file and call if using the START /B command.

SET DELAY=%1
IF "%1"=="" SET DELAY=120
PING 127.0.0.1 -n %DELAY% >NUL 2>&1 || PING ::1 -n %DELAY% >NUL 2>&1
START /B l0ck.bat

Note this technique uses PING as that comes standard.  There is a SLEEP command in the Windows Resource Kits but that is not standard.  This method isn't perfectly accurate but I think would be good enough for this purpose.

After some code review, as I am still in favor of using Plex framework to read the settings instead of using custom code (so, if Plex decides to change how it stores/manages the settings, the plugin is not impacted), I did change the python script like this:

I have added a parameter "params" to pass any list of parameters to the script:

def L0ck(query, path, params, task):
  oc = ObjectContainer(title2="L0ck", no_cache=True)
  if task == 'UnL0cked' or task == 'ShortUnL0cked':
    if query == PASSWORD:
      Execute(path, params)

Where: 

def Execute(path, params):
  filepath = path.replace(' ', '\ ')
  parameters = ''
  for p in params:
    parameters = parameters + ' ' + p
  command = 'sh ' + filepath + parameters
  os.system(command)
  return

and I call L0ck like this:

oc.add(InputDirectoryObject(key=Callback(L0ck, path=BASEPATHSHORTUNL0CK, params=[Prefs['lockdelay']], task='ShortUnL0cked'), title="Short Unl0ck", thumb=R(SHORTUNL0CK_ICON), prompt="Enter your password"))

oc.add(InputDirectoryObject(key=Callback(L0ck, path=BASEPATHUNL0CK, params=[], task=‘UnL0cked’), title=“Unl0ck”, thumb=R(UNL0CK_ICON), prompt=“Enter your password”))

That being said, to be consistent, I should also fetch the path of the db via Plex's framework and pass it as a parameters too. Currently, I am using your code (at least, it's relative and does not depend on the server platform):

cd "${0%/*}"
./sqlite3 ../../../../../Plug-in\ Support/Databases/com.plexapp.plugins.library.db "$l0ckplex" >> l0ck_error.log 2>&1

I presume that I should use the same code as used by iShow to get BASEPATHL0CK, etc... I will do that later (once the kids are in beds :) )

V.

Unless there is functionality in the Plex framework to determine the Database directory then I think the relative path works fine as it will be determined using the same framework code as the location of the script directory.  I do however agree that the better way is to pass in the custom values such as delay rather than read from the config files directly.  This is not only better practice but is more platform independent as I do not know of a way to due the same in native Windows.

In the Execute function you may want to wrap the parameters in Quotes so as to handle spaces.  This would be needed if the SQL commands are added to the configuration and passed in as parameters:

def Execute(path, params):
        filepath = path.replace(' ', '\ ')
        parameters = ''
        for p in params:
                parameters = parameters + ' "' + p + '"'
        command = 'sh ' + filepath + parameters
        os.system(command)
        return

What are your thoughts also on doing this with the assignment of the filepath?  i.e. filepath = '"' + path + '"' instead of the replace.  I'm again thinking of platform independence as the replace will not work in Windows.

[EDIT]: I think iShow mentioned that he decided to use a local copy of sqlite because not all systems have the right version installed, which is the case on my NAS. S, I had to rollback this change from your script... 

The problem here is that the local copy breaks some installs mine included but yea my solution looks to see if it is installed and if so call the installed version.  Maybe a better solution would be to have call if the one way and then call if the other if an error occurs.  i.e. call local first and if error call installed version.

If the quotes are more "cross-platform-compatible", it's indeed probably the way to go.

I can confirm it works fine on my NAS. I will try on my Windows server when I can.

def Execute(path, params):
	parameters = ''
	for p in params:
		parameters = parameters + ' \'' + p + '\''
	command = 'sh \'' + path + '\''+ parameters
	Logger('Execute: ' + command, force=True)
	os.system(command)
	return

Regarding the location of the database, it can be built as the others:

BASEPATHDATABASE = (Core.storage.join_path(Core.storage.join_path(Core.app_support_path, Core.config.plugin_support_dir_name), 'Databases')+'com.plexapp.plugins.library.db')
 

So, I pass it now as a parameter to the shell scripts.

NB: I had a look to access sqlite directly from the python script. But python does not find the module sqlite3.so on my NAS?!

sys.path seems to be empty ??? Weird as the module is for sure available in /lib/python2.7/lib-dynload/_sqlite3.so. I still have a lot to learn :/

But with the adequate parameters to recreate the sql query on the fly, one could possible get rid of the shell scripts...

Here attach is the current result (I removed the code using the pre-installed sqlite as it fails on my NAS, until one can handle the errors as you suggested... I see now I didn't read all your previous post. I will have a look on your latest findings).

To sleep on Windows, the ping can indeed be used as you suggest...

So, to sleep 120 sec in a .bat, I would ping on a fake IP:

ping 1.1.1.1 -n 1 -w 120000 > nul

But as far as I know, it comes with MSDos...

NB: I had a look to access sqlite directly from the python script. But python does not find the module sqlite3.so on my NAS?!

Running QNAP, Synology or?

I was a bit involved in this in it's very first stage, where this was mentioned (By me)

And was getting fired all up when it was turned over to a Ninja, and then aborted when the origen author without warning took it back again, and went into a "path hell"  :rolleyes:

But here's my comment from then:

https://forums.plex.tv/topic/113976-parental-control-channel-for-pms/?p=680233

/T

Thx /T!

I have a Synology...

Reading about your attempts, I will rather not invest more time in that direction ;)

Thx Dane!

I have a Synology...

Reading about your attempts, I will rather not invest more time in that direction ;)

Saying that I didn't understand, would be the understaement of the year  :D

But if you are like me, a "nerd", then for personal reasons:

Then follow the links in the post i lead you towards, and you'll find the missing file...

All you then need to do, is figure out the path to put it in, since I'm not a Synology dude  ;)

Best regards

Tommy

I read here that one can simply "hack" a library (it's id) to hide its content, instead of "deleting" its content... 

Using that trick, found by TobyWiddow, there is no need to refresh the client after a lock/unlock... I like this trick  :P

So, I have now added a second settings for the plugin: locktype = 'hide' or 'empty'.

Using 'empty' (the default) uses iShow's approach... Using 'hide' uses TobyWiddows' approach. E.g. for the L0ck.sh

if [ "${2}" == "empty" ]; then
  l0ckplex="UPDATE metadata_items SET metadata_type=20 WHERE library_section_id=18 and metadata_type=1; DELETE FROM library_sections WHERE id=18;"
else
  l0ckplex="UPDATE metadata_items SET library_section_id = -library_section_id where library_section_id = 18 and metadata_type=1;"
fi

However, changing that settings after a lock and before the unlock can be confusing (it will actually not work. Except if one executes always the two updates by default!). So I should add a hidden setting where I store the locktype mode used for the latest "lock" and use that one for the unlock... having such a setting, I could also hide the "L0ck" button when libraries are assumed to already be "L0cked"  B)

Pros of using the lock type 'hide': no need to refresh and if the library is fully displayed in Plex Web before the re-lock, one can apparently still browse and open a movie! (Does not work however with Plex Home Theater and Plex for Samsung).

Cons: the name of the library is still visible... (don't name it "P0rn" but "Horror movies" :rolleyes: )

I will look tomorrow to add a hidden setting with the lock type used for the last l0ck and upload my changes here when it runs.

Also, it would be great to select which library must be "Controlled" from within the Plugin. Using only the lock type 'hide', one needs nothing more than the library_section_id's... I would really need to be able to execute sqlite query from Python for that... or dig into Plex framework ?!

V.

To sleep on Windows, the ping can indeed be used as you suggest...

So, to sleep 120 sec in a .bat, I would ping on a fake IP:

ping 1.1.1.1 -n 1 -w 120000 > nul

But as far as I know, it comes with MSDos...

Yes ping is standard on a Windows install but I would  use the line I had above as it factors in IP6 support as well.  Also yes building the DB location that way is much better.

It would defiantly be better to handle the locking/unlocking directly from python.  Handling the Sleep mechanism could be done use os.fork.  Here us a good explanation and example.

So, yet a few changes for my own comfort  :rolleyes:

  • No ShortUnlock.sh script anymore. I use Zarquon's suggestion to relock with a thread started from within python.
  • When libraries are locked, only buttons to unlock are visible.
  • When libraries are unlocked, only button to lock is visible.
  • If something went wrong when unlocking/locking, all buttons are visible (Panic mode :D )
  • "L0ck" can be made based on two options: "Hide" or "Empty"
    • 'Hide' hides the libraries from Plex client by deleting them from the database when locked (iShow's approach). Libraries are recreated when unlocked and Plex Client possibly has to be refreshed
    • 'Empty' empties the libraries from Plex client by "flagging" them in the database (TobyWiddows' approach). Libraries are "unflagged" when unlocked and Plex Client does not need to be refreshed
  • "Unlock" is always done with the mode used for the last "L0ck"
  • I have refactored the L0ck method into explicit (although a bit redundant) methods: L0ck, Unlock, ShortUnl0ck and Rel0ck.

Possible next steps:

  • Configure the libraries to be "emptied" (via the plugin). One simply needs a list of library_section_id, so it should be much easier than configuring the libraries to be "hidden" (which requires to recreate the library_sections)... B)
  • One could next implement support to lock/unlock the libraries individually with a distinct password for each...  :P
  • Navigate back to the Home Screen after locking/unlocking (or at least after "short unlocking")... if I could find how to do that  :unsure:

Yessssss! The official parental control is finally there:

https://blog.plex.tv/2014/11/20/introducing-plex-home/

I will test it asap... I hope this will be available in the Samsung smart hub too :P

In the mean time, l0ck will still be very useful...

So I guess this is now a little irrelevant but your to make your changes work I had to make the following adaptations to the script file.  I think I solve the sqlite3 calling issue but need you to test. :P   The biggest issue was the discrepancy between the .py code calling sh and the script file being a bash file.  Switched to sh in the header and changed the syntax accordingly.

#!/bin/sh

if [ “$2” = “empty” ]; then
l0ckplex=“UPDATE metadata_items SET metadata_type=20 WHERE library_section_id=7 and metadata_type=1; DELETE FROM library_sections WHERE id=7;UPDATE metadata_items SET metadata_type=20 WHERE library_section_id=12 AND metadata_type=1; DELETE FROM library_sections WHERE id=12;”
else
l0ckplex=“UPDATE metadata_items SET library_section_id = -library_section_id WHERE library_section_id IN (7,12) AND metadata_type=1;”
fi

cd “${0%/*}”
echo ${3:-LOCK}: date >> l0ck_error.log
templog="$(mktemp)"
./sqlite3 “${1}” “$l0ckplex” > $templog 2>&1
rc=$?
if [ $rc -ne 127 ] ; then
cat $templog >> l0ck_error.log
else
sqlite3 “${1}” “$l0ckplex” >> l0ck_error.log 2>&1
rc=$?
fi
if [ $rc != 0 ] ; then
echo Error Code [$rc] >> l0ck_error.log
fi

exit 0 

Now after updating to the latest plex your .py changes fail on the writing of the preferences. :(  I guess we have to figure out how to negotiate the new permissions mechanism

Replace localhost by 127.0.0.1

This change is required since plex home has new security requirements.

L0ck will still be relevant for me as long as the fast switch user is not available on my Samsung TV...

V.

Hey Zarquon,

I didn't test your script yet because I was busy with implementing my last features (not only because the new Home Plex features are not yet available on my Samsung TV, but also because it's a good exercise to learn Python, Sqlite and Plex's framework; reason why I try a bit of everything: Prefs, Dict, Data, Http Query, etc...)..

Now, there is no customization anymore required in the shell scripts... neither to "hide" the libraries (by deleting the section from the DB) nor to make it "empty" (by changing the section id in the metadata).

I can indeed now do the configuration directly in the plugin. I display a list of the existing libraries and one can select those to be "controlled"...

Next, I pass that list to the scripts as an additional parameters. And to recreate the deleted section, I dump them first in a temporary file...

Here is the scenario:

- First time one starts the plugin, one has access to "Update Feed" and "Configure" as well as to Plugin's Preferences.

- By default, in the Preferences, the locking mode is "empty" (my favorite as one doesn't need to refresh the Plex Client. Also less "risky" as it doesn't delete any data).

- Click on "Configure" to select the libraries to be controlled. (Y) is added in front of the name when a library is controlled. Notice that the display is in "List mode" when using Plex Web and in "Poster mode" when using PHT... I cannot get it?!

- Once the libraries to be controlled are all selected, click "Done". Click "Clear" to deselect all the libraries. Notice that one cannot reselect/deselect a library that has just been deselected/selected... Another point that I cannot get?!

- Once configuration done, one has access to "Lock" (currently with the lock mode between brackets as a reminder while testing)

- if one clicks on "Lock", the controlled libraries' sections are deleted when using hide mode and the metadata are "detached".

If the "Lock" technically succeeds, the button disappears and one has now access to the "Unlock" and "Short Unlock" buttons instead (currently with the unlock mode between brackets as a reminder while testing. The unlock mode is obviously the lock mode that was applied by the "Lock" button. It's required to determine if sections must be recreated). In addition, while libraries are locked, the Configuration button becomes unavailable (changing the controlled libraries while some are locked could result in a mess, especially with the "hide" mode).

If one clicks next on "Unlock" or "Short Unlock" (and the operation technically succeeds), the "Lock" and "Configure" buttons reappear while the two others disappear.

If by any accident an operation fails (ex.: due to any sqlite command failure), both "Lock" and "Unlock" buttons are made available, so then can be used to retry once the bug is fixed in the scripts. But one has to be careful as the losing the temporary file with sections' data could be fatal. Also, the Configure button can be used to reset the "failed" status.

heu... voilà. It still requires a lot of testing and improvement* but the bases is there IMO. Next time I play with it, I will insert your code to handle sqlite!

* for example, there is no "home" button in PHT. So going back to the home screen is a real pain in...