diff --git a/plugin.video.defconconf/addon.xml b/plugin.video.defconconf/addon.xml
index 7786a05..2820607 100644
--- a/plugin.video.defconconf/addon.xml
+++ b/plugin.video.defconconf/addon.xml
@@ -1,5 +1,5 @@
-
+
diff --git a/plugin.video.defconconf/default.py b/plugin.video.defconconf/default.py
index 512b49c..df0c16e 100644
--- a/plugin.video.defconconf/default.py
+++ b/plugin.video.defconconf/default.py
@@ -4,16 +4,16 @@
# Author: Oli Passey (DoubleT)
-import os
-import xbmc
-import xbmcaddon
-import xbmcplugin
+import os
+import xbmc
+import xbmcaddon
+import xbmcplugin
from koding import route, Addon_Setting, Add_Dir, Find_In_Text, Open_URL, OK_Dialog
from koding import Open_Settings, Play_Video, Run, Text_File
-debug = Addon_Setting(setting='debug')
-addon_id = xbmcaddon.Addon().getAddonInfo('id')
+debug = Addon_Setting(setting='debug')
+addon_id = xbmcaddon.Addon().getAddonInfo('id')
BASE = "plugin://plugin.video.youtube/playlist/"
BASE2 = "plugin://plugin.video.youtube/channel/"
@@ -26,36 +26,41 @@ YOUTUBE_CHANNEL_ID_5 = "UCur4HQg-2EQltweoKQfwOfg"
YOUTUBE_CHANNEL_ID_6 = "PL9fPq3eQfaaBD_8E9PJ8yyiTL0JhynlGK"
YOUTUBE_CHANNEL_ID_7 = "PL9fPq3eQfaaCIZajWLyN5f6M0HoU_Avuk"
YOUTUBE_CHANNEL_ID_8 = "PL9fPq3eQfaaDcbIEMSzdL5yuzh_m6BB-E"
+YOUTUBE_CHANNEL_ID_9 = "PL9fPq3eQfaaD0cf5c7wkzMoj2kifzGO4U"
@route(mode='main_menu')
def Main_Menu():
- Add_Dir(
+ Add_Dir(
+ name="DEF CON 26 (2018)", url=BASE+YOUTUBE_CHANNEL_ID_9+"/", folder=True,
+ icon="https://www.defcon.org/images/defcon-26/post-images/defcon26-logo.png")
+
+ Add_Dir(
name="DEF CON 25 (2017)", url=BASE+YOUTUBE_CHANNEL_ID_1+"/", folder=True,
icon="https://www.defcon.org/images/defcon-25/post-images/dc-25-logo.jpg")
- Add_Dir(
+ Add_Dir(
name="DEF CON 24 (2016)", url=BASE+YOUTUBE_CHANNEL_ID_4+"/", folder=True,
icon="https://www.defcon.org/images/defcon-24/dc-24-logo-sm.png")
- Add_Dir(
+ Add_Dir(
name="DEF CON 23 (2015)", url=BASE+YOUTUBE_CHANNEL_ID_3+"/", folder=True,
icon="https://www.defcon.org/images/defcon-23/dc-23-logo-sm.jpg")
- Add_Dir(
+ Add_Dir(
name="DEF CON 22 (2014)", url=BASE+YOUTUBE_CHANNEL_ID_2+"/", folder=True,
icon="https://www.defcon.org/images/defcon-22/dc-22-web.jpg")
- Add_Dir(
+ Add_Dir(
name="DEF CON 21 (2013)", url=BASE+YOUTUBE_CHANNEL_ID_6+"/", folder=True,
icon="https://www.defcon.org/images/defcon-21/dc-21-logo-sm.png")
- Add_Dir(
+ Add_Dir(
name="DEF CON 20 (2012)", url=BASE+YOUTUBE_CHANNEL_ID_8+"/", folder=True,
icon="https://www.defcon.org/images/defcon-20/dc20-logo_smsq.png")
-
- Add_Dir(
+
+ Add_Dir(
name="DEF CON 20 Documentary", url=BASE+YOUTUBE_CHANNEL_ID_7+"/", folder=True,
- icon="https://www.defcon.org/images/defcon-20/dc20-logo_smsq.png")
+ icon="https://www.defcon.org/images/defcon-20/dc20-logo_smsq.png")
@route(mode='koding_settings')
def Koding_Settings():
@@ -67,4 +72,4 @@ def Simple_Dialog(title,msg):
if __name__ == "__main__":
Run(default='main_menu')
- xbmcplugin.endOfDirectory(int(sys.argv[1]))
\ No newline at end of file
+ xbmcplugin.endOfDirectory(int(sys.argv[1]))
diff --git a/script.module.python.koding.aio/Changelog-1.0.txt b/script.module.python.koding.aio/Changelog-1.0.txt
new file mode 100644
index 0000000..921ad9c
--- /dev/null
+++ b/script.module.python.koding.aio/Changelog-1.0.txt
@@ -0,0 +1,386 @@
+v.1.0:
+- Stable release, only bug reported in past few months was custom fanart not showing for the Add_Dir funtion so this has now been fixed.
+- Cleaned up some old unused strings.
+
+v.0.9.9.4.3:
+- Added options to include icon and fanart into the Custom_Dialog function.
+- Added new Merge_Dicts function to merge multiple dictionaries together.
+- Fixed bug with Create_Paths function.
+- Added List_From_Dict function, just a simple function which returns a list of either the keys or values found in a dictionary.
+- Edited the Addon_Genre function, the values were set as keys and vice versa. Value is now the add-on ID and the key is the add-on name.
+
+v.0.9.9.4.2:
+- Added Requirements function support for AFTV and other non-rooted devices
+
+v.0.9.9.4.1:
+- Small cleanup getting ready for future features
+
+v.0.9.9.4:
+- Added ability to send through a custom resolver for Play_Video()
+- Added a try/except for dolog so it will silently fail if wrong params are sent through and print the relevant error message to the log.
+
+v.0.9.9.3.4:
+- Added unidecode import for future use
+- Added some mac bootcamp support
+
+v.0.9.9.3.3:
+- Fixed bad indentation in addon.xml
+
+v.0.9.9.3.2:
+- Added extra security checks in Move_Tree, esp. for OE/LE devices.
+
+v.0.9.9.3.1:
+- Bug fix with Move_Tree, some platforms it was corrupting files on move
+
+v.0.9.9.3:
+- Bug fix with adult enable
+- Move_Tree can now accept special paths.
+
+v.0.9.9.2:
+- Tidied up the README file, now matches what's in the add-on and every function has been tested
+- Fixed some example code
+
+v.0.9.9.1.4:
+- Added Physical_Path function which will return a correct special path even with Windows Store versions of Kodi
+- Fixed a number of modules so it's Win10 (Windows Store version) friendly
+- Improved the Create_Paths function so it can now take the special:// path
+- Fixed some bugs with Data_Type being referenced in the wrong module
+- Fixed small bug in xml function
+- Changed the default proxy url for the Link_Tester function (old one now uses CF)
+- Changed most os functions to xbmcvfs accross all modules
+- Removed Grab_Params and Populate_List functions, no longer used
+- Cleaned up all old features no longer used in init.py
+- Cleaned up the README.txt
+- Added new contact info to the headers of all py files
+
+v.0.9.9.1.2:
+- Added Remove_Formatting() function, this can remove any color, bold and italic tags as well as removing any preceding characters such as spaces, dots and dashes
+
+v.0.9.9.1.1:
+- Removed excessive logging in addons.py
+
+v.0.9.9.1:
+- Fixed the dolog function, now showing correct file and line numbers
+- Added xml function which will allow for creation/editing of completely new and separate settings.xml files for your add-ons. All credit goes to OptimusGreen for this code.
+- Edited tutorials section so it also picks up classes as well as functions.
+
+v.0.9.9.0:
+- Added compatibility for Windows 10 on creating paths
+
+v.0.9.8.9:
+- Removed debugging info in sleep functions.
+
+v.0.9.8.8:
+- Added check on web based functions, attempt to auto change url's to http instead of https if Python version is lower than 2.7
+- Added new option to dolog function which can show line number and file where the dolog command was called from. Thanks to OptimusGreen for the suggestion and line number code.
+- Removed some unnecessary logging from the Add_Dir command.
+
+v.0.9.8.7:
+- Added new Python_Version function to return current running version of Python
+- Added beautifulsoup to addon.xml as dependency
+
+v.0.9.8.6:
+- Improved Fresh_Install function, now multi-platform and can add paths/addons to ignore
+- Fixed the Delete_Folders function, was removing sub-folders from items in ignore list
+
+v.0.9.8.4:
+- Re-done the Compress function as shutil.make_archive doesn't work on python 2.6 so now using tarfile and zipfile.
+
+v.0.9.8.3:
+- Added option to browse into zips when using Browse_To_File, default is False
+- Fixed the update spinner in Update Screen, wasn't able to disable
+- Fixed alignment of text in Update Screen, was showing oddly on some skins
+
+v.0.9.8.2:
+- Edited update_screen alignment for text
+
+v.0.9.8.1:
+- Edited adult toggle to use safe mode and toggle all at once rather than one by one
+- Adult_Toggle now requires an update_status to be sent through, if nothing sent through the default status will be set to auto-update once function is complete.
+
+v.0.9.8.0:
+- Browse_To_Folder no longer shows contents of archives
+
+v.0.9.7.9:
+- Removed dolog import from video.py as was causing problems
+- Fixed profile_path in database.py
+
+v.0.9.7.8:
+- Added Reset_Percent function
+- Fixed bug in database module, Addon ID was not being called correctly.
+- Added extra debugging notifications for add-on install
+- Disable/enable auto add-on updates in add-on install at start/end
+
+v.0.9.7.7:
+- Added timeout checker for Download function, especially useful on older installs which do not support SSL as it was previously waiting 30s before finally giving up.
+
+v.0.9.7.6:
+- Added Update_Screen function
+- Improved Update_Progress function
+
+v.0.9.7.5:
+- Added Update_Progress function for setting percentage of items currently processed as a skin string and also allows for any properties to be set at the same time.
+
+v.0.9.7.4:
+- Removed unquote from routing function as it was messing up the final var received
+- Re-work of how Toggle_Addons and Dependency_Check work, should be far more optimised now as when toggling add-ons it enables each dependency one at a time starting with the dependencies with no sub-dependencies and moving on up through the list progressively.
+
+v.0.9.7.3:
+- Another bug fix for routing unquoting issue
+- Added ability to pass through a skip_list in Addon_Service
+- Removed service toggle in Addon_Toggle, was causing problems
+
+v.0.9.7.2:
+- fix unquoting issue (midraal)
+
+v.0.9.7.1:
+- Added Disable/Enable services to Addon_Toggle
+
+v.0.9.7.0:
+- Added new Addon_Service funtion to show all addons running as service and also has the ability to disable/enable them. This only comments out the lines in the addon.xml, if they are already running as a service they will not be disabled until the next profile load. Similarly if enabled they will require a profile load for changes to take effect.
+- Fixed bug with Toggle_Addons - was trying to import a dependency from a path which no longer exists
+
+v.0.9.6.9.9:
+- Fixed bug with Addon_Genre not deleting old failed temp files
+
+v.0.9.6.9.8:
+- Fixed bug with dependency check, wasn't showing all recursive deps
+
+v.0.9.6.9.7:
+- Added Link_Tester function, returns whether or not a link is locked to users IP
+- Added fix for Play_Video so it doesn't close dp if ignore_dp is set to True.
+
+v.0.9.6.9.6:
+- Added Last_Played function to return the real url of the last played item
+- Updated Addon_Genre, now fully unlocked and working for all developers to hook into.
+
+v.0.9.6.9.5:
+- Added Last_Played function to return the real url of the last played item
+- Added new Split_List function for creating new lists out of elements of another
+- Added new Table_Convert function, this will allow you to pull data from a html
+page conaining a table and convert into a list of dictionaries with your own custom keys.
+- Updated Addon_Genre, now fully unlocked and working for all developers to hook into.
+- Updated Open_URL so it can now take send a request via a proxy
+
+v.0.9.6.9.4:
+- Changed Add_Dir and route function so it auto converts physical paths into special paths when sending through any urls or creating routes. These are then auto converted back once returned to the function. This means all paths will work when using community shared content - no need for manually editing special paths.
+
+v.0.9.6.9.3:
+- Added new functionality to Convert_Special, can now set string to True and it will replace any instances of physical paths to special in the string which was sent through.
+
+v.0.9.6.9.2:
+- IMPORTANT FOR DEVS: Moved some functions around in preparation for upcoming stable 1.0 release. A new module has been created called vartools, all variable/string related functions can now be found in here as we seem to be accumilating rather a lof of these new functions!
+- Added new Fuzzy_Search function which searches for similar matches to a string
+- Fixed Open_Settings, wasn't opening into custom addon id's settings.
+- Changed the return of Keyboard, was returning as unicode.
+- Re-done some of the live example code with better dialogs and wording.
+- Re-formatted the README to reflect new changes.
+
+v.0.9.6.9.1:
+- Removed auto resolving of m3u in Play_Video as it was causing bugs in some links
+
+v.0.9.6.9:
+- New M3U_Selector function for playing m3u/m3u8 playlists
+- Added new Parse_XML function
+- Added button colour options for Custom_Dialog function
+- Added midraals new code in check_playback which hopefully should get rid of the dialog appearing on top of playback on some systems.
+
+v.0.9.6.8:
+- Added Decode_String function to clean up unicode chars
+- Updated the readme info, had outdated info for Custom_Dialog
+
+v.0.9.6.7:
+- New Custom_Dialog function allowing for multiple buttons and custom size dialogs
+- Add_Dir now automatically converts dictionary items for multiple urls
+
+v.0.9.6.6:
+- Allow for metadata to be passed through to Play_Video (as a dictionary)
+- Fixed typo in Play_Video when sending through an item param
+- Added some missing functions in README.
+
+v.0.9.6.5:
+- Fixed Check_Repos, was a bug if scanning full paths
+
+v.0.9.6.4.2:
+- Added a timeout to Validate_Link function
+- Validate_Link will now return False if https cannot be accessed
+- Check_Repos now returns False if https being used on Python 2.6
+
+v.0.9.6.4.1:
+- Fixed indentation in toggle_addons
+
+v.0.9.6.4:
+- Still a bug with toggle_addons locking up system so reverted that code to old.
+
+v.0.9.6.3:
+- Redone Addon_Toggle a little as there are still bugs
+
+v.0.9.6.2:
+- Bug fix for misplaced variable in Addon_Toggle
+
+v.0.9.6.1:
+- Added Check_Repo() function to check if the xml links can be resolved
+- Fixed bug in Toggle_Addons, was failing to enable recently unzipped
+- Updated some missing info from the README file
+
+v.0.9.6:
+- Added Requirements function to return min and max requirements for kodi dependencies
+- Added Highest_Version function for returning highest version in a list
+- Improved some video.py functionality, thanks to midraal
+- Fixed Adult_Toggle, had had a typo in there which was occasionally causing an error
+- Fixed exception in adult toggele for when no id can be found natively
+- Fixed the strings so error message works for run of online code when in debug mode
+- Removed Install_Addon due to high server loads at NaN. This is hopefully temporary
+but at this moment in time the future of this function is uncertain.
+
+v.0.9.5:
+- Fixed bug in sleep_if_function_active, was closing dialog busy
+
+v.0.9.4:
+- Added new Adult_Toggle function
+- Improved Adult_Genre so it can take a custom url, no longer NaN dependant.
+
+v.0.9.3.1:
+- Fixed indentation error in database module
+
+v.0.9.3:
+- fixed a bug with remove_from_table
+- added reset_db function to use when running into threading error with sqlite
+- Added exceptions for built-in python libraries, no need to attempt enabling those
+- Fixed Main() commands which had broken due to cleaning of newlines and tabs
+- Fixed
+
+v.0.9.2:
+- Added Select_Dialog function to allow user to select from multiple values.
+- Fixed the Open_URL function so it works correctly with User_Info again.
+- Changed Open_URL to return False in case of failed attempt (was returning a test string). Will also print details of the bad url to log if addon debugging mode enabled.
+
+v.0.9.1:
+- Added option to send through params in Open_URL (see README for full info)
+- Added Delete_Cookies function
+- Fixed bug in Addon_Settings where it wasn't possible to set a blank string
+- Added an optional error message if Extract fails
+
+v.0.9:
+- Fixed db query example, wasn't working on pre-Krypton
+- Added exception for Get_ID, only works on certain platforms so returns False if not supported on current os.
+
+v.0.8.9:
+- Added extra logging for Main runcode
+- Added proper exceptions if URL could not be opened
+
+v.0.8.8:
+- Removed rogue ampersand in addon.xml
+
+v.0.8.7:
+- Added OK_Dialog function
+- Added YesNo_Dialog function
+- Added Compress function - this will probably end up replacing Archive_Tree longer term
+- Suppressed error popup message when online code fails
+
+v.0.8.6:
+- Fixed logging in Open_URL, was logging everything by default
+
+v.0.8.5:
+- Fixed bad error reporting in addon_enable
+- Fixed example code for Sleep_If_Playback_Active function
+
+v.0.8.4:
+- Added extra json query attempt for addon_enable
+
+v.0.8.3:
+- Fixed Timestamp issue in Addon_Genre function
+
+v.0.8.2:
+- Added Sleep_If_Playback_Active() function, this pauses code until playback stops
+
+v.0.8.1:
+- Fixed the Open_Settings, wasn't opening the caller id correctly
+
+v.0.8:
+- Improved Open_URL. Added cookie, header, auth and timeout support
+- Edited Default_Setting function so it can return a dictionary of all defaults
+- Added the ability to send through various params to Add_Dir
+- Added new routing (thanks to midraal for PR)
+- Added Remove_From_Table function
+- Renamed Run_As_Kodi to Get_ID and changed function behaviour slightly
+- Split up add-on based functions into new addon module
+- Added some more documentation to the README
+
+v.0.7.5:
+- Rezipped, had used 7zip think that caused problems.
+
+v.0.7.4:
+- Fixed download dialog not auto-closing once complete. Last push didn't work correctly
+
+v.0.7.3:
+- Fixed the download function, wasn't automatically closing dialog once complete.
+
+v.0.7.2:
+- Tidied up the getSetting functions.
+- Fixed Create_Paths function.
+- Fixed the tutorials on Windows platforms, required url sending through as urlencoded then unencoded.
+
+v.0.7.1:
+- Added Run_As_Kodi() function which hopefully should fix Android Fresh_Install()
+- Improved Open_Settings(), can now focus on a specific item and also send an automated click on the focused item.
+
+v.0.7:
+- Added Addon_Info() function to return various built in add-on values.
+- Added Addon_Setting() function to set/retrieve an add-on setting.
+- Added new Add-on section to the README.
+- Added subprocess import for fresh_install.
+- Typo in Android check for fresh_install, hopefully fixed.
+- Added Addon_Install() function to install add-ons.
+- Added Addon_Genre() function to return a dict of all specific genre type add-ons found at the Add-on Portal.
+- Fixed unicode issue when sending through list of addons to Toggle_Addons()
+
+v.0.6:
+- Added ability to run another plugin via Add_Dir, just send mode as a blank string.
+- Added System() function which allow easy access to various xbmc commands.
+- Cleaned up the README file.
+- Added ASCII_Check() function, will return a list of non-ASCII filenames found.
+- Added md5_check() function to return md5 value of file or folder.
+- Added Android functions for opening, uninstalling and listing installed apps.
+- Added Android function for opening into a specific apps settings.
+- Added Force_Close() function
+- Added Running_App() function which returns the name of current running app.
+- Added Fresh_Install() function which will wipe your current install.
+- Added Delete_Crashlogs() function.
+- Improved Delete_Folders function, can now send through an ignore list for
+file/folder paths you don't want removed.
+- Added Sleep_If_Function_Active() which will sleep until the previously called
+function has completed.
+- Renamed Sleep_If_Active() to Sleep_If_Window_Active()
+- Fixed Open_URL function which was broken in last push
+- Added new Caller() function
+- Changed the way the addon id is pulled so it works when called from scripts.
+- Added requests dependency which had somehow been ommitted
+- Fixed up the Add_Dir function so it's now showing correct infolabels (hopefully!)
+- Added auto infolabel generation to the Play_Video function (thanks midraal)
+- Re-coded Add_Dir so it now takes context menus and dictionaries for params
+- Cleaned up and improved a lot of the guides
+- Fixed Play_Video so it also works with audio
+- Improved Grab_Log, can now filter and also have clean formatted text
+- Added Notify (A simple notification popup)
+- Added Sleep_If_Active (pauses code until a specific window closes)
+- Added Move_Tree (Moves directory from a to b)
+- Added Toggle_Addons (enables/disables add-ons)
+- Added various database functions (thanks to midraal), details below:
+-- Add_To_Table (adds a row to the table stored in your addons addon_data folder)
+-- Create_Table (creates a table in your addons db)
+-- Get_All_From_Table (grabs all results from a specific table in your db)
+-- Get_From_Table (grabs specific results based on a query on your db)
+-- Remove_From_Table (removes specific results based on a query on your db)
+
+
+v.0.4:
+- Fixed Browse_To_File
+- Moved Open_URL into web module, made more sense there
+- Added Validate_Link (Checks if url sends back a code 200 - in other words it's live)
+- Added DB_Query (A simple SQLite Db tool, execute any SQL command)
+- Added Get_Extension (Returns the extension type of an online file)
+- Added Dependency_Check (Returns all dependencies required for an addon and optionally all sub-dependencies)
+
+
+v.0.1: Initial release
diff --git a/script.module.python.koding.aio/License.txt b/script.module.python.koding.aio/License.txt
new file mode 100644
index 0000000..9576c1e
--- /dev/null
+++ b/script.module.python.koding.aio/License.txt
@@ -0,0 +1,402 @@
+Attribution-NonCommercial-NoDerivatives 4.0 International
+
+=======================================================================
+
+Creative Commons Corporation ("Creative Commons") is not a law firm and
+does not provide legal services or legal advice. Distribution of
+Creative Commons public licenses does not create a lawyer-client or
+other relationship. Creative Commons makes its licenses and related
+information available on an "as-is" basis. Creative Commons gives no
+warranties regarding its licenses, any material licensed under their
+terms and conditions, or any related information. Creative Commons
+disclaims all liability for damages resulting from their use to the
+fullest extent possible.
+
+Using Creative Commons Public Licenses
+
+Creative Commons public licenses provide a standard set of terms and
+conditions that creators and other rights holders may use to share
+original works of authorship and other material subject to copyright
+and certain other rights specified in the public license below. The
+following considerations are for informational purposes only, are not
+exhaustive, and do not form part of our licenses.
+
+ Considerations for licensors: Our public licenses are
+ intended for use by those authorized to give the public
+ permission to use material in ways otherwise restricted by
+ copyright and certain other rights. Our licenses are
+ irrevocable. Licensors should read and understand the terms
+ and conditions of the license they choose before applying it.
+ Licensors should also secure all rights necessary before
+ applying our licenses so that the public can reuse the
+ material as expected. Licensors should clearly mark any
+ material not subject to the license. This includes other CC-
+ licensed material, or material used under an exception or
+ limitation to copyright. More considerations for licensors:
+ wiki.creativecommons.org/Considerations_for_licensors
+
+ Considerations for the public: By using one of our public
+ licenses, a licensor grants the public permission to use the
+ licensed material under specified terms and conditions. If
+ the licensor's permission is not necessary for any reason--for
+ example, because of any applicable exception or limitation to
+ copyright--then that use is not regulated by the license. Our
+ licenses grant only permissions under copyright and certain
+ other rights that a licensor has authority to grant. Use of
+ the licensed material may still be restricted for other
+ reasons, including because others have copyright or other
+ rights in the material. A licensor may make special requests,
+ such as asking that all changes be marked or described.
+ Although not required by our licenses, you are encouraged to
+ respect those requests where reasonable. More_considerations
+ for the public:
+ wiki.creativecommons.org/Considerations_for_licensees
+
+=======================================================================
+
+Creative Commons Attribution-NonCommercial-NoDerivatives 4.0
+International Public License
+
+By exercising the Licensed Rights (defined below), You accept and agree
+to be bound by the terms and conditions of this Creative Commons
+Attribution-NonCommercial-NoDerivatives 4.0 International Public
+License ("Public License"). To the extent this Public License may be
+interpreted as a contract, You are granted the Licensed Rights in
+consideration of Your acceptance of these terms and conditions, and the
+Licensor grants You such rights in consideration of benefits the
+Licensor receives from making the Licensed Material available under
+these terms and conditions.
+
+
+Section 1 -- Definitions.
+
+ a. Adapted Material means material subject to Copyright and Similar
+ Rights that is derived from or based upon the Licensed Material
+ and in which the Licensed Material is translated, altered,
+ arranged, transformed, or otherwise modified in a manner requiring
+ permission under the Copyright and Similar Rights held by the
+ Licensor. For purposes of this Public License, where the Licensed
+ Material is a musical work, performance, or sound recording,
+ Adapted Material is always produced where the Licensed Material is
+ synched in timed relation with a moving image.
+
+ b. Copyright and Similar Rights means copyright and/or similar rights
+ closely related to copyright including, without limitation,
+ performance, broadcast, sound recording, and Sui Generis Database
+ Rights, without regard to how the rights are labeled or
+ categorized. For purposes of this Public License, the rights
+ specified in Section 2(b)(1)-(2) are not Copyright and Similar
+ Rights.
+
+ c. Effective Technological Measures means those measures that, in the
+ absence of proper authority, may not be circumvented under laws
+ fulfilling obligations under Article 11 of the WIPO Copyright
+ Treaty adopted on December 20, 1996, and/or similar international
+ agreements.
+
+ d. Exceptions and Limitations means fair use, fair dealing, and/or
+ any other exception or limitation to Copyright and Similar Rights
+ that applies to Your use of the Licensed Material.
+
+ e. Licensed Material means the artistic or literary work, database,
+ or other material to which the Licensor applied this Public
+ License.
+
+ f. Licensed Rights means the rights granted to You subject to the
+ terms and conditions of this Public License, which are limited to
+ all Copyright and Similar Rights that apply to Your use of the
+ Licensed Material and that the Licensor has authority to license.
+
+ g. Licensor means the individual(s) or entity(ies) granting rights
+ under this Public License.
+
+ h. NonCommercial means not primarily intended for or directed towards
+ commercial advantage or monetary compensation. For purposes of
+ this Public License, the exchange of the Licensed Material for
+ other material subject to Copyright and Similar Rights by digital
+ file-sharing or similar means is NonCommercial provided there is
+ no payment of monetary compensation in connection with the
+ exchange.
+
+ i. Share means to provide material to the public by any means or
+ process that requires permission under the Licensed Rights, such
+ as reproduction, public display, public performance, distribution,
+ dissemination, communication, or importation, and to make material
+ available to the public including in ways that members of the
+ public may access the material from a place and at a time
+ individually chosen by them.
+
+ j. Sui Generis Database Rights means rights other than copyright
+ resulting from Directive 96/9/EC of the European Parliament and of
+ the Council of 11 March 1996 on the legal protection of databases,
+ as amended and/or succeeded, as well as other essentially
+ equivalent rights anywhere in the world.
+
+ k. You means the individual or entity exercising the Licensed Rights
+ under this Public License. Your has a corresponding meaning.
+
+
+Section 2 -- Scope.
+
+ a. License grant.
+
+ 1. Subject to the terms and conditions of this Public License,
+ the Licensor hereby grants You a worldwide, royalty-free,
+ non-sublicensable, non-exclusive, irrevocable license to
+ exercise the Licensed Rights in the Licensed Material to:
+
+ a. reproduce and Share the Licensed Material, in whole or
+ in part, for NonCommercial purposes only; and
+
+ b. produce and reproduce, but not Share, Adapted Material
+ for NonCommercial purposes only.
+
+ 2. Exceptions and Limitations. For the avoidance of doubt, where
+ Exceptions and Limitations apply to Your use, this Public
+ License does not apply, and You do not need to comply with
+ its terms and conditions.
+
+ 3. Term. The term of this Public License is specified in Section
+ 6(a).
+
+ 4. Media and formats; technical modifications allowed. The
+ Licensor authorizes You to exercise the Licensed Rights in
+ all media and formats whether now known or hereafter created,
+ and to make technical modifications necessary to do so. The
+ Licensor waives and/or agrees not to assert any right or
+ authority to forbid You from making technical modifications
+ necessary to exercise the Licensed Rights, including
+ technical modifications necessary to circumvent Effective
+ Technological Measures. For purposes of this Public License,
+ simply making modifications authorized by this Section 2(a)
+ (4) never produces Adapted Material.
+
+ 5. Downstream recipients.
+
+ a. Offer from the Licensor -- Licensed Material. Every
+ recipient of the Licensed Material automatically
+ receives an offer from the Licensor to exercise the
+ Licensed Rights under the terms and conditions of this
+ Public License.
+
+ b. No downstream restrictions. You may not offer or impose
+ any additional or different terms or conditions on, or
+ apply any Effective Technological Measures to, the
+ Licensed Material if doing so restricts exercise of the
+ Licensed Rights by any recipient of the Licensed
+ Material.
+
+ 6. No endorsement. Nothing in this Public License constitutes or
+ may be construed as permission to assert or imply that You
+ are, or that Your use of the Licensed Material is, connected
+ with, or sponsored, endorsed, or granted official status by,
+ the Licensor or others designated to receive attribution as
+ provided in Section 3(a)(1)(A)(i).
+
+ b. Other rights.
+
+ 1. Moral rights, such as the right of integrity, are not
+ licensed under this Public License, nor are publicity,
+ privacy, and/or other similar personality rights; however, to
+ the extent possible, the Licensor waives and/or agrees not to
+ assert any such rights held by the Licensor to the limited
+ extent necessary to allow You to exercise the Licensed
+ Rights, but not otherwise.
+
+ 2. Patent and trademark rights are not licensed under this
+ Public License.
+
+ 3. To the extent possible, the Licensor waives any right to
+ collect royalties from You for the exercise of the Licensed
+ Rights, whether directly or through a collecting society
+ under any voluntary or waivable statutory or compulsory
+ licensing scheme. In all other cases the Licensor expressly
+ reserves any right to collect such royalties, including when
+ the Licensed Material is used other than for NonCommercial
+ purposes.
+
+
+Section 3 -- License Conditions.
+
+Your exercise of the Licensed Rights is expressly made subject to the
+following conditions.
+
+ a. Attribution.
+
+ 1. If You Share the Licensed Material, You must:
+
+ a. retain the following if it is supplied by the Licensor
+ with the Licensed Material:
+
+ i. identification of the creator(s) of the Licensed
+ Material and any others designated to receive
+ attribution, in any reasonable manner requested by
+ the Licensor (including by pseudonym if
+ designated);
+
+ ii. a copyright notice;
+
+ iii. a notice that refers to this Public License;
+
+ iv. a notice that refers to the disclaimer of
+ warranties;
+
+ v. a URI or hyperlink to the Licensed Material to the
+ extent reasonably practicable;
+
+ b. indicate if You modified the Licensed Material and
+ retain an indication of any previous modifications; and
+
+ c. indicate the Licensed Material is licensed under this
+ Public License, and include the text of, or the URI or
+ hyperlink to, this Public License.
+
+ For the avoidance of doubt, You do not have permission under
+ this Public License to Share Adapted Material.
+
+ 2. You may satisfy the conditions in Section 3(a)(1) in any
+ reasonable manner based on the medium, means, and context in
+ which You Share the Licensed Material. For example, it may be
+ reasonable to satisfy the conditions by providing a URI or
+ hyperlink to a resource that includes the required
+ information.
+
+ 3. If requested by the Licensor, You must remove any of the
+ information required by Section 3(a)(1)(A) to the extent
+ reasonably practicable.
+
+
+Section 4 -- Sui Generis Database Rights.
+
+Where the Licensed Rights include Sui Generis Database Rights that
+apply to Your use of the Licensed Material:
+
+ a. for the avoidance of doubt, Section 2(a)(1) grants You the right
+ to extract, reuse, reproduce, and Share all or a substantial
+ portion of the contents of the database for NonCommercial purposes
+ only and provided You do not Share Adapted Material;
+
+ b. if You include all or a substantial portion of the database
+ contents in a database in which You have Sui Generis Database
+ Rights, then the database in which You have Sui Generis Database
+ Rights (but not its individual contents) is Adapted Material; and
+
+ c. You must comply with the conditions in Section 3(a) if You Share
+ all or a substantial portion of the contents of the database.
+
+For the avoidance of doubt, this Section 4 supplements and does not
+replace Your obligations under this Public License where the Licensed
+Rights include other Copyright and Similar Rights.
+
+
+Section 5 -- Disclaimer of Warranties and Limitation of Liability.
+
+ a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
+ EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
+ AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
+ ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
+ IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
+ WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
+ ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
+ KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
+ ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
+
+ b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
+ TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
+ NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
+ INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
+ COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
+ USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
+ ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
+ DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
+ IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
+
+ c. The disclaimer of warranties and limitation of liability provided
+ above shall be interpreted in a manner that, to the extent
+ possible, most closely approximates an absolute disclaimer and
+ waiver of all liability.
+
+
+Section 6 -- Term and Termination.
+
+ a. This Public License applies for the term of the Copyright and
+ Similar Rights licensed here. However, if You fail to comply with
+ this Public License, then Your rights under this Public License
+ terminate automatically.
+
+ b. Where Your right to use the Licensed Material has terminated under
+ Section 6(a), it reinstates:
+
+ 1. automatically as of the date the violation is cured, provided
+ it is cured within 30 days of Your discovery of the
+ violation; or
+
+ 2. upon express reinstatement by the Licensor.
+
+ For the avoidance of doubt, this Section 6(b) does not affect any
+ right the Licensor may have to seek remedies for Your violations
+ of this Public License.
+
+ c. For the avoidance of doubt, the Licensor may also offer the
+ Licensed Material under separate terms or conditions or stop
+ distributing the Licensed Material at any time; however, doing so
+ will not terminate this Public License.
+
+ d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
+ License.
+
+
+Section 7 -- Other Terms and Conditions.
+
+ a. The Licensor shall not be bound by any additional or different
+ terms or conditions communicated by You unless expressly agreed.
+
+ b. Any arrangements, understandings, or agreements regarding the
+ Licensed Material not stated herein are separate from and
+ independent of the terms and conditions of this Public License.
+
+
+Section 8 -- Interpretation.
+
+ a. For the avoidance of doubt, this Public License does not, and
+ shall not be interpreted to, reduce, limit, restrict, or impose
+ conditions on any use of the Licensed Material that could lawfully
+ be made without permission under this Public License.
+
+ b. To the extent possible, if any provision of this Public License is
+ deemed unenforceable, it shall be automatically reformed to the
+ minimum extent necessary to make it enforceable. If the provision
+ cannot be reformed, it shall be severed from this Public License
+ without affecting the enforceability of the remaining terms and
+ conditions.
+
+ c. No term or condition of this Public License will be waived and no
+ failure to comply consented to unless expressly agreed to by the
+ Licensor.
+
+ d. Nothing in this Public License constitutes or may be interpreted
+ as a limitation upon, or waiver of, any privileges and immunities
+ that apply to the Licensor or You, including from the legal
+ processes of any jurisdiction or authority.
+
+=======================================================================
+
+Creative Commons is not a party to its public
+licenses. Notwithstanding, Creative Commons may elect to apply one of
+its public licenses to material it publishes and in those instances
+will be considered the “Licensor.” The text of the Creative Commons
+public licenses is dedicated to the public domain under the CC0 Public
+Domain Dedication. Except for the limited purpose of indicating that
+material is shared under a Creative Commons public license or as
+otherwise permitted by the Creative Commons policies published at
+creativecommons.org/policies, Creative Commons does not authorize the
+use of the trademark "Creative Commons" or any other trademark or logo
+of Creative Commons without its prior written consent including,
+without limitation, in connection with any unauthorized modifications
+to any of its public licenses or any other arrangements,
+understandings, or agreements concerning use of licensed material. For
+the avoidance of doubt, this paragraph does not form part of the
+public licenses.
+
+Creative Commons may be contacted at creativecommons.org.
diff --git a/script.module.python.koding.aio/README b/script.module.python.koding.aio/README
new file mode 100644
index 0000000..9c60483
--- /dev/null
+++ b/script.module.python.koding.aio/README
@@ -0,0 +1,3453 @@
+WHAT IS KODING.PY?
+=================
+This is a universal module which has an variety of functions to make life much easier for developers whilst giving them the opportunity to keep their code secure.
+
+
+IMPORTING KODING.PY
+===================
+
+addon.xml - just import as you would any other module, the following code would work:
+
+
+
+default.py (or whatever your initial opening py document is called) - all you need is to import koding.
+
+------------------------------------------------------------------------------------------
+
+A D D O N B A S E D F U N C T I O N S
+
+------------------------------------------------------------------------------------------
+
+ADDON GENRE DICTIONARY:
+Return a dictionary of add-ons which match a specific genre.
+
+ CODE: Addon_Genre([genre, custom_url])
+
+ AVAILABLE PARAMS:
+
+ genre - By default this is set to 'adult' which will return
+ a dictionary of all known adult add-ons. The genre details are pulled from the
+ Add-on Portal at noobsandnerds.com so you can use any of the supported genre tags
+ listed on this page: http://noobsandnerds.com/latest/?p=3762
+
+ custom_url - If you have your own custom url which returns a dictionary
+ of genres you can enter it here and use that rather than rely on NaN categorisation.
+
+ EXAMPLE CODE:
+ -------------
+ dialog.ok('ADD-ON GENRES','We will now list all known comedy based add-ons. If you have add-ons installed which you feel should be categorised as supplying comedy but they aren\'t then you can help tag them up correctly via the Add-on Portal at NaN.')
+ comedy_addons = koding.Addon_Genre(genre='comedy')
+ if comedy_addons:
+ my_return = 'LIST OF AVAILABLE COMEDY BASED ADD-ONS:\n\n'
+
+ # Convert the dictionary into a list:
+ comedy_addons = comedy_addons.items()
+ for item in comedy_addons:
+ my_return += '[COLOR=gold]Name:[/COLOR] %s | [COLOR=dodgerblue]ID:[/COLOR] %s\n' % (item[0],item[1])
+ koding.Text_Box('[COLOR gold]COMEDY ADD-ONS[/COLOR]',my_return)
+
+------------------------------------------------------------------------------------------
+
+ADD-ON ID FROM PATH:
+If you know the folder name of an add-on but want to find out the
+addon id (it may not necessarily be the same as folder name) then
+you can use this function. Even if the add-on isn't enabled on the
+system this will regex out the add-on id.
+
+ CODE: Get_Addon_ID(folder)
+
+ AVAILABLE PARAMS:
+
+ folder - This is folder name of the add-on. Just the name not the path.
+
+ EXAMPLE CODE:
+ -------------
+ dialog.ok('ABOUT','This function allows us to pass through a folder name found in the addons folder and it will return the real id. The vast majority of add-ons use the same folder name as id but there are exceptions. Let\'s check Python Koding...')
+ my_id = koding.Get_Addon_ID(folder='script.module.python.koding.aio')
+ dialog.ok('PYTHON KODING ID','The add-on id found for this folder folder is:','[COLOR=dodgerblue]%s[/COLOR]'%my_id)
+
+------------------------------------------------------------------------------------------
+
+ADD-ON INFO:
+Retrieve details about an add-on, lots of built-in values are available
+such as path, version, name etc.
+
+ CODE: Addon_Setting(id, [addon_id])
+
+ AVAILABLE PARAMS:
+
+ (*) id - This is the name of the id you want to retrieve.
+ The list of built in id's you can use (current as of 15th April 2017)
+ are: author, changelog, description, disclaimer, fanart, icon, id, name,
+ path, profile, stars, summary, type, version
+
+ addon_id - By default this will use your current add-on id but you
+ can access any add-on you want by entering an id in here.
+
+ EXAMPLE CODE:
+ -------------
+ dialog.ok('ADD-ON INFO','We will now try and pull name and version details for our current running add-on.')
+ version = koding.Addon_Info(id='version')
+ name = koding.Addon_Info(id='name')
+ dialog.ok('NAME AND VERSION','[COLOR=dodgerblue]Add-on Name:[/COLOR] %s' % name,'[COLOR=dodgerblue]Version:[/COLOR] %s' % version)
+
+------------------------------------------------------------------------------------------
+
+ADDON LISTS:
+Return a list of enabled or disabled add-ons found in the database.
+
+ CODE: Addon_List([enabled, inc_new])
+
+ AVAILABLE PARAMS:
+
+ enabled - By default this is set to True which means you'll
+ get a list of all the enabled add-ons found in addons*.db but
+ if you want a list of all the disabled ones just set this to
+ False.
+
+ inc_new - This will also add any new add-on folders found on
+ your system that aren't yet in the database (ie ones that have
+ been recently been manually extracted but not scanned in). By
+ default this is set to False.
+
+ EXAMPLE CODE:
+ -------------
+ enabled_list = Addon_List(enabled=True)
+ disabled_list = Addon_List(enabled=False)
+ my_return = ''
+
+ for item in enabled_list:
+ my_return += '[COLOR=lime]ENABLED:[/COLOR] %s\n' % item
+ for item in disabled_list:
+ my_return += '[COLOR=red]DISABLED:[/COLOR] %s\n' % item
+ koding.Text_Box('ADDON STATUS',my_return)
+
+------------------------------------------------------------------------------------------
+
+ADDON SERVICE
+Send through an add-on id, list of id's or leave as the default which is "all". This
+will loop through the list of add-ons and return the ones which are run as services.
+
+This enable/disable feature will comment out the service lines, and does not stop a running
+service or start a service. This is designed more for if you've manually extracted a new
+add-on into your system and it isn't yet enabled. Occasionally if the add-ons have dependencies
+which are run as services then trying to enable them can cause Kodi to freeze.
+
+ CODE: Addon_Service([addon,disable])
+
+ AVAILABLE PARAMS:
+
+ addons - By default this is set to "all" but if there's a sepcific set of add-ons you
+ want to disable the service for just send through the id's in the form of a list.
+
+ mode - By default this is set to 'list' meaning you'll get a return of add-on folders
+ which contain an instance of service in the add-on.xml. You can set this to "disable" to
+ comment out the instances of service and similarly when you need to re-enable you can use
+ "enable" and that will uncomment out the service item. Please note that by uncommenting
+ the service will not automatically start - you'll need to reload the profile for that.
+
+ skip_service - When running the enable or disable mode you can choose to add a list of
+ add-ons you'd like to skip the process for. Of course you may be thinking why would I send
+ through a list of addons I want the service enabled/disabled for but then I also add them
+ to the skip_service list to say DON'T enable/disable - it makes no sense?! Well you'd be
+ correct that doesn't make any sense as presumably you've already filtered out the add-ons
+ you don't want affected, this command is designed more for those who don't send through a
+ list of add-ons and instead use the default "all" value for the addons paramater. This
+ then makes it very easy to just skip a handful of add-on services and enable all others.
+
+ EXAMPLE CODE:
+ -------------
+ dialog.ok('CHECKING FOR SERVICES','We will now check for all add-ons installed which contain services')
+ service_addons = Addon_Service(mode='list')
+ my_text = 'List of add-ons running as a service:\n\n'
+ for item in service_addons:
+ my_text += item+'\n'
+ koding.Text_Box('[COLOR gold]SERVICE ADDONS[/COLOR]',my_text)
+
+------------------------------------------------------------------------------------------
+
+ADDON SETTINGS - RETRIEVE/SET VALUE:
+Change or retrieve an add-on setting.
+
+ CODE: Addon_Setting(setting, [value, addon_id])
+
+ AVAILABLE PARAMS:
+
+ (*) setting - This is the name of the setting you want to access, by
+ default this function will return the value but if you add the
+ value param shown below it will CHANGE the setting.
+
+ value - If set this will change the setting above to whatever value
+ is in here.
+
+ addon_id - By default this will use your current add-on id but you
+ can access any add-on you want by entering an id in here.
+
+ EXAMPLE CODE:
+ -------------
+ dialog.ok('ADDON SETTING','We will now try and pull the language settings for the YouTube add-on')
+ if os.path.exists(xbmc.translatePath('special://home/addons/plugin.video.youtube')):
+ my_setting = koding.Addon_Setting(setting='youtube.language',addon_id='plugin.video.youtube')
+ dialog.ok('YOUTUBE SETTING','[COLOR=dodgerblue]Setting name:[/COLOR] youtube.language','[COLOR=dodgerblue]Value:[/COLOR] %s' % my_setting)
+ else:
+ dialog.ok('YOUTUBE NOT INSTALLED','Sorry we cannot run this example as you don\'t have YouTube installed.')
+
+------------------------------------------------------------------------------------------
+
+ADDON SETTINGS - OPEN:
+By default this will open the current add-on settings but if you pass through an addon_id it will open the settings for that add-on.
+
+ CODE: Open_Settings([addon_id, focus, click, stop_script])
+
+ AVAILABLE PARAMS:
+
+ addon_id - This optional, it can be any any installed add-on id. If nothing is passed
+ through the current add-on settings will be opened.
+
+ focus - This is optional, if not set the settings will just open to the first item
+ in the list (normal behaviour). However if you want to open to a specific category and
+ setting then enter the number in here separated by a dot. So for example if we want to
+ focus on the 2nd category and 3rd setting in the list we'd send through focus='2.3'
+
+ click - If you want the focused item to automatically be clicked set this to True.
+
+ stop_script - By default this is set to True, as soon as the addon settings are opened
+ the current script will stop running. If you pass through as False then the script will
+ continue running in the background - opening settings does not pause a script, Kodi just
+ see's it as another window being opened.
+
+ EXAMPLE CODE:
+ -------------
+ youtube_path = xbmc.translatePath('special://home/addons/plugin.video.youtube')
+ if os.path.exists(youtube_path):
+ dialog.ok('YOUTUBE SETTINGS','We will now open the YouTube settings.','We will focus on category 2, setting 3 AND send a click.')
+ koding.Open_Settings(addon_id='plugin.video.youtube',focus='2.3',click=True,stop_script=True)
+ else:
+ dialog.ok('YOUTUBE NOT INSTALLED','We cannot run this example as it uses the YouTube add-on which has not been found on your system.')
+
+------------------------------------------------------------------------------------------
+
+ADDON TOGGLE:
+Send through either a list of add-on ids or one single add-on id.
+The add-ons sent through will then be added to the addons*.db
+and enabled or disabled (depending on state sent through).
+
+WARNING: If safe_mode is set to False this directly edits the
+addons*.db rather than using JSON-RPC. Although directly amending
+the db is a lot quicker there is no guarantee it won't cause
+severe problems in later versions of Kodi (this was created for v17).
+DO NOT set safe_mode to False unless you 100% understand the consequences!
+
+ CODE: Toggle_Addons([addon, enable, safe_mode, exclude_list, new_only, refresh])
+
+ AVAILABLE PARAMS:
+ (*) addon - This can be a list of addon ids, one single id or
+ 'all' to enable/disable all. If enabling all you can still use
+ the exclude_list for any you want excluded from this function.
+ enable - By default this is set to True, if you want to disable
+ the add-on(s) then set this to False.
+
+ safe_mode - By default this is set to True which means the add-ons
+ are enabled/disabled via JSON-RPC which is the method recommended by
+ the XBMC foundation. Setting this to False will result in a much
+ quicker function BUT there is no guarantee this will work on future
+ versions of Kodi and it may even cause corruption in future versions.
+ Setting to False is NOT recommended and you should ONLY use this if
+ you 100% understand the risks that you could break multiple setups.
+
+ exclude_list - Send through a list of any add-on id's you do not
+ want to be included in this command.
+
+ new_only - By default this is set to True so only newly extracted
+ add-on folders will be enabled/disabled. This means that any existing
+ add-ons which have deliberately been disabled by the end user are
+ not affected.
+
+ refresh - By default this is set to True, it will refresh the
+ current container and also force a local update on your add-ons db.
+
+ update_status - When running this function it needs to disable the
+ auto-update of add-ons by Kodi otherwise it risks crashing. This
+ update_status paramater is the state you want Kodi to revert back to
+ once the toggle of add-ons has completed. By default this is set to 0
+ which is auto-update. You can also choose 1 (notify of updates) or 2
+ (disable auto updates).
+
+ EXAMPLE CODE:
+ -------------
+ xbmc.executebuiltin('ActivateWindow(Videos, addons://sources/video/)')
+ xbmc.sleep(2000)
+ dialog.ok('DISABLE YOUTUBE','We will now disable YouTube (if installed)')
+ koding.Toggle_Addons(addon='plugin.video.youtube', enable=False, safe_mode=True, exclude_list=[], new_only=False)
+ koding.Refresh('container')
+ xbmc.sleep(2000)
+ dialog.ok('ENABLE YOUTUBE','When you click OK we will enable YouTube (if installed)')
+ koding.Toggle_Addons(addon='plugin.video.youtube', enable=True, safe_mode=True, exclude_list=[], new_only=False)
+ koding.Refresh('container')
+
+------------------------------------------------------------------------------------------
+
+ADULT TOGGLE:
+Remove/Enable a list of add-ons, these are put into a containment area until enabled again.
+
+CODE: Adult_Toggle(adult_list, [disable, update_status])
+
+AVAILABLE PARAMS:
+
+ (*) adult_list - A list containing all the add-ons you want to be disabled.
+
+ disable - By default this is set to true so any add-ons in the list sent
+ through will be disabled. Set to False if you want to enable the hidden add-ons.
+
+ update_status - When running this function it needs to disable the
+ auto-update of add-ons by Kodi otherwise it risks crashing. This
+ update_status paramater is the state you want Kodi to revert back to
+ once the toggle of add-ons has completed. By default this is set to 0
+ which is auto-update. You can also choose 1 (notify of updates) or 2
+ (disable auto updates).
+
+------------------------------------------------------------------------------------------
+
+CALLER(S) OF FUNCTION:
+Return the add-on id or path of the script which originally called
+your function. If it's been called through a number of add-ons/scripts
+you can grab a list of paths that have been called.
+
+ CODE: Caller(my_return)
+
+ AVAILABLE PARAMS:
+
+ my_return - By default this is set to 'addon', view the options below:
+
+ 'addon' : Return the add-on id of the add-on to call this function.
+
+ 'addons': Return a list of all add-on id's called to get to this function.
+
+ 'path' : Return the full path to the script which called this function.
+
+ 'paths' : Return a list of paths which have been called to get to this
+ final function.
+
+ EXAMPLE CODE:
+ -------------
+ my_addon = koding.Caller(my_return='addon')
+ my_addons = koding.Caller(my_return='addons')
+ my_path = koding.Caller(my_return='path')
+ my_paths = koding.Caller(my_return='paths')
+
+ dialog.ok('ADD-ON ID', 'Addon id you called this function from:','[COLOR=dodgerblue]%s[/COLOR]' % my_addon)
+ dialog.ok('SCRIPT PATH', 'Script which called this function:','[COLOR=dodgerblue]%s[/COLOR]' % my_path)
+
+ addon_list = 'Below is a list of add-on id\'s which have been called to get to this final piece of code:\n\n'
+ for item in my_addons:
+ addon_list += item+'\n'
+ koding.Text_Box('ADD-ON LIST', addon_list)
+ koding.Sleep_If_Window_Active(10147)
+ path_list = 'Below is a list of scripts which have been called to get to this final piece of code:\n\n'
+ for item in my_paths:
+ path_list += item+'\n'
+ koding.Text_Box('ADD-ON LIST', path_list)
+
+------------------------------------------------------------------------------------------
+
+CHECK REPO AVAILABILITY STATUS:
+This will check the status of repo and return True if the repo is online or False
+if it contains paths that are no longer accessible online.
+
+IMPORTANT: If you're running an old version of Kodi which uses the old Python 2.6
+(OSX and Android lower than Kodi 17 or a linux install with old Python installed on system)
+you will get a return of False on https links regardless of their real status. This is due
+to the fact Python 2.6 cannot access secure links. Any still using standard http links
+will return the correct results.
+
+ CODE: Check_Repo(repo, [show_busy, timeout])
+
+ AVAILABLE PARAMS:
+
+ (*) repo - This is the name of the folder the repository resides in.
+ You can either use the full path or just the folder name which in 99.99%
+ of cases is the add-on id. If only using the folder name DOUBLE check first as
+ there are a handful which have used a different folder name to the actual add-on id!
+
+ show_busy - By default this is set to True and a busy dialog will show during the check
+
+ timeout - By default this is set to 10 (seconds) - this is the maximum each request
+ to the repo url will take before timing out and returning False.
+
+ EXAMPLE CODE:
+ -------------
+ repo_status = Check_Repo('special://xbmc',show_busy=False,timeout=10)
+ if repo_status:
+ dialog.ok('REPO STATUS','The repository modules4all is: [COLOR=lime]ONLINE[/COLOR]')
+ else:
+ dialog.ok('REPO STATUS','The repository modules4all is: [COLOR=red]OFFLINE[/COLOR]')
+
+------------------------------------------------------------------------------------------
+
+DEFAULT SETTING:
+This will return the DEFAULT value for a setting (as set in resources/settings.xml)
+and optionally reset the current value back to this default. If you pass through
+the setting as blank it will return a dictionary of all default settings.
+
+ CODE: Default_Setting(setting, [addon_id, reset])
+
+ AVAILABLE PARAMS:
+
+ setting - The setting you want to retreive the value for.
+ Leave blank to return a dictionary of all settings
+
+ addon_id - This is optional, if not set it will use the current id.
+
+ reset - By default this is set to False but if set to true and it will
+ reset the current value to the default.
+
+ EXAMPLE CODE:
+ -------------
+ youtube_path = xbmc.translatePath('special://home/addons/plugin.video.youtube')
+ if os.path.exists(youtube_path):
+ my_value = koding.Default_Setting(setting='youtube.region', addon_id='plugin.video.youtube', reset=False)
+ dialog.ok('YOUTUBE SETTING','Below is a default setting for plugin.video.youtube:','Setting: [COLOR=dodgerblue]youtube.region[/COLOR]','Value: [COLOR=dodgerblue]%s[/COLOR]' % my_value)
+ else:
+ dialog.ok('YOUTUBE NOT INSTALLED','We cannot run this example as it uses the YouTube add-on which has not been found on your system.')
+
+------------------------------------------------------------------------------------------
+
+DELETE COOKIES:
+This will delete your cookies file.
+
+ CODE: Delete_Cookies([filename])
+
+ AVAILABLE PARAMS:
+
+ filename - By default this is set to the filename 'cookiejar'.
+ This is the default cookie filename which is created by the Open_URL
+ function but you can use any name you want and this function will
+ return True or False on whether or not it's successfully been removed.
+
+ EXAMPLE CODE:
+ -------------
+ Open_URL(url='http://google.com',cookiejar='google')
+ dialog.ok('GOOGLE COOKIES CREATED','We have just opened a page to google.com, if you check your addon_data folder for your add-on you should see a cookies folder and in there should be a cookie called "google". When you press OK this will be removed.')
+
+------------------------------------------------------------------------------------------
+
+DEPENDENCY CHECK:
+This will return a list of all dependencies required by an add-on.
+This information is grabbed directly from the currently installed addon.xml,
+an individual add-on id or a list of add-on id's.
+
+ CODE: Dependency_Check([addon_id, recursive])
+
+ AVAILABLE PARAMS:
+
+ addon_id - This is optional, if not set it will return a list of every
+ dependency required from all installed add-ons. If you only want to
+ return results of one particular add-on then send through the id.
+
+ recursive - By default this is set to False but if set to true and you
+ also send through an individual addon_id it will return all dependencies
+ required for that addon id AND the dependencies of the dependencies.
+
+ EXAMPLE CODE:
+ -------------
+ current_id = xbmcaddon.Addon().getAddonInfo('id')
+ dependencies = koding.Dependency_Check(addon_id=current_id, recursive=True)
+ clean_text = ''
+ for item in dependencies:
+ clean_text += item+'\n'
+ koding.Text_Box('Modules required for %s'%current_id,clean_text)
+
+------------------------------------------------------------------------------------------
+
+INSTALLED ADD-ON DETAILS:
+This will send back a list of currently installed add-ons on the system.
+All the three paramaters you can send through to this function are optional,
+by default (without any params) this function will return a dictionary of all
+installed add-ons. The dictionary will contain "addonid" and "type" e.g. 'xbmc.python.pluginsource'.
+
+ CODE: Installed_Addons([types, content, properties]):
+
+ AVAILABLE PARAMS:
+
+ types - If you only want to retrieve details for specific types of add-ons
+ then use this filter. Unfortunately only one type can be filtered at a time,
+ it is not yet possible to filter multiple types all in one go. Please check
+ the official wiki for the add-on types avaialble but here is an example if
+ you only wanted to show installed repositories: koding.Installed_Addons(types='xbmc.addon.repository')
+
+ content - Just as above unfortunately only one content type can be filtered
+ at a time, you can filter by video,audio,image and executable. If you want to
+ only return installed add-ons which appear in the video add-ons section you
+ would use this: koding.Installed_Addons(content='video')
+
+ properties - By default a dictionary containing "addonid" and "type" will be
+ returned for all found add-ons meeting your criteria. However you can add any
+ properties in here available in the add-on xml (check official Wiki for properties
+ available). Unlike the above two options you can choose to add multiple properties
+ to your dictionary, see example below:
+ koding.Installed_Addons(properties='name,thumbnail,description')
+
+
+ EXAMPLE CODE:
+ -------------
+ my_video_plugins = koding.Installed_Addons(types='xbmc.python.pluginsource', content='video', properties='name')
+ final_string = ''
+ for item in my_video_plugins:
+ final_string += 'ID: %s | Name: %s\n'%(item["addonid"], item["name"])
+ koding.Text_Box('LIST OF VIDEO PLUGINS',final_string)
+
+------------------------------------------------------------------------------------------
+
+SETTINGS - CREATE CUSTOM ADD-ON SETTINGS:
+All credit goes to OptimusGREEN for this module.
+
+This will create a new settings file for your add-on which you can read and write to. This is separate
+to the standard settings.xml and you can call the file whatever you want, however you would presumably
+call it something other than settings.xml as that is already used by Kodi add-ons.
+
+ CODE: XML(path)
+
+ AVAILABLE CLASSES:
+
+ ParseValue - This class will allow you to get the value of an item in these custom settings.
+
+ SetValue - This class allows you to set a value to the custom settings. If the settings.xml doesn't exist it will be automatically created so long as the path given in XML is writeable.
+
+
+ EXAMPLE CODE:
+ -------------
+ myXmlFile = "special://userdata/addon_data/script.module.python.koding.aio/timefile.xml"
+ timefile = koding.xml(myXmlFile)
+ getSetting = timefile.ParseValue
+ setSetting = timefile.SetValue
+ dialog.ok('CHECK SETTINGS','If you take a look in the addon_data folder for python koding a new file called timefile.xml will be created when you click OK.')
+ setSetting("autorun", "true")
+ autoRun = getSetting("autorun")
+ dialog.ok('AUTORUN VALUE','The value of autorun in these new settings is [COLOR dodgerblue]%s[/COLOR].[CR][CR]Press OK to delete this file.'%autoRun)
+ os.remove(koding.Physical_Path(myXmlFile))
+
+------------------------------------------------------------------------------------------
+
+TOGGLE ADD-ONS:
+Send through either a list of add-on ids or one single add-on id. The add-ons sent through will then be added
+to the addons*.db and enabled or disabled (depending on state sent through).
+
+WARNING: If safe_mode is set to False this directly edits the addons*.db rather than using JSON-RPC.
+Although directly amending the db is a lot quicker there is no guarantee it won't cause severe problems in
+later versions of Kodi (this was created for v17). DO NOT set safe_mode to False unless you 100% understand
+the consequences!
+
+ CODE: Toggle_Addons([addon, enable, safe_mode, exclude_list, new_only, refresh])
+
+ AVAILABLE PARAMS:
+
+ (*) addon - This can be a list of addon ids, one single id or
+ 'all' to enable/disable all. If enabling all you can still use
+ the exclude_list for any you want excluded from this function.
+
+ enable - By default this is set to True, if you want to disable
+ the add-on(s) then set this to False.
+
+ safe_mode - By default this is set to True which means the add-ons
+ are enabled/disabled via JSON-RPC which is the method recommended by
+ the XBMC foundation. Setting this to False will result in a much
+ quicker function BUT there is no guarantee this will work on future
+ versions of Kodi and it may even cause corruption in future versions.
+ Setting to False is NOT recommended and you should ONLY use this if
+ you 100% understand the risks that you could break multiple setups.
+
+ exclude_list - Send through a list of any add-on id's you do not
+ want to be included in this command.
+
+ new_only - By default this is set to True so only newly extracted
+ add-on folders will be enabled/disabled. This means that any existing
+ add-ons which have deliberately been disabled by the end user are
+ not affected.
+
+ refresh - By default this is set to True, it will refresh the
+ current container and also force a local update on your add-ons db.
+
+ EXAMPLE CODE:
+ -------------
+ xbmc.executebuiltin('ActivateWindow(Videos, addons://sources/video/)')
+ xbmc.sleep(2000)
+ dialog.ok('DISABLE YOUTUBE','We will now disable YouTube (if installed)')
+ koding.Toggle_Addons(addon='plugin.video.youtube', enable=False, safe_mode=True, exclude_list=[], new_only=False)
+ koding.Refresh('container')
+ xbmc.sleep(2000)
+ dialog.ok('ENABLE YOUTUBE','When you click OK we will enable YouTube (if installed)')
+ koding.Toggle_Addons(addon='plugin.video.youtube', enable=True, safe_mode=True, exclude_list=[], new_only=False)
+
+------------------------------------------------------------------------------------------
+
+A N D R O I D S P E C I F I C
+
+------------------------------------------------------------------------------------------
+
+ANDROID APP SETTINGS:
+Open up the settings for an installed Android app.
+
+ CODE: App_Settings(apk_id)
+
+ AVAILABLE PARAMS:
+
+ (*) apk_id - The id of the app you want to open the settings for.
+
+ EXAMPLE CODE:
+ -------------
+ my_apps = koding.My_Apps()
+ choice = dialog.select('CHOOSE AN APK', my_apps)
+ koding.App_Settings(apk_id=my_apps[choice])
+
+------------------------------------------------------------------------------------------
+
+INSTALLED APPS:
+Return a list of apk id's installed on system
+
+ CODE: My_Apps()
+
+ EXAMPLE CODE:
+ -------------
+ my_apps = koding.My_Apps()
+ choice = dialog.select('CHOOSE AN APK', my_apps)
+ if choice >= 0:
+ koding.App_Settings(apk_id=my_apps[choice])
+
+------------------------------------------------------------------------------------------
+
+START ANDROID APP:
+Open an Android application
+
+ CODE: Start_App(apk_id)
+
+ AVAILABLE PARAMS:
+
+ (*) apk_id - The id of the app you want to open.
+
+ EXAMPLE CODE:
+ -------------
+ dialog.ok('OPEN FACEBOOK','Presuming you have Facebook installed and this is an Android system we will now open that apk')
+ koding.Start_App(apk_id='com.facebook.katana')
+
+------------------------------------------------------------------------------------------
+
+UNINSTALL ANDROID APP
+Uninstall and Android app
+
+ CODE: Uninstall_APK(apk_id)
+
+ EXAMPLE CODE:
+ -------------
+ if dialog.yesno('UNINSTALL FACEBOOK','Would you like to uninstall the Facebook app from your system?'):
+ koding.Uninstall_APK(apk_id='com.facebook.katana')
+
+------------------------------------------------------------------------------------------
+
+D A T A B A S E S
+
+------------------------------------------------------------------------------------------
+
+A GENERIC QUERY:
+Open a database and either return an array of results with the SELECT SQL command or perform an action such as INSERT, UPDATE, CREATE.
+
+ CODE: DB_Query(db_path, query, [values])
+
+ AVAILABLE PARAMS:
+
+ (*) db_path - the full path to the database file you want to access.
+
+ (*) query - this is the actual db query you want to process, use question marks for values
+
+ values - a list of values, even if there's only one value it must be sent through as a list item.
+
+ IMPORTANT: Directly accessing databases which are outside of your add-ons domain is very much frowned
+ upon. If you need to access a built-in kodi database (as shown in example below) you should always use
+ the JSON-RPC commands where possible.
+
+ EXAMPLE CODE:
+ -------------
+ dbpath = filetools.DB_Path_Check('addons')
+ db_table = 'addon'
+ kodi_version = int(float(xbmc.getInfoLabel("System.BuildVersion")[:2]))
+ if kodi_version >= 17:
+ db_table = 'addons'
+ db_query = koding.DB_Query(db_path=dbpath, query='SELECT * FROM %s WHERE addonID LIKE ? AND addonID NOT LIKE ?'%db_table, values=['%youtube%','%script.module%'])
+ koding.Text_Box('DB SEARCH RESULTS',str(db_query))
+------------------------------------------------------------------------------------------
+
+ADD TO TABLE:
+Add a row to the table in /userdata/addon_data//database.db
+
+ CODE: Add_To_Table(table, spec)
+
+ AVAILABLE PARAMS:
+
+ (*) table - The table name you want to query
+
+ (*) spec - Sent through as a dictionary this is the colums and constraints.
+
+ abort_on_error - Default is set to False but set to True if you want to abort
+ the process when it hits an error.
+
+ EXAMPLE CODE:
+ -------------
+ create_specs = {"columns":{"name":"TEXT", "id":"TEXT"}}
+ koding.Create_Table("test_table", create_specs)
+ add_specs1 = {"name":"YouTube", "id":"plugin.video.youtube"}
+ add_specs2 = {"name":"vimeo","id":"plugin.video.vimeo"}
+ koding.Add_To_Table("test_table", add_specs1)
+ koding.Add_To_Table("test_table", add_specs2)
+ results = koding.Get_All_From_Table("test_table")
+ final_results = ''
+ for item in results:
+ final_results += 'ID: %s | Name: %s\n'%(item["id"], item["name"])
+ koding.Text_Box('DB RESULTS', final_results)
+ koding.Remove_Table('test_table')
+
+------------------------------------------------------------------------------------------
+
+ADD MULTIPLE ROWS TO TABLE:
+This will allow you to add multiple rows to a table in one big (fast) bulk command
+The db file is: /userdata/addon_data//database.db
+
+ CODE: Add_To_Table(table, spec)
+
+ AVAILABLE PARAMS:
+
+ (*) table - The table name you want to query
+
+ (*) keys - Send through a list of keys you want to add to
+
+ (*) values - A list of values you want to add, this needs to be
+ a list of lists (see example below)
+
+ EXAMPLE CODE:
+ -------------
+ create_specs = {"columns":{"name":"TEXT", "id":"TEXT"}}
+ koding.Create_Table("test_table", create_specs)
+ dialog.ok('ADD TO TABLE','Lets add the details of 3 add-ons to "test_table" in our database.')
+ mykeys = ["name","id"]
+ myvalues = [("YouTube","plugin.video.youtube"), ("vimeo","plugin.video.vimeo"), ("test2","plugin.video.test2")]
+ koding.Add_Multiple_To_Table(table="test_table", keys=mykeys, values=myvalues)
+ results = koding.Get_All_From_Table("test_table")
+ final_results = ''
+ for item in results:
+ final_results += 'ID: %s | Name: %s\n'%(item["id"], item["name"])
+ koding.Text_Box('DB RESULTS', 'Below are details of the items pulled from our db:\n\n%s'%final_results)
+ koding.Remove_Table('test_table')
+
+------------------------------------------------------------------------------------------
+
+CREATE A NEW TABLE:
+Create a new table in the database at /userdata/addon_data//database.db
+
+ CODE: Create_Table(table, spec)
+
+ AVAILABLE PARAMS:
+
+ (*) table - The table name you want to query
+
+ (*) spec - Sent through as a dictionary this is the colums and constraints.
+
+ EXAMPLE CODE:
+ -------------
+ create_specs = { "columns":{"name":"TEXT", "id":"TEXT"}, "constraints":{"unique":"id"} }
+ koding.Create_Table("test_table", create_specs)
+ dialog.ok('TABLE CREATED','A new table has been created in your database and the id column has been set as UNIQUE.')
+ my_specs = {"name":"YouTube", "id":"plugin.video.youtube"}
+ try:
+ koding.Add_To_Table("test_table", my_specs)
+ koding.Add_To_Table("test_table", my_specs)
+ except:
+ dialog.ok('FAILED TO ADD','Could not add duplicate items because the the column "id" is set to be UNIQUE')
+ results = koding.Get_All_From_Table("test_table")
+ final_results = ''
+ for item in results:
+ final_results += 'ID: %s | Name: %s\n'%(item["id"], item["name"])
+ koding.Text_Box('DB RESULTS', final_results)
+ koding.Remove_Table('test_table')
+
+------------------------------------------------------------------------------------------
+
+GET ALL RESULTS FROM A TABLE:
+Return a list of all entries from a specific table in /userdata/addon_data//database.db
+
+ CODE: Get_All_From_Table(table)
+
+ AVAILABLE PARAMS:
+
+ (*) table - The table name you want to query
+
+ EXAMPLE CODE:
+ -------------
+ create_specs = {"columns":{"name":"TEXT", "id":"TEXT"}}
+ koding.Create_Table("test_table", create_specs)
+ add_specs1 = {"name":"YouTube", "id":"plugin.video.youtube"}
+ add_specs2 = {"name":"vimeo","id":"plugin.video.vimeo"}
+ koding.Add_To_Table("test_table", add_specs1)
+ koding.Add_To_Table("test_table", add_specs2)
+ results = koding.Get_All_From_Table("test_table")
+ final_results = ''
+ for item in results:
+ final_results += 'ID: %s | Name: %s\n'%(item["id"], item["name"])
+ koding.Text_Box('DB RESULTS', final_results)
+ koding.Remove_Table('test_table')
+
+------------------------------------------------------------------------------------------
+
+GET SPECIFIC RESULTS FROM A TABLE:
+Return a list of all entries matching a specific criteria from the
+database stored at: /userdata/addon_data//database.db
+
+ CODE: Get_From_Table(table, spec, compare_operator)
+
+ AVAILABLE PARAMS:
+
+ (*) table - The table name you want to query
+
+ spec - This is the query value, sent through as a dictionary.
+
+ default_compare_operator - By default this is set to '=' but could be any
+ other SQL query string such as 'LIKE', 'NOT LIKE', '!=' etc.
+
+ EXAMPLE CODE:
+ -------------
+ create_specs = {"columns":{"name":"TEXT", "id":"TEXT"}}
+ koding.Create_Table("test_table", create_specs)
+ add_specs1 = {"name":"YouTube", "id":"plugin.video.youtube"}
+ add_specs2 = {"name":"vimeo","id":"plugin.video.vimeo"}
+ koding.Add_To_Table("test_table", add_specs1)
+ koding.Add_To_Table("test_table", add_specs2)
+ results = koding.Get_From_Table(table="test_table", spec={"name":"%vim%"}, default_compare_operator="LIKE")
+ final_results = ''
+ for item in results:
+ final_results += 'ID: %s | Name: %s\n'%(item["id"], item["name"])
+ koding.Text_Box('DB CONTENTS', final_results)
+ koding.Remove_Table('test_table')
+
+------------------------------------------------------------------------------------------
+
+REMOVE ROW FROM TABLE:
+Remove entries in the db table at /userdata/addon_data//database.db
+
+ CODE: Remove_From_Table(table, spec, [compare_operator])
+
+ AVAILABLE PARAMS:
+
+ (*) table - The table name you want to query
+
+ spec - This is the query value, sent through as a dictionary.
+
+ default_compare_operator - By default this is set to '=' but could be any
+ other SQL query string such as 'LIKE', 'NOT LIKE', '!=' etc.
+
+ EXAMPLE CODE:
+ -------------
+ create_specs = {"columns":{"name":"TEXT", "id":"TEXT"}}
+ koding.Create_Table(table="test_table", spec=create_specs)
+ add_specs1 = {"name":"YouTube", "id":"plugin.video.youtube"}
+ add_specs2 = {"name":"vimeo","id":"plugin.video.vimeo"}
+ koding.Add_To_Table(table="test_table", spec=add_specs1)
+ koding.Add_To_Table(table="test_table", spec=add_specs2)
+ results = koding.Get_All_From_Table(table="test_table")
+ final_results = ''
+ for item in results:
+ final_results += 'ID: %s | Name: %s\n'%(item["id"], item["name"])
+ koding.Text_Box('DB CONTENTS', final_results)
+ dialog.ok('REMOVE ITEM','We will now remove vimeo from the table, lets see if it worked...')
+ koding.Remove_From_Table(table="test_table", spec={"name":"vimeo"})
+ results = koding.Get_All_From_Table(table="test_table")
+ final_results = ''
+ for item in results:
+ final_results += 'ID: %s | Name: %s\n'%(item["id"], item["name"])
+ koding.Text_Box('NEW DB CONTENTS', final_results)
+ koding.Remove_Table('test_table')
+
+------------------------------------------------------------------------------------------
+
+REMOVE TABLE:
+Use with caution, this will completely remove a database table and
+all of it's contents. The only database you can access with this command
+is your add-ons own db file called database.db
+
+ CODE: Remove_Table(table)
+
+ AVAILABLE PARAMS:
+
+ (*) table - This is the name of the table you want to permanently delete.
+
+ EXAMPLE CODE:
+ -------------
+ dialog.ok('REMOVE TABLE','It\'s a bit pointless doing this as you can\'t physically see what\'s happening so you\'ll just have to take our word it works!')
+ koding.Remove_Table('test_table')
+
+------------------------------------------------------------------------------------------
+
+D I A L O G S
+
+------------------------------------------------------------------------------------------
+
+BROWSE TO A FILE AND RETURN PATH:
+This will allow the user to browse to a specific file and return the path.
+
+IMPORTANT: Do not confuse this with the Browse_To_Folder function
+
+ CODE: koding.Browse_To_File([header, path, extension, browse_in_archives])
+
+ AVAILABLE PARAMS:
+
+ header - As the name suggests this is a string to be used for the header/title
+ of the window. The default is "Select the file you want to use".
+
+ path - Optionally you can add a default path for the browse start folder.
+ The default start position is the Kodi HOME folder.
+
+ extension - Optionally set extensions to filter by, let's say you only wanted
+ zip and txt files to show you would send through '.zip|.txt'
+
+ browse_in_archives - Set to true if you want to be able to browse inside zips and
+ other archive files. By default this is set to False.
+
+ EXAMPLE CODE:
+ -------------
+ dialog.ok('[COLOR gold]BROWSE TO FILE 1[/COLOR]','We will now browse to your addons folder with browse_in_archives set to [COLOR dodgerblue]False[/COLOR]. Try clicking on a zip file if you can find one (check packages folder).')
+ folder = koding.Browse_To_File(header='Choose a file you want to use', path='special://home/addons')
+ dialog.ok('FOLDER DETAILS','File path: [COLOR=dodgerblue]%s[/COLOR]'%folder)
+ dialog.ok('[COLOR gold]BROWSE TO FILE 2[/COLOR]','We will now browse to your addons folder with browse_in_archives set to [COLOR dodgerblue]True[/COLOR]. Try clicking on a zip file if you can find one (check packages folder).')
+ folder = koding.Browse_To_File(header='Choose a file you want to use', path='special://home/addons', browse_in_archives=True)
+ dialog.ok('FOLDER DETAILS','File path: [COLOR=dodgerblue]%s[/COLOR]'%folder)
+
+------------------------------------------------------------------------------------------
+
+BROWSE TO A FOLDER AND RETURN PATH:
+As the title suggests this will bring up a dialog that allows the user to browse to a folder
+and the path is then returned.
+
+IMPORTANT: Do not confuse this with the Browse_To_File function
+
+ CODE: Browse_To_Folder(header, path)
+
+ AVAILABLE PARAMS:
+ header - As the name suggests this is a string to be used for the header/title
+ of the window. The default is "Select the folder you want to use".
+
+ path - Optionally you can add a default path for the browse start folder.
+ The default start position is the Kodi HOME folder.
+
+
+ EXAMPLE CODE:
+ -------------
+ folder = koding.Browse_To_Folder(header='Choose a folder you want to use', path='special://home/userdata')
+ dialog.ok('FOLDER DETAILS','Folder path: [COLOR=dodgerblue]%s[/COLOR]'%folder)
+
+------------------------------------------------------------------------------------------
+
+COUNTDOWN TIMER:
+Bring up a countdown timer and return true if waited or false if cancelled.
+
+ CODE: Countdown(title, message, update_msg, wait_time, allow_cancel, cancel_msg):
+
+ AVAILABLE PARAMS:
+
+ title - The header string in the dialog window, the default is:
+ 'COUNTDOWN STARTED'
+
+ message - A short line of info which will show on the first line
+ of the dialog window just below the title. Default is:
+ 'A quick simple countdown example.'
+
+ update_msg - The message you want to update during the countdown.
+ This must contain a %s which will be replaced by the current amount
+ of seconds that have passed. The default is:
+ 'Please wait, %s seconds remaining.'
+
+ wait_time - This is the amount of seconds you want the countdown to
+ run for. The default is 10.
+
+ allow_cancel - By default this is set to true and the user can cancel
+ which will result in False being returned. If this is set to True
+ they will be unable to cancel.
+
+ cancel_msg - If allow_cancel is set to False you can add a custom
+ message when the user tries to cancel. The default string is:
+ '[COLOR=gold]Sorry, this process cannot be cancelled[/COLOR]'
+
+ EXAMPLE CODE:
+ -------------
+ dialog.ok('COUNTDOWN EXAMPLE', 'Press OK to bring up a countdown timer', '', 'Try cancelling the process.')
+ my_return = koding.Countdown(title='COUNTDOWN EXAMPLE', message='Quick simple countdown message (cancel enabled).', update_msg='%s seconds remaining', wait_time=5)
+ if my_return:
+ dialog.ok('SUCCESS!','Congratulations you actually waited through the countdown timer without cancelling!')
+ else:
+ dialog.ok('BORED MUCH?','What happened, did you get bored waiting?', '', '[COLOR=dodgerblue]Let\'s set off another countdown you CANNOT cancel...[/COLOR]')
+ koding.Countdown(title='COUNTDOWN EXAMPLE', message='Quick simple countdown message (cancel disabled).', update_msg='%s seconds remaining', wait_time=5, allow_cancel=False, cancel_msg='[COLOR=gold]Sorry, this process cannot be cancelled[/COLOR]')
+
+------------------------------------------------------------------------------------------
+
+CUSTOM DIALOG:
+A fully customisable dialog where you can have as many buttons as you want.
+Similar behaviour to the standard Kodi yesno dialog but this allows as many buttons
+as you want, as much text as you want (with a slider) as well as fully configurable
+sizing and positioning.
+
+ CODE: Custom_Dialog([pos, dialog, size, button_width, header, main_content, buttons,\
+ header_color, text_color, background, transparency, highlight_color, button_color_focused,\
+ button_trans_focused, button_color_nonfocused, button_trans_nonfocused])
+
+ AVAILABLE PARAMS:
+
+ pos - This is the co-ordinates of where on the screen you want the
+ dialog to appear. This needs to be sent through as a string so for
+ example if you want the dialog top left corner to be 20px in and
+ 10px down you would use pos='20x10'. By default this is set to 'center'
+ which will center the dialog on the screen.
+
+ dialog - By default this is set to 'Text'. Currently that is the
+ only custom dialog available but there are plans to improve upon this
+ and allow for image and even video dialogs.
+
+ size - Sent through as a string this is the dimensions you want the
+ dialog to be, by default it's set to '700x500' but you can set to any
+ size you want using that same format. Setting to 'fullscreen' will
+ use 1280x720 (fullscreen).
+
+ button_width - This is sent through as an integer and is the width you
+ want your buttons to be. By default this is set to 200 which is quite large
+ but looks quite nice if using only 2 or 3 buttons.
+
+ header - Sent through as a string this is the header shown in the dialog.
+ The default is 'Disclaimer'.
+
+ header_color - Set the text colour, by default it's 'gold'
+
+ text_color - Set the text colour, by default it's 'white'
+
+ main_content - This is sent through as a string and is the main message text
+ you want to show in your dialog. When the ability to add videos, images etc.
+ is added there may well be new options added to this param but it will remain
+ backwards compatible.
+
+ buttons - Sent through as a list (tuple) this is a list of all your buttons.
+ Make sure you do not duplicate any names otherwise it will throw off the
+ formatting of the dialog and you'll get false positives with the results.
+
+ background - Optionally set the background colour (hex colour codes required).
+ The default is '000000' (black).
+
+ transparency - Set the percentage of transparency as an integer. By default
+ it's set to 100 which is a solid colour.
+
+ highlight_color - Set the highlighted text colour, by default it's 'gold'
+
+ button_color_focused - Using the same format as background you can set the
+ colour to use for a button when it's focused.
+
+ button_trans_focused - Using the same format as transparency you can set the
+ transparency amount to use on the button when in focus.
+
+ button_color_nonfocused - Using the same format as background you can set the
+ colour to use for buttons when they are not in focus.
+
+ button_trans_nonfocused - Using the same format as transparency you can set the
+ transparency amount to use on the buttons when not in focus.
+
+ EXAMPLE CODE:
+ -------------
+ main_text = 'This is my main text.\n\nYou can add anything you want in here and the slider will allow you to see all the contents.\n\nThis example shows using a blue background colour and a transparency of 90%.\n\nWe have also changed the highlighted_color to yellow.'
+ my_buttons = ['button 1', 'button 2', 'button 3']
+ my_choice = koding.Custom_Dialog(main_content=main_text,pos='center',buttons=my_buttons,background='213749',transparency=90,highlight_color='yellow')
+ dialog.ok('CUSTOM DIALOG 1','You selected option %s'%my_choice,'The value of this is: [COLOR=dodgerblue]%s[/COLOR]'%my_buttons[my_choice])
+
+ main_text = 'This is example 2 with no fancy colours, just a fullscreen and a working scrollbar.\n\nYou\'ll notice there are also a few more buttons on this one.\n\nline 1\nline 2\nline 3\nline 4\nline 5\nline 6\nline 7\nline 8\nline 9\nline 10\nline 11\nline 12\nline 13\nline 14\nline 15\nline 16\nline 17\nline 18\nline 19\nline 20\n\nYou get the idea we\'ll stop there!'
+ my_buttons = ['button 1', 'button 2', 'button 3','button 4', 'button 5', 'button 6','button 7', 'button 8', 'button 9','button 10', 'button 11', 'button 12', 'button 13','button 14', 'button 15', 'button 16','button 17', 'button 18', 'button 19','button 20']
+ my_choice = koding.Custom_Dialog(main_content=main_text,pos='center',size='fullscreen',buttons=my_buttons)
+ dialog.ok('CUSTOM DIALOG 2','You selected option %s'%my_choice,'The value of this is: [COLOR=dodgerblue]%s[/COLOR]'%my_buttons[my_choice])
+
+------------------------------------------------------------------------------------------
+
+ENABLE/DISABLE THE BUSY (WORKING) SYMBOL:
+This will show/hide a "working" symbol.
+
+ CODE: Show_Busy([status, sleep])
+
+ AVAILABLE PARAMS:
+
+ status - This optional, by default it's True which means the "working"
+ symbol appears. False will disable.
+
+ sleep - If set the busy symbol will appear for amount of
+ milliseconds and then disappear.
+
+ EXAMPLE CODE:
+ -------------
+ dialog.ok('BUSY SYMBOL','Press OK to show a busy dialog which restricts any user interaction. We have added a sleep of 5 seconds at which point it will disable.')
+ koding.Show_Busy(sleep=5000)
+ dialog.ok('BUSY SYMBOL','We will now do the same but with slightly different code')
+ koding.Show_Busy(status=True)
+ xbmc.sleep(5000)
+ koding.Show_Busy(status=False)
+
+------------------------------------------------------------------------------------------
+
+KEYBOARD:
+Show an on-screen keyboard and return the string
+
+ CODE: koding.Keyboard([default, heading, hidden, return_false, autoclose, kb_type])
+
+ AVAILABLE PARAMS:
+
+ heading - Optionally enter a heading for the text box.
+
+ default - This is optional, if set this will act as the default text shown in the text box
+
+ hidden - Boolean, if set to True the text will appear as hidden (starred out)
+
+ return_false - By default this is set to False and when escaping out of the keyboard
+ the default text is returned (or an empty string if not set). If set to True then
+ you'll receive a return of False.
+
+ autoclose - By default this is set to False but if you want the keyboard to auto-close
+ after a period of time you can send through an integer. The value sent through needs to
+ be milliseconds, so for example if you want it to close after 3 seconds you'd send through
+ 3000. The autoclose function only works with standard alphanumeric keyboard types.
+
+ kb_type - This is the type of keyboard you want to show, by default it's set to alphanum.
+ A list of available values are listed below:
+
+ 'alphanum' - A standard on-screen keyboard containing alphanumeric characters.
+ 'numeric' - An on-screen numerical pad.
+ 'date' - An on-screen numerical pad formatted only for a date.
+ 'time' - An on-screen numerical pad formatted only for a time.
+ 'ipaddress' - An on-screen numerical pad formatted only for an IP Address.
+ 'password' - A standard keyboard but returns value as md5 hash. When typing
+ the text is starred out, once you've entered the password you'll get another
+ keyboard pop up asking you to verify. If the 2 match then your md5 has is returned.
+
+
+ EXAMPLE CODE:
+ -------------
+ mytext = koding.Keyboard(heading='Type in the text you want returned',default='test text')
+ dialog.ok('TEXT RETURNED','You typed in:', '', '[COLOR=dodgerblue]%s[/COLOR]'%mytext)
+ dialog.ok('AUTOCLOSE ENABLED','This following example we\'ve set the autoclose to 3000. That\'s milliseconds which converts to 3 seconds.')
+ mytext = koding.Keyboard(heading='Type in the text you want returned',default='this will close in 3s',autoclose=3000)
+ dialog.ok('TEXT RETURNED','You typed in:', '', '[COLOR=dodgerblue]%s[/COLOR]'%mytext)
+ mytext = koding.Keyboard(heading='Enter a number',kb_type='numeric')
+ dialog.ok('NUMBER RETURNED','You typed in:', '', '[COLOR=dodgerblue]%s[/COLOR]'%mytext)
+ dialog.ok('RETURN FALSE ENABLED','All of the following examples have "return_false" enabled. This means if you escape out of the keyboard the return will be False.')
+ mytext = koding.Keyboard(heading='Enter a date',return_false=True,kb_type='date')
+ dialog.ok('DATE RETURNED','You typed in:', '', '[COLOR=dodgerblue]%s[/COLOR]'%mytext)
+ mytext = koding.Keyboard(heading='Enter a time',return_false=True,kb_type='time')
+ dialog.ok('TIME RETURNED','You typed in:', '', '[COLOR=dodgerblue]%s[/COLOR]'%mytext)
+ mytext = koding.Keyboard(heading='IP Address',return_false=True,kb_type='ipaddress',autoclose=5)
+ dialog.ok('IP RETURNED','You typed in:', '', '[COLOR=dodgerblue]%s[/COLOR]'%mytext)
+ mytext = koding.Keyboard(heading='Password',kb_type='password')
+ dialog.ok('MD5 RETURN','The md5 for this password is:', '', '[COLOR=dodgerblue]%s[/COLOR]'%mytext)
+
+------------------------------------------------------------------------------------------
+
+NOTIFICATION POPUP:
+Show a short notification for x amount of seconds
+
+ CODE: koding.Notify(title, message, [duration, icon])
+
+ AVAILABLE PARAMS:
+
+ (*) title - A short title to show on top line of notification
+
+ (*) message - A short message to show on the bottom line of notification
+
+ duration - An integer in milliseconds, the default to show the notification for is 2000
+
+ icon - The icon to show in notification bar, default is the update icon from this module.
+
+ EXAMPLE CODE:
+ -------------
+ koding.Notify(title='TEST NOTIFICATION', message='This is a quick 5 second test', duration=5000)
+
+------------------------------------------------------------------------------------------
+
+OK DIALOG:
+This will bring up a short text message in a dialog.ok window.
+
+ CODE: OK_Dialog(title,message)
+
+ AVAILABLE PARAMS:
+
+ (*) title - This is title which appears in the header of the window.
+
+ (*) message - This is the main text you want to appear.
+
+ EXAMPLE CODE:
+ -------------
+ koding.OK_Dialog(title='TEST DIALOG',message='This is a test dialog ok box. Click OK to quit.')
+
+------------------------------------------------------------------------------------------
+
+PERCENTAGE RESET (CUSTOM DIALOGS):
+If using the Update_Progress function for setting percentages in skinning then this
+will allow you to reset all the percent properties (1-100)
+
+ CODE: Reset_Percent([property,window_id])
+
+ AVAILABLE PARAMS:
+
+ property - the property name you want reset, this will reset all properties starting
+ with this string from 1-100. For example if you use the default 'update_percent_' this
+ will loop through and reset update_percent_1, update_percent_2 etc. all the way through
+ to update_percent_100.
+
+ window_id - By default this is set to 10000 but you can send any id through you want.
+
+ kwargs - Send through any other params and the respective property will be set.colours etc.')
+
+------------------------------------------------------------------------------------------
+
+SELECTION DIALOG
+This will bring up a selection of options to choose from. The options are
+sent through as a list and only one can be selected - this is not a multi-select dialog.
+
+ CODE: Select_Dialog(title,options,[key])
+
+ AVAILABLE PARAMS:
+
+ (*) title - This is title which appears in the header of the window.
+
+ (*) options - This is a list of the options you want the user to be able to choose from.
+
+ key - By default this is set to True so you'll get a return of the item number. For example
+ if the user picks "option 2" and that is the second item in the list you'll receive a return of
+ 1 (0 would be the first item in list and 1 is the second). If set to False you'll recieve a return
+ of the actual string associated with that key, in this example the return would be "option 2".
+
+ EXAMPLE CODE:
+ -------------
+ my_options = ['Option 1','Option 2','Option 3','Option 4','Option 5']
+ mychoice = koding.Select_Dialog(title='TEST DIALOG',options=my_options,key=False)
+ koding.OK_Dialog(title='SELECTED ITEM',message='You selected: [COLOR=dodgerblue]%s[/COLOR]\nNow let\'s try again - this time we will return a key...'%mychoice)
+ mychoice = koding.Select_Dialog(title='TEST DIALOG',options=my_options,key=True)
+ koding.OK_Dialog(title='SELECTED ITEM',message='The item you selected was position number [COLOR=dodgerblue]%s[/COLOR] in the list'%mychoice)
+
+------------------------------------------------------------------------------------------
+
+TEXT BOX (LARGE WINDOW OF TEXT):
+This will allow you to open a blank window and fill it with some text.
+
+ CODE: koding.Text_Box(header, message)
+
+ AVAILABLE PARAMS:
+
+ (*) header - As the name suggests this is a string to be used for the header/title of the window
+
+ (*) message - Yes you've probably already gussed it, this is the main message text
+
+
+ EXAMPLE CODE:
+ -------------
+ koding.Text_Box('TEST HEADER','Just some random text... Use kodi tags for new lines, colours etc.')
+
+------------------------------------------------------------------------------------------
+
+UPDATE PROGRESS:
+This function is designed for skinners but can be used for general Python too. It will
+work out the current percentage of items that have been processed and update the
+"update_percent" property accordingly (1-100). You can also send through any properties
+you want updated and it will loop through updating them with the relevant values.
+
+To send through properties just send through the property name as the param and assign to a value.
+Example: Update_Progress( total_items=100,current_item=56, {"myproperty1":"test1","myproperty2":"test2"} )
+
+
+ CODE: Update_Progress(total_items,current_item,[kwargs])
+
+ AVAILABLE PARAMS:
+
+ (*) total_items - Total amount of items in your list you're processing
+
+ (*) current_item - Current item number that's been processed.
+
+ kwargs - Send through any other params and the respective property will be set.colours etc.
+
+------------------------------------------------------------------------------------------
+
+UPDATE SCREEN:
+This will create a full screen overlay showing progress of updates. You'll need to
+use this in conjunction with the Update_Progress function.
+
+ CODE: Update_Screen([disable_quit, auto_close))
+
+ AVAILABLE PARAMS:
+
+ disable_quit - By default this is set to False and pressing the parent directory
+ button (generally esc) will allow you to close the window. Setting this to True
+ will mean it's not possible to close the window manually.
+
+ auto_close - By default this is set to true and when the percentage hits 100
+ the window will close. If you intend on then sending through some more commands
+ you might want to consider leaving this window open in which case you'd set this
+ to false. Bare in mind if you go this route the window will stay active until
+ you send through the kill command which is: xbmc.executebuiltin('Action(firstpage)')
+
+ EXAMPLE CODE:
+ -------------
+ mykwargs = {
+ "update_header" : "Downloading latest updates",\
+ "update_main_text" : "Your device is now downloading all the latest updates.\nThis shouldn\'t take too long, "\
+ "depending on your internet speed this could take anything from 2 to 10 minutes.\n\n"\
+ "Once downloaded the system will start to install the updates.",\
+ "update_bar_color" : "4e91cf",\
+ "update_icon" : "special://home/addons/script.module.python.koding.aio/resources/skins/Default/media/update.png",\
+ "update_spinner" : "true"}
+ Update_Screen()
+ counter = 1
+ while counter <= 60:
+ xbmc.sleep(300)
+ Update_Progress(total_items=60,current_item=counter,**mykwargs)
+ if counter == 30:
+ mykwargs = {
+ "update_header" : "Halfway there!",\
+ "update_main_text" : "We just updated the properties to show how you can change things on the fly "\
+ "simply by sending through some different properties. Both the icon and the "\
+ "background images you see here are being pulled from online.",\
+ "update_header_color" : "4e91cf",\
+ "update_percent_color" : "4e91cf",\
+ "update_bar_color" : "4e91cf",\
+ "update_background" : "http://www.planwallpaper.com/static/images/518164-backgrounds.jpg",\
+ "update_icon" : "http://totalrevolution.tv/img/tr_small_black_bg.jpg",\
+ "update_spinner" : "false"}
+ counter += 1
+
+------------------------------------------------------------------------------------------
+
+YES/NO DIALOG:
+This will bring up a short text message in a dialog.yesno window. This will
+return True or False
+
+ CODE: YesNo_Dialog(title,message,[yeslabel,nolabel])
+
+ AVAILABLE PARAMS:
+
+ (*) title - This is title which appears in the header of the window.
+
+ (*) message - This is the main text you want to appear.
+
+ yes - Optionally change the default "YES" to a custom string
+
+ no - Optionally change the default "NO" to a custom string
+
+ EXAMPLE CODE:
+ -------------
+ mychoice = koding.YesNo_Dialog(title='TEST DIALOG',message='This is a yes/no dialog with custom labels.\nDo you want to see an example of a standard yes/no.',yes='Go on then',no='Nooooo!')
+ if mychoice:
+ koding.YesNo_Dialog(title='STANDARD DIALOG',message='This is an example of a standard one without sending custom yes/no params through.')
+
+------------------------------------------------------------------------------------------
+
+D I R E C T O R Y F U N C T I O N S
+
+------------------------------------------------------------------------------------------
+
+ADD DIRECTORY ITEM:
+This allows you to create a list item/folder inside your add-on.
+Please take a look at your addon default.py comments for more information
+(presuming you created one at http://totalrevolution.tv)
+
+TOP TIP: If you want to send multiple variables through to a function just
+send through as a dictionary encapsulated in quotation marks. In the function
+you can then use the following code to access them:
+
+params = eval(url)
+^ That will then give you a dictionary where you can just pull each variable and value from.
+
+ CODE: Add_Dir(name, url, mode, [folder, icon, fanart, description, info_labels, content_type, context_items, context_override, playable])
+
+ AVAILABLE PARAMS:
+
+ (*) name - This is the name you want to show for the list item
+
+ url - If the route (mode) you're calling requires extra paramaters
+ to be sent through then this is where you add them. If the function is
+ only expecting one item then you can send through as a simple string.
+ Unlike many other Add_Dir functions Python Koding does allow for multiple
+ params to be sent through in the form of a dictionary so let's say your
+ function is expecting the 2 params my_time & my_date. You would send this info
+ through as a dictionary like this:
+ url={'my_time':'10:00', 'my_date':'01.01.1970'}
+
+ If you send through a url starting with plugin:// the item will open up into
+ that plugin path so for example:
+ url='plugin://plugin.video.youtube/play/?video_id=FTI16i7APhU'
+
+ mode - The mode you want to open when this item is clicked, this is set
+ in your master_modes dictionary (see template add-on linked above)
+
+ folder - This is an optional boolean, by default it's set to False.
+ True will open into a folder rather than an executable command
+
+ icon - The path to the thumbnail you want to use for this list item
+
+ fanart - The path to the fanart you want to use for this list item
+
+ description - A description of your list item, it's skin dependant but this
+ usually appears below the thumbnail
+
+ info_labels - You can send through any number of info_labels via this option.
+ For full details on the infolabels available please check the pydocs here:
+ http://mirrors.kodi.tv/docs/python-docs/16.x-jarvis/xbmcgui.html#ListItem-setInfo
+
+ When passing through infolabels you need to use a dictionary in this format:
+ {"genre":"comedy", "title":"test video"}
+
+ set_art - Using the same format as info_labels you can set your artwork via
+ a dictionary here. Full details can be found here:
+ http://mirrors.kodi.tv/docs/python-docs/16.x-jarvis/xbmcgui.html#ListItem-setArt
+
+ set_property - Using the same format as info_labels you can set your artwork via
+ a dictionary here. Full details can be found here:
+ http://kodi.wiki/view/InfoLabels#ListItem
+
+ content_type - By default this will set the content_type for kodi to a blank string
+ which is what Kodi expects for generic category listings. There are plenty of different
+ types though and when set Kodi will perform different actions (such as access the
+ database looking for season/episode information for the list item).
+
+ WARNING: Setting the wrong content type for your listing can cause the system to
+ log thousands of error reports in your log, cause the system to lag and make
+ thousands of unnecessary db calls - sometimes resulting in a crash. You can find
+ details on the content_types available here: http://forum.kodi.tv/showthread.php?tid=299107
+
+ context_items - Add context items to your directory. The params you need to send through
+ need to be in a list format of [(label, action,),] look at the example code below for
+ more details.
+
+ context_override - By default your context items will be added to the global context
+ menu items but you can override this by setting this to True and then only your
+ context menu items will show.
+
+ playable - By default this is set to False but if set to True kodi will just try
+ and play this item natively with no extra fancy functions.
+
+ EXAMPLE:
+ --------
+ my_context = [('Music','xbmc.executebuiltin("ActivateWindow(music)")'),('Programs','xbmc.executebuiltin("ActivateWindow(programs)")')]
+ # ^ This is our two basic context menu items (music and programs)
+
+ Add_Dir(name='TEST DIRECTORY', url='', mode='test_directory', folder=True, context_items=my_context, context_override=True)
+ # ^ This will add a folder AND a context menu item for when bring up the menu (when focused on this directory).
+ # ^^ The context_override is set to True which means it will override the default Kodi context menu items.
+
+ Add_Dir(name='TEST ITEM', url='', mode='test_item', folder=False, context_items=my_context, context_override=False)
+ # ^ This will add an item to the list AND a context menu item for when bring up the menu (when focused on this item).
+ # ^^ The context_override is set to False which means the new items will appear alongside the default Kodi context menu items.
+
+------------------------------------------------------------------------------------------
+
+ROUTE:
+Use this to set a function in your master_modes dictionary.
+This is to be used for Add_Dir() items, see the example below.
+
+ CODE: route(mode, [args])
+
+ AVAILABLE PARAMS:
+
+ (*) mode - This must be set, it needs to be a custom string.
+ This is the string you'd use in your Add_Dir command to call
+ the function.
+
+ args - This is optional but if the function you're calling
+ requires extra paramaters you can add them in here. Just add them
+ as a list of strings. Example: args=['name','artwork','description']
+
+ BELOW IS AN EXAMPLE OF HOW TO CALL THE CODE IN YOUR MAIN ADDON PY FILE:
+ -----------------------------------------------------------------------
+ @route(mode="test", args=["name","description"])
+ def Test_Function(name,description):
+ dialog.ok('This is a test function', name, description')
+
+ koding.Add_Dir(name='Test Dialog', url={"name":"My Test Function", "description" : "Its ALIVE!!!"}, mode='test')
+ koding.run()
+
+------------------------------------------------------------------------------------------
+
+RUN:
+This needs to be called at the bottom of your code in the main default.py
+
+This checks the modes called in Add_Dir and does all the clever stuff
+in the background which assigns those modes to functions and sends
+through the various params.
+
+Just after this command you need to make
+sure you set the endOfDirectory (as shown below).
+
+
+ CODE: run([default])
+ xbmcplugin.endOfDirectory(int(sys.argv[1]))
+
+ AVAILABLE PARAMS:
+
+ default - This is the default mode you want the add-on to open
+ into, it's set as "main" by default. If you have a different mode
+ name you want to open into just edit accordingly.
+
+------------------------------------------------------------------------------------------
+
+F I L E T O O L S
+
+------------------------------------------------------------------------------------------
+
+CLEAN CACHED IMAGES:
+This will check for any cached artwork and wipe if it's not been accessed more than 10 times in the past x amount of days.
+
+ CODE: Cleanup_Textures([frequency, use_count])
+
+ AVAILABLE PARAMS:
+
+ frequency - This is an optional integer, be default it checks for any
+ images not accessed in 14 days but you can use any amount of days here.
+
+ use_count - This is an optional integer, be default it checks for any
+ images not accessed more than 10 times. If you want to be more ruthless
+ and remove all images not accessed in the past x amount of days then set this very high.
+
+ EXAMPLE CODE:
+ -------------
+ dialog.ok('Clean Textures','We are going to clear any old cached images not accessed at least 10 times in the past 5 days')
+ koding.Cleanup_Textures(frequency=5)
+
+------------------------------------------------------------------------------------------
+
+COMPRESS FILES:
+Compress files in either zip or tar format. This will most likely be replacing
+Archive_Tree longer term as this has better functionality but it's currently
+missing the custom message and exclude files options.
+
+IMPORTANT: There was a known bug where some certain compressed tar.gz files can cause the system to hang
+and a bad zipfile will continue to be made until it runs out of space on your storage device. In the unlikely
+event you encounter this issue just add the problematic file(s) to your exclude list. I think this has since
+been fixed since a complete re-code to this function, or at least I've been unable to recreate it. If you
+find this problem is still occuring please let me know on the forum at http://totalrevolution.tv/forum
+(user: trevdev), thankyou.
+
+ CODE: Compress(src,dst,[compression,parent])
+
+ AVAILABLE PARAMS:
+
+ (*) src - This is the source folder you want to compress
+
+ (*) dst - This is the destination file you want to create
+
+ compression - By default this is set to 'zip' but you can also use 'tar'
+
+ parent - By default this is set to False which means it will compress
+ everything inside the path given. If set to True it will do the same but
+ it will include the parent folder name - ideal if you want to zip up
+ an add-on folder and be able to install via Kodi Settings.
+
+ exclude_dirs - This is optional, if you have folder names you want to exclude just
+ add them here as a list item. By default the folder 'temp' is added to this list so
+ if you need to include folders called temp make sure you send through a list, even
+ if it's an empty one. The reason for leaving temp out is that's where Kodi logfiles
+ and crashlogs are stored on a lot of devices and these are generally not needed in
+ backup zips.
+
+ exclude_files - This is optional, if you have specific file names you want to
+ exclude just add them here as a list item. By default the list consists of:
+ 'kodi.log','kodi.old.log','xbmc.log','xbmc.old.log','spmc.log','spmc.old.log'
+
+ EXAMPLE CODE:
+ -------------
+ koding_path = koding.Physical_Path('special://home/addons/script.module.python.koding.aio')
+ zip_dest = koding.Physical_Path('special://home/test_addon.zip')
+ zip_dest2 = koding.Physical_Path('special://home/test_addon2.zip')
+ tar_dest = koding.Physical_Path('special://home/test_addon.tar')
+ tar_dest2 = koding.Physical_Path('special://home/test_addon2.tar')
+ koding.Compress(src=koding_path,dst=zip_dest,compression='zip',parent=True)
+ koding.Compress(src=koding_path,dst=zip_dest2,compression='zip',parent=False)
+ koding.Compress(src=koding_path,dst=tar_dest,compression='tar',parent=True)
+ koding.Compress(src=koding_path,dst=tar_dest2,compression='tar',parent=False)
+ koding.Text_Box('CHECK HOME FOLDER','If you check your Kodi home folder you should now have 4 different compressed versions of the Python Koding add-on.\n\ntest_addon.zip: This has been zipped up with parent set to True\n\ntest_addon2.zip: This has been zipped up with parent set to False.\n\ntest_addon.tar: This has been compressed using tar format and parent set to True\n\ntest_addon2.tar: This has been compressed using tar format and parent set to False.\n\nFeel free to manually delete these.')
+
+------------------------------------------------------------------------------------------
+
+CONVERT PHYSICAL PATHS TO SPECIAL:
+ Convert physcial paths stored in text files to their special:// equivalent or
+ replace instances of physical paths to special in a string sent through.
+
+ CODE: Convert_Special([filepath, string])
+
+ AVAILABLE PARAMS:
+
+ filepath - This is the path you want to scan, by default it's set to the Kodi HOME directory.
+
+ string - By default this is set to False which means it will convert all instances found of
+ the physical paths to their special equivalent. The scan will convert all instances in all filenames
+ ending in ini, xml, hash, properties. If you set this value to True you will get a return of your
+ 'filepath' string and no files will be altered.
+
+ quoted - By default this is set to true, this means the return you get will be converted
+ with urllib.quote_plus(). This is ideal if you need to get a string you can send
+ through as a path for routing.
+
+ EXAMPLE CODE:
+ -------------
+ path = koding.Physical_Path('special://profile')
+ dialog.ok('ORIGINAL PATH','Let\'s convert this path to it\'s special equivalent:\n[COLOR dodgerblue]%s[/COLOR]'%path)
+ path = Convert_Special(filepath=path,string=True,quoted=False)
+ dialog.ok('CONVERTED PATH','This is the converted path:\n[COLOR dodgerblue]%s[/COLOR]'%path)
+ if dialog.yesno('CONVERT PHYSICAL PATHS','We will now run through your Kodi folder converting all physical paths to their special:// equivalent in xml/hash/properties/ini files.\nDo you want to continue?'):
+ koding.Convert_Special()
+ dialog.ok('SUCCESS','Congratulations, all references to your physical paths have been converted to special:// paths.')
+
+------------------------------------------------------------------------------------------
+
+CREATE A DUMMY FILE:
+Create a dummy file in whatever location you want and with the size you want.
+Use very carefully, this is designed for testing purposes only. Accidental
+useage can result in the devices storage becoming completely full in just a
+few seconds. If using a cheap poor quality device (like many android units)
+then you could even end up killing the device as some of them are made
+with very poor components which are liable to irreversable corruption.
+
+ CODE: koding.Dummy_File(dest, [size, size_format])
+
+ AVAILABLE PARAMS:
+
+ dst - This is the destination folder. This needs to be a FULL path including
+ the file extension. By default this is set to special://home/dummy.txt
+
+ size - This is an optional integer, by default a file of 10 MB will be created.
+
+ size_format - By default this is set to 'mb' (megabytes) but you can change this to
+ 'b' (bytes), 'kb' (kilobytes), 'gb' (gigabytes)
+
+ EXAMPLE CODE:
+ -------------
+ dummy = 'special://home/test_dummy.txt'
+ koding.Dummy_File(dst=dummy, size=100, size_format='b')
+ dialog.ok('DUMMY FILE CREATED','Check your Kodi home folder and you should see a 100 byte test_dummy.txt file.','[COLOR=gold]Press OK to delete this file.[/COLOR]')
+ xbmcvfs.delete(dummy)
+
+------------------------------------------------------------------------------------------
+
+CREATE PATHS:
+Send through a path to a file, if the directories required do not exist this will create them.
+
+ CODE: Create_Paths(path)
+
+ AVAILABLE PARAMS:
+
+ (*) path - This is the full path including the filename. The path
+ sent through will be split up at every instance of '/'
+
+ EXAMPLE CODE:
+ -------------
+ my_path = xbmc.translatePath('special://home/test/testing/readme.txt')
+ koding.Create_Paths(path=my_path)
+ dialog.ok('PATH CREATED','Check in your Kodi home folder and you should now have sub-folders of /test/testing/.','[COLOR=gold]Press ok to remove these folders.[/COLOR]')
+ shutil.rmtree(xbmc.translatePath('special://home/test'))
+
+------------------------------------------------------------------------------------------
+
+DELETE CRASHLOGS
+Delete all kodi crashlogs. This function will retun the amount of successfully removed crashlogs.
+
+ CODE: Delete_Crashlogs([extra_paths])
+
+ AVAILABLE PARAMS:
+ extra_paths - By default this will search for crashlogs for xbmc,
+ kodi and spmc. If you want to add compatibility for other forks of
+ Kodi please send through a list of the files you want deleted. The
+ format to use needs to be like example shown below.
+
+ EXAMPLE CODE:
+ -------------
+ # Lets setup some extra crashlog types for tvmc and ftmc kodi forks
+ log_path = xbmc.translatePath('special://logpath/')
+ tvmc_path = os.path.join(log_path,'tvmc_crashlog*.*')
+ ftmc_path = os.path.join(log_path,'ftmc_crashlog*.*')
+
+
+ deleted_files = koding.Delete_Crashlogs(extra_paths=[tvmc_path, ftmc_path])
+ if deleted_files > 0:
+ dialog.ok('CRASHLOGS DELETED','Congratulations, a total of %s crashlogs have been deleted.')
+ else:
+ dialog.ok('NO CRASHLOGS','No crashlogs could be found on the system.')
+
+------------------------------------------------------------------------------------------
+
+DELETE FILES IN PATH:
+Delete all specific filetypes in a path (including sub-directories)
+
+ CODE: Delete_Files([filepath, filetype, subdirectories])
+
+ AVAILABLE PARAMS:
+
+ (*) filepath - By default this points to the Kodi HOME folder (special://home).
+ The path you send through must be a physical path and not special://
+
+ (*) filetype - The type of files you want to delete, by default it's set to *.txt
+
+ subdirectories - By default it will only search the folder given, if set to True
+ all filetypes listed above will be deleted in the sub-directories too.
+
+ WARNING: This is an extremely powerful and dangerous tool! If you wipe your whole system
+ by putting in the wrong path then it's your own stupid fault!
+
+ EXAMPLE CODE:
+ -------------
+ delete_path = 'special://profile/addon_data/test'
+ xbmcvfs.mkdirs(delete_path)
+ test1 = os.path.join(delete_path,'test1.txt')
+ test2 = os.path.join(delete_path,'test2.txt')
+ koding.Text_File(test1,'w','testing1')
+ koding.Text_File(test2,'w','testing2')
+ dialog.ok('DELETE FILES','All *.txt files will be deleted from:', '', '/userdata/addon_data/test/')
+ koding.Delete_Files(filepath=delete_path, filetype='.txt', subdirectories=True)
+
+------------------------------------------------------------------------------------------
+
+DELETE A FOLDER PATH:
+Completely delete a folder and all it's sub-folders. With the ability to add
+an ignore list for any folders/files you don't want removed.
+
+ CODE: Delete_Folders(filepath, [ignore])
+
+ AVAILABLE PARAMS:
+
+ (*) filepath - Use the physical path you want to remove, this must be converted
+ to the physical path and will not work with special://
+
+ ignore - A list of paths you want to ignore. These need to be sent
+ through as physical paths so just use koding.Physical_Path() when creating
+ your list and these can be folder paths or filepaths.
+
+ WARNING: This is an extremely powerful and dangerous tool! If you wipe important
+ system files from your system by putting in the wrong path then I'm afraid that's
+ your own stupid fault! A check has been put in place so you can't accidentally
+ wipe the whole root.
+
+ EXAMPLE CODE:
+ -------------
+ delete_path = koding.Physical_Path('special://profile/py_koding_test')
+
+ # Create new test directory to remove
+ if not os.path.exists(delete_path):
+ os.makedirs(delete_path)
+
+ # Fill it with some dummy files
+ file1 = os.path.join(delete_path,'file1.txt')
+ file2 = os.path.join(delete_path,'file2.txt')
+ file3 = os.path.join(delete_path,'file3.txt')
+ koding.Dummy_File(dst=file1, size=10, size_format='kb')
+ koding.Dummy_File(dst=file2, size=10, size_format='kb')
+ koding.Dummy_File(dst=file3, size=10, size_format='kb')
+
+ dialog.ok('TEST FILE CREATED','If you look in your userdata folder you should now see a new test folder containing 3 dummy files. The folder name is \'py_koding_test\'.')
+ if dialog.yesno('[COLOR gold]DELETE FOLDER[/COLOR]','Everything except file1.txt will now be removed from:', '/userdata/py_koding_test/','Do you want to continue?'):
+ koding.Delete_Folders(filepath=delete_path, ignore=[file1])
+ dialog.ok('DELETE LEFTOVERS','When you press OK we will delete the whole temporary folder we created including it\'s contents')
+ koding.Delete_Folders(filepath=delete_path)
+
+------------------------------------------------------------------------------------------
+
+EXTRACT:
+This function will extract a zip or tar file and return true or false so unlike the
+builtin xbmc function "Extract" this one will pause code until it's completed the action.
+
+ CODE: koding.Extract(src,dst,[dp])
+ dp is optional, by default it is set to false
+
+ AVAILABLE PARAMS:
+
+ (*) src - This is the source file, the actual zip/tar. Make sure this is a full path to
+ your zip file and also make sure you're not using "special://". This extract function
+ is only compatible with .zip/.tar/.tar.gz files
+
+ (*) dst - This is the destination folder, make sure it's a physical path and not
+ "special://...". This needs to be a FULL path, if you want it to extract to the same
+ location as where the zip is located you still have to enter the full path.
+
+ dp - This is optional, if you pass through the dp function as a DialogProgress()
+ then you'll get to see the status of the extraction process. If you choose not to add
+ this paramater then you'll just get a busy spinning circle icon until it's completed.
+ See the example below for a dp example.
+
+ show_error - By default this is set to False, if set to True an error dialog
+ will appear showing details of the file which failed to extract.
+
+ EXAMPLE CODE:
+ -------------
+ koding_path = koding.Physical_Path('special://home/addons/script.module.python.koding.aio')
+ zip_dest = koding.Physical_Path('special://home/test_addon.zip')
+ extract_dest = koding.Physical_Path('special://home/TEST')
+ koding.Compress(src=koding_path,dst=zip_dest,compression='zip',parent=True)
+ dp = xbmcgui.DialogProgress()
+ dp.create('Extracting Zip','Please Wait')
+ if koding.Extract(_in=zip_dest,_out=extract_dest,dp=dp,show_error=True):
+ dialog.ok('YAY IT WORKED!','We just zipped up your python koding add-on then extracted it to a new folder in your Kodi root directory called TEST. Press OK to delete these files.')
+ xbmcvfs.delete(zip_dest)
+ shutil.rmtree(extract_dest)
+ else:
+ dialog.ok('BAD NEWS!','UH OH SOMETHING WENT HORRIBLY WRONG')
+
+------------------------------------------------------------------------------------------
+
+FREE SPACE:
+Show the amount of available free space in a path, this can be returned in a number of different formats.
+
+ CODE: Free_Space([dirname, filesize])
+
+ AVAILABLE PARAMS:
+
+ dirname - This optional, by default it will tell you how much space is available in your special://home
+ folder. If you require information for another path (such as a different partition or storage device)
+ then enter the physical path. This currently only works for local paths and not networked drives.
+
+ filesize - By default you'll get a return of total bytes, however you can get the value as bytes,
+ kilobytes, megabytes, gigabytes and terabytes..
+
+ VALUES:
+ 'b' = bytes (integer)
+ 'kb' = kilobytes (float to 1 decimal place)
+ 'mb' = kilobytes (float to 2 decimal places)
+ 'gb' = kilobytes (float to 3 decimal places)
+ 'tb' = terabytes (float to 4 decimal places)
+
+ EXAMPLE CODE:
+ -------------
+ HOME = koding.Physical_Path('special://home')
+ my_space = koding.Free_Space(HOME, 'gb')
+ dialog.ok('Free Space','Available space in HOME: %s GB' % my_space)
+
+------------------------------------------------------------------------------------------
+
+FOLDER SIZE:
+Return the size of a folder path including sub-directories,
+this can be returned in a number of different formats.
+
+ CODE: koding.Folder_Size([dirname, filesize])
+
+ AVAILABLE PARAMS:
+
+ dirname - This optional, by default it will tell you how much space is available in your
+ special://home folder. If you require information for another path (such as a different
+ partition or storage device) then enter the physical path. This currently only works for
+ local paths and not networked drives.
+
+ filesize - By default you'll get a return of total bytes, however you can get the value as
+ bytes, kilobytes, megabytes, gigabytes and terabytes..
+
+ VALUES:
+ 'b' = bytes (integer)
+ 'kb' = kilobytes (float to 1 decimal place)
+ 'mb' = kilobytes (float to 2 decimal places)
+ 'gb' = kilobytes (float to 3 decimal places)
+ 'tb' = terabytes (float to 4 decimal places)
+
+ EXAMPLE CODE:
+ -------------
+ HOME = Physical_Path('special://home')
+ home_size = Folder_Size(HOME, 'mb')
+ dialog.ok('Folder Size','KODI HOME: %s MB' % home_size)
+
+------------------------------------------------------------------------------------------
+
+FRESH INSTALL:
+Attempt to completely wipe your install. You can send through a list
+of addons or paths you want to ignore (leave in the setup) or you can
+leave blank. If left blank and the platform is OpenELEC or LibreELEC
+it will perform a hard reset command followed by a reboot.
+
+ CODE: Fresh_Install([keep_addons, ignore, keepdb)
+
+ AVAILABLE PARAMS:
+
+ keep_addons - This is optional, if you have specific add-ons you want to omit
+ from the wipe (leave intact) then just enter a list of add-on id's here. The code
+ will determine from the addon.xml file which dependencies and sub-dependencies are
+ required for that add-on so there's no need to create a huge list, you only need to
+ list the master add-on id's. For example if you want to keep the current skin and
+ your add-on you would use: keep_addons=['plugin.program.myaddon',System('currentskin')]
+ and all addons/dependencies associated with those two add-ons will be added to the ignore
+ list.
+
+ ignore - This is optional, you can send through a list of paths you want to omit from
+ the wipe. You can use folder paths to skip the whole folder or you can use individual
+ file paths. Please make sure you use the physical path and not special://
+ So before creating your list make sure you use xbmc.translatePath()
+
+ keepdb - By default this is set to True which means the code will keep all the Kodi databases
+ intact and perform a profile reload once wipe is complete. This will mean addons, video, music,
+ epg, ADSP and viewtypes databases will remain completely untouched and Kodi should be fine to use
+ without the need for a restart. If you set keepdb to False nothing will happen once the wipe has
+ completed and it's up to you to choose what to do in your main code. I would highly recommend an
+ ok dialog followed by xbmc.executebuiltin('Quit'). This will force Kodi to recreate all the relevant
+ databases when they re-open. If you try and continue using Kodi without restarting the databases
+ will not be recreated and you risk corruption.
+
+ EXAMPLE CODE:
+ -------------
+ if dialog.yesno('[COLOR gold]TOTAL WIPEOUT![/COLOR]','This will attempt give you a totally fresh install of Kodi.','Are you sure you want to continue?'):
+ if dialog.yesno('[COLOR gold]FINAL CHANCE!!![/COLOR]','If you click Yes this WILL attempt to wipe your install', '[COLOR=dodgerblue]ARE YOU 100% CERTAIN YOU WANT TO WIPE?[/COLOR]'):
+ clean_state = koding.Fresh_Install()
+------------------------------------------------------------------------------------------
+
+GET CONTENTS OF A PATH (INC. SUB-DIRECTORIES)
+Return a list of either files or folders in a given path.
+
+ CODE: Get_Contents(path, [folders, subfolders, exclude_list, full_path, filter])
+
+ AVAILABLE PARAMS:
+
+ (*) path - This is the path you want to search, no sub-directories are scanned.
+
+ folders - By default this is set to True and the returned list will only
+ show folders. If set to False the returned list will show files only.
+
+ exclude_list - Optionally you can add a list of items you don't want returned
+
+ full_path - By default the entries in the returned list will contain the full
+ path to the folder/file. If you only want the file/folder name set this to False.
+
+ subfolders - By default this is set to False but if set to true it will check
+ all sub-directories and not just the directory sent through.
+
+ filter - If you want to only return files ending in a specific string you
+ can add details here. For example to only show '.xml' files you would send
+ through filter='.xml'.
+
+ EXAMPLE CODE:
+ -------------
+ ADDONS = Physical_Path('special://home/addons')
+ addon_folders = koding.Get_Contents(path=ADDONS, folders=True, exclude_list=['packages','temp'], full_path=False)
+ results = ''
+ for item in addon_folders:
+ results += 'FOLDER: [COLOR=dodgerblue]%s[/COLOR]\n'%item
+ koding.Text_Box('ADDON FOLDERS','Below is a list of folders found in the addons folder (excluding packages and temp):\n\n%s'%results)
+
+------------------------------------------------------------------------------------------
+
+HIGHEST VERSION:
+Send through a list of strings which all have a common naming structure,
+the one with the highest version number will be returned.
+
+ CODE: Highest_Version(content,[start_point,end_point])
+
+ AVAILABLE PARAMS:
+
+ (*) content - This is the list of filenames you want to check.
+
+ start_point - If your filenames have a common character/string immediately
+ before the version number enter that here. For example if you're looking at
+ online repository/add-on files you would use '-' as the start_point. The version
+ numbers always appear after the final '-' with add-ons residing on repo's.
+
+ end_point - If your version number is followed by a common string (e.g. '.zip')
+ then enter it in here.
+
+ EXAMPLE CODE:
+ -------------
+ mylist = ['plugin.test-1.0.zip','plugin.test-0.7.zip','plugin.test-1.1.zip','plugin.test-0.9.zip']
+ dialog.ok('OUR LIST OF FILES', '[COLOR=dodgerblue]%s[/COLOR]\n[COLOR=powderblue]%s[/COLOR]\n[COLOR=dodgerblue]%s[/COLOR]\n[COLOR=powderblue]%s[/COLOR]'%(mylist[0],mylist[1],mylist[2],mylist[3]))
+
+ highest = Highest_Version(content=mylist,start_point='-',end_point='.zip')
+ dialog.ok('HIGHEST VERSION', 'The highest version number of your files is:','[COLOR=dodgerblue]%s[/COLOR]'%highest)
+
+------------------------------------------------------------------------------------------
+
+MOVE A DIRECTORY:
+Move a directory including all sub-directories to a new location.
+This will automatically create the new location if it doesn't already
+exist and it wierwrite any existing entries if they exist.
+
+ CODE: koding.Move_Tree(src, dst)
+
+ AVAILABLE PARAMS:
+
+ (*) src - This is source directory that you want to copy
+
+ (*) dst - This is the destination location you want to copy a directory to.
+
+ dp - This is optional, if you pass through the dp function as a DialogProgress()
+ then you'll get to see the status of the move process. See the example below for a dp example.
+
+ EXAMPLE CODE:
+ -------------
+ dp = xbmcgui.DialogProgress()
+ source = koding.Physical_Path('special://profile/move_test')
+
+ # Lets create a 500MB dummy file so we can move and see dialog progress
+ dummy = os.path.join(source,'dummy')
+ if not os.path.exists(source):
+ os.makedirs(source)
+ koding.Dummy_File(dst=dummy+'1.txt', size=10, size_format='mb')
+ koding.Dummy_File(dst=dummy+'2.txt', size=10, size_format='mb')
+ koding.Dummy_File(dst=dummy+'3.txt', size=10, size_format='mb')
+ koding.Dummy_File(dst=dummy+'4.txt', size=10, size_format='mb')
+ koding.Dummy_File(dst=dummy+'5.txt', size=10, size_format='mb')
+ koding.Dummy_File(dst=dummy+'6.txt', size=10, size_format='mb')
+ dialog.ok('DUMMY FILE CREATED','If you want to check in your userdata folder you should have a new folder called "move_test" which has 6x 10MB dummy files.')
+
+ # This is optional but if you want to see a dialog progress then you'll need this
+ dp.create('MOVING FILES','Please Wait')
+
+ destination = koding.Physical_Path('special://home/My MOVED Dummy File')
+ koding.Move_Tree(source, destination, dp)
+ dialog.ok('CHECK YOUR KODI HOME FOLDER','Please check your Kodi home folder, the dummy file should now have moved in there. When you press OK it will be removed')
+ shutil.rmtree(destination)
+
+------------------------------------------------------------------------------------------
+
+PATH TO CURRENT "LIVE" DATABASE:
+If you need to find out the current "real" database in use then this is the function for you.
+It will scan for a specific database type (e.g. addons) and return the path to the one which was last updated.
+This is particularly useful if the system has previously updated to a newer version rather than a fresh install
+or if they've installed a "build" which contained old databases.
+
+ CODE: DB_Path_Check(db_path)
+
+ AVAILABLE VALUES:
+
+ (*) db_path - This is the string the database starts with.
+ If you want to find the path for the addons*.db you would use "addons"
+ as the value, if you wanted to find the path of the MyVideos*.db you would use
+ "myvideos" etc. - it is not case sensitive.
+
+ EXAMPLE CODE:
+ -------------
+ dbpath = koding.DB_Path_Check(db_path='addons')
+ dialog.ok('ADDONS DB','The path to the current addons database is:',dbpath)
+
+------------------------------------------------------------------------------------------
+
+PHYSICAL PATHS:
+Send through a special:// path and get the real physical path returned.
+This has been written due to the problem where if you're running the Windows Store
+version of Kodi the built-in xbmc.translatePath() function is returning bogus results
+making it impossible to access databases.
+
+ CODE: koding.Physical_Path([path])
+
+ AVAILABLE PARAMS:
+
+ path - This is the path to the folder/file you want returned. This is optional,
+ if you leave this out it will just return the path to the root directory (special://home)
+
+ EXAMPLE CODE:
+ -------------
+ location = 'special://home/addons/script.module.python.koding.aio'
+ real_location = koding.Physical_Path(location)
+ xbmc.log(real_location,2)
+ dialog.ok('PHYSICAL PATH','The real path of special://home/addons/script.module.python.koding.aio is:','[COLOR=dodgerblue]%s[/COLOR]'%real_location)
+
+------------------------------------------------------------------------------------------
+
+READ/WRITE TEXT FILES:
+Open/create a text file and read/write to it.
+
+ CODE: koding.Text_File(path, mode, [text])
+
+ AVAILABLE PARAMS:
+
+ (*) path - This is the path to the text file
+
+ (*) mode - This can be 'r' (for reading) or 'w' (for writing)
+
+ text - This is only required if you're writing to a file, this
+ is the text you want to enter. This will completely overwrite any
+ text already in the file.
+
+ EXAMPLE CODE:
+ -------------
+ HOME = koding.Physical_Path('special://home')
+ koding_test = os.path.join(HOME, 'koding_test.txt')
+ koding.Text_File(path=koding_test, mode='w', text='Well done, you\'ve created a text file containing this text!')
+ dialog.ok('CREATE TEXT FILE','If you check your home Kodi folder and you should now have a new koding_test.txt file in there.','[COLOR=gold]DO NOT DELETE IT YET![/COLOR]')
+ mytext = koding.Text_File(path=koding_test, mode='r')
+ dialog.ok('TEXT FILE CONTENTS','The text in the file created is:','[COLOR=dodgerblue]%s[/COLOR]'%mytext,'[COLOR=gold]CLICK OK TO DELETE THE FILE[/COLOR]')
+ try:
+ os.remove(koding_test)
+ except:
+ dialog.ok('FAILED TO REMOVE','Could not remove the file, looks like you might have it open in a text editor. Please manually remove yourself')
+
+------------------------------------------------------------------------------------------
+
+RETURN END OF A PATH:
+Split the path at every '/' and return the final file/folder name.
+If your path uses backslashes rather than forward slashes it will use
+that as the separator.
+
+ CODE: End_Path(path)
+
+ AVAILABLE PARAMS:
+
+ path - This is the path where you want to grab the end item name.
+
+ EXAMPLE CODE:
+ -------------
+ addons_path = 'special://home/addons'
+ file_name = koding.End_Path(path=addons_path)
+ dialog.ok('ADDONS FOLDER','Path checked:',addons_path,'Folder Name: [COLOR=dodgerblue]%s[/COLOR]'%file_name)
+ file_path = 'special://home/addons/script.module.python.koding.aio/addon.xml'
+ file_name = koding.End_Path(path=file_path)
+ dialog.ok('FILE NAME','Path checked:',file_path,'File Name: [COLOR=dodgerblue]%s[/COLOR]'%file_name)
+
+------------------------------------------------------------------------------------------
+
+S Y S T E M B A S E D F U N C T I O N S
+
+------------------------------------------------------------------------------------------
+
+CURRENT PROFILE:
+This will return the current running profile, it's only one line of code but this is for my benefit as much as
+anyone else's. I use this function quite a lot and keep forgetting the code so figured it would be easier to
+just write a simple function for it :)
+
+ CODE: Current_Profile()
+
+ EXAMPLE CODE:
+ -------------
+ profile = koding.Current_Profile()
+ dialog.ok('CURRENT PROFILE','Your current running profile is:','[COLOR=dodgerblue]%s[/COLOR]' % profile)
+
+------------------------------------------------------------------------------------------
+
+CURRENT TIMESTAMP:
+This will return the timestamp in various formats. By default it returns as "integer" mode but other options are listed below:
+
+ CODE: Timestamp(mode)
+ mode is optional, by default it's set as integer
+
+ AVAILABLE VALUES:
+
+ 'integer' - An integer which is nice and easy to work with in Python (especially for
+ finding out human readable diffs). The format returned is [year][month][day][hour][minutes][seconds].
+
+ 'epoch' - Unix Epoch format (calculated in seconds passed since 12:00 1st Jan 1970).
+
+ 'clean' - A clean user friendly time format: Tue Jan 13 10:17:09 2009
+
+ 'date_time' - A clean interger style date with time at end: 2017-04-07 10:17:09
+
+ EXAMPLE CODE:
+ -------------
+ integer_time = koding.Timestamp('integer')
+ epoch_time = koding.Timestamp('epoch')
+ clean_time = koding.Timestamp('clean')
+ date_time = koding.Timestamp('date_time')
+ import datetime
+ installedtime = str(datetime.datetime.now())[:-7]
+ dialog.ok('CURRENT TIME','Integer: %s' % integer_time, 'Epoch: %s' % epoch_time, 'Clean: %s' % clean_time)
+
+------------------------------------------------------------------------------------------
+
+ENABLE/DISABLE/SET VARIOUS KODI SETTINGS:
+Use this to set built-in kodi settings via JSON or set skin settings.
+
+ CODE: Set_Setting(setting, [setting_type, value])
+
+ AVAILABLE PARAMS:
+
+ setting_type - The type of setting type you want to change. By default
+ it's set to 'kodi_setting', see below for more info.
+
+ AVAILALE VALUES:
+
+ 'string' : sets a skin string, requires a value.
+
+ 'bool_true' : sets a skin boolean to true, no value required.
+
+ 'bool_false' sets a skin boolean to false, no value required.
+
+ 'kodi_setting' : sets values found in guisettings.xml. Requires
+ a string of 'true' or 'false' for the value paramater.
+
+ 'addon_enable' : enables/disables an addon. Requires a string of
+ 'true' (enable) or 'false' (disable) as the value. You will get a
+ return of True/False on whether successul. Depending on your requirements
+ you may prefer to use the Toggle_Addons function.
+
+ 'json' : WIP - setitng = method, value = params, see documentation on
+ JSON-RPC API here: http://kodi.wiki/view/JSON-RPC_API)
+
+ setting - This is the name of the setting you want to change, it could be a
+ setting from the kodi settings or a skin based setting. If you're wanting
+ to enable/disable an add-on this is set as the add-on id.
+
+ value: This is the value you want to change the setting to. By default this
+ is set to 'true'.
+
+
+ EXAMPLE CODE:
+ -------------
+ if dialog.yesno('RSS FEEDS','Would you like to enable or disable your RSS feeds?',yeslabel='ENABLE',nolabel='DISABLE'):
+ koding.Set_Setting(setting_type='kodi_setting', setting='lookandfeel.enablerssfeeds', value='true')
+ else:
+ koding.Set_Setting(setting_type='kodi_setting', setting='lookandfeel.enablerssfeeds', value='false')
+
+------------------------------------------------------------------------------------------
+
+FORCE CLOSE:
+Force close Kodi, should only be used in extreme circumstances.
+
+ CODE: Force_Close()
+
+ EXAMPLE CODE:
+ -------------
+ if dialog.yesno('FORCE CLOSE','Are you sure you want to forcably close Kodi? This could potentially cause corruption if system tasks are taking place in background.'):
+ koding.Force_Close()
+
+------------------------------------------------------------------------------------------
+
+GET USER ID & GROUP ID:
+A simple function to set user id and group id to the current running App
+for system commands. For example if you're using the subprocess command
+you could send through the preexec_fn paramater as koding.Get_ID(setid=True).
+This function will also return the uid and gid in form of a dictionary.
+
+ CODE: Get_ID([setid])
+
+ AVAILABLE PARAMS:
+
+ (*) setid - By default this is set to False but if set to True it
+ will set the ids (to be used for subprocess commands)
+
+ EXAMPLE CODE:
+ -------------
+ ids = Get_ID(setid=False)
+ if ids:
+ uid = ids['uid']
+ gid = ids['gid']
+ dialog.ok('USER & GROUP ID','User ID: %s'%uid, 'Group ID: %s'%gid)
+ else:
+ dialog.ok('USER & GROUP ID','This function is not applicable to your system. We\'ve been sent back a return of False to indicate this function does not exist on your os.')
+
+------------------------------------------------------------------------------------------
+
+GRAB LOG:
+This will grab the log file contents, works on all systems even forked kodi.
+
+ CODE: Grab_Log([log_type, formatting, sort_order])
+
+ AVAILABLE PARAMS:
+
+ log_type - This is optional, if not set you will get the current log.
+ If you would prefer the old log set this to 'old'
+
+ formatting - By default you'll just get a default log but you can set
+ this to 'warnings', 'notices', 'errors' to filter by only those error types.
+ Notices will return in blue, warnings in gold and errors in red.
+ You can use as many of the formatting values as you want, just separate by an
+ underscore such as 'warnings_errors'. If using anything other than the
+ default in here your log will returned in order of newest log activity first
+ (reversed order). You can also use 'clean' as an option and that will just
+ return the full log but with clean text formatting and in reverse order.
+
+ sort_order - This will only work if you've sent through an argument other
+ than 'original' for the formatting. By default the log will be shown in
+ 'reverse' order but you can set this to 'original' if you prefer ascending
+ timestamp ordering like a normal log.
+
+ EXAMPLE CODE:
+ -------------
+ my_log = koding.Grab_Log()
+ dialog.ok('KODI LOG LOOP','Press OK to see various logging options, every 5 seconds it will show a new log style.')
+ koding.Text_Box('CURRENT LOG FILE (ORIGINAL)',my_log)
+ xbmc.sleep(5000)
+ my_log = koding.Grab_Log(formatting='clean', sort_order='reverse')
+ koding.Text_Box('CURRENT LOG FILE (clean in reverse order)',my_log)
+ xbmc.sleep(5000)
+ my_log = koding.Grab_Log(formatting='errors_warnings', sort_order='reverse')
+ koding.Text_Box('CURRENT LOG FILE (erros & warnings only - reversed)',my_log)
+ xbmc.sleep(5000)
+ old_log = koding.Grab_Log(log_type='old')
+ koding.Text_Box('OLD LOG FILE',old_log)
+
+------------------------------------------------------------------------------------------
+
+LAST ERROR:
+Return details of the last error produced, perfect for try/except statements
+
+ CODE: Last_Error()
+
+ EXAMPLE CODE:
+ -------------
+ try:
+ xbmc.log(this_should_error)
+ except:
+ koding.Text_Box('ERROR MESSAGE',Last_Error())
+
+------------------------------------------------------------------------------------------
+
+NETWORK SETTINGS:
+Attempt to open the WiFi/network settings for the current running operating system.
+
+I have no access to any iOS based systems so if anybody wants to add support for
+that and you know the working code please contact me at info@totalrevolution.tv
+The Linux one is also currently untested and of course there are many different
+distros so if you know of any improved code please do pass on. Thank you.
+
+ CODE: Network_Settings()
+
+ EXAMPLE CODE:
+ -------------
+ koding.Network_Settings()
+
+------------------------------------------------------------------------------------------
+
+PRINT TO LOG - DEBUG MODE:
+Print to the Kodi log but only if debugging is enabled in settings.xml
+
+ CODE: koding.dolog(string, [my_debug])
+
+ AVAILABLE PARAMS:
+
+ (*) string - This is your text you want printed to log.
+
+ my_debug - This is optional, if you set this to True you will print
+ to the log regardless of what the debug setting is set at in add-on settings.
+
+ line_info - By default this is set to True and will show the line number where
+ the dolog command was called from along with the filepath it was called from.
+
+ EXAMPLE CODE:
+ -------------
+ koding.dolog(string='Quick test to see if this gets printed to the log', my_debug=True, line_info=True)
+ dialog.ok('[COLOR gold]CHECK LOGFILE 1[/COLOR]','If you check your log file you should be able to see a new test line we printed \
+ and immediately below that should be details of where it was called from.')
+ koding.dolog(string='This one should print without the line and file info', my_debug=True, line_info=False)
+ dialog.ok('[COLOR gold]CHECK LOGFILE 2[/COLOR]','If you check your log file again you should now be able to see a new line printed \
+ but without the file/line details.')
+
+------------------------------------------------------------------------------------------
+
+PYTHON VERSION:
+Return the current version of Python as a string. Very useful if you need
+to find out whether or not to return https links (Python 2.6 is not SSL friendly).
+
+CODE: Python_Version()
+
+ EXAMPLE CODE:
+ -------------
+ py_version = koding.Python_Version()
+ dialog.ok('PYTHON VERSION','You are currently running:','Python v.%s'%py_version)
+
+------------------------------------------------------------------------------------------
+
+REFRESH/RELOAD VARIOUS SECTIONS:
+Refresh a number of items in kodi, choose the order they are
+executed in by putting first in your r_mode. For example if you
+want to refresh addons then repo and then the profile you would
+send through a list in the order you want them to be executed.
+
+ CODE: Refresh(r_mode, [profile])
+
+ AVAILABLE PARAMS:
+
+ r_mode - This is the types of "refresh you want to perform",
+ you can send through just one item or a list of items from the
+ list below. If you want a sleep between each action just put a
+ '~' followed by amount of milliseconds after the r_mode. For example
+ r_mode=['addons~3000', 'repos~2000', 'profile']. This would refresh
+ the addons, wait 2 seconds then refresh the repos, wait 3 seconds then
+ reload the profile. The default is set to do a force refresh on
+ addons and repositories - ['addons', 'repos'].
+
+ 'addons': This will perform the 'UpdateLocalAddons' command.
+
+ 'container': This will refresh the contents of the page.
+
+ 'profile': This will refresh the current profile or if
+ the profile_name param is set it will load that.
+
+ 'repos': This will perform the 'UpdateAddonRepos' command.
+
+ 'skin': This will perform the 'ReloadSkin' command.
+
+ profile_name - If you're sending through the option to refresh
+ a profile it will reload the current running profile by default
+ but you can pass through a profile name here.
+
+ EXAMPLE CODE:
+ -------------
+ dialog.ok('RELOAD SKIN','We will now attempt to update the addons, pause 3s, update repos and pause 2s then reload the skin. Press OK to continue.')
+ koding.Refresh(r_mode=['addons~3000', 'repos~2000', 'skin'])
+ xbmc.sleep(2000)
+ dialog.ok('COMPLETE','Ok that wasn\'t the best test to perform as you can\'t physically see any visible changes other than the skin refreshing so you\'ll just have to trust us that it worked!')
+
+------------------------------------------------------------------------------------------
+
+REQUIREMENTS:
+Return the min and max versions of built-in kodi dependencies required by
+the running version of Kodi (xbmc.gui, xbmc.python etc.), The return will
+be a dictionary with the keys 'min' and 'max'.
+
+ CODE: Requirements(dependency)
+
+ AVAILABLE PARAMS:
+
+ (*) dependency - This is the dependency you want to check.
+ You can check any built-in dependency which has backwards-compatibility
+ but the most commonly used are xbmc.gui and xbmc.python.
+
+ EXAMPLE CODE:
+ -------------
+ xbmc_gui = Requirements('xbmc.gui')
+ xbmc_python = Requirements('xbmc.python')
+ dialog.ok('DEPENDENCIES','[COLOR=dodgerblue]xbmc.gui[/COLOR] Min: %s Max: %s'%(xbmc_gui['min'],xbmc_gui['max']),'[COLOR=dodgerblue]xbmc.python[/COLOR] Min: %s Max: %s'%(xbmc_python['min'],xbmc_python['max']))
+
+------------------------------------------------------------------------------------------
+
+RUNNING APP:
+Return the Kodi app name you're running, useful for fork compatibility
+
+ CODE: Running_App()
+
+ EXAMPLE CODE:
+ -------------
+ my_kodi = koding.Running_App()
+ kodi_ver = xbmc.getInfoLabel("System.BuildVersion")
+ dialog.ok('KODI VERSION','You are running:','[COLOR=dodgerblue]%s[/COLOR] - v.%s' % (my_kodi, kodi_ver))
+
+------------------------------------------------------------------------------------------
+
+SLEEP IF FUNCTION ACTIVE:
+This will allow you to pause code while a specific function is
+running in the background.
+
+ CODE: Sleep_If_Function_Active(function, args, kill_time, show_busy)
+
+ AVAILABLE PARAMS:
+
+ function - This is the function you want to run. This does
+ not require brackets, you only need the function name.
+
+ args - These are the arguments you want to send through to
+ the function, these need to be sent through as a list.
+
+ kill_time - By default this is set to 30. This is the maximum
+ time in seconds you want to wait for a response. If the max.
+ time is reached before the function completes you will get
+ a response of False.
+
+ show_busy - By default this is set to True so you'll get a busy
+ working dialog appear while the function is running. Set to
+ false if you'd rather not have this.
+
+ EXAMPLE CODE:
+ -------------
+ def Open_Test_URL(url):
+ koding.Open_URL(url)
+
+ dialog.ok('SLEEP IF FUNCTION ACTIVE','We will now attempt to read a 20MB zip and then give up after 10 seconds.','Press OK to continue.')
+ koding.Sleep_If_Function_Active(function=Open_Test_URL, args=['http://download.thinkbroadband.com/20MB.zip'], kill_time=10, show_busy=True)
+ dialog.ok('FUNCTION COMPLETE','Of course we cannot read that file in just 10 seconds so we\'ve given up!')
+
+------------------------------------------------------------------------------------------
+
+SLEEP IF WINDOW/DIALOG IS ACTIVE:
+This will allow you to pause code while a specific window is open.
+
+ CODE: Sleep_If_Window_Active(window_type)
+
+ AVAILABLE PARAMS:
+
+ window_type - This is the window xml name you want to check for, if it's
+ active then the code will sleep until it becomes inactive. By default this
+ is set to the custom text box (10147). You can find a list of window ID's
+ here: http://kodi.wiki/view/Window_IDs
+
+ EXAMPLE CODE:
+ -------------
+ koding.Text_Box('EXAMPLE TEXT','This is just an example, normally a text box would not pause code and the next command would automatically run immediately over the top of this.')
+ koding.Sleep_If_Window_Active(10147) # This is the window id for the text box
+ dialog.ok('WINDOW CLOSED','The window has now been closed so this dialog code has now been initiated')
+
+------------------------------------------------------------------------------------------
+
+SYSTEM COMMANDS:
+This is just a simplified method of grabbing certain Kodi infolabels, paths
+and booleans as well as performing some basic built in kodi functions.
+We have a number of regularly used functions added to a dictionary which can
+quickly be called via this function or you can use this function to easily
+run a command not currently in the dictionary. Just use one of the
+many infolabels, builtin commands or conditional visibilities available:
+
+info: http://kodi.wiki/view/InfoLabels
+bool: http://kodi.wiki/view/List_of_boolean_conditions
+
+ CODE: System(command, [function])
+
+ AVAILABLE PARAMS:
+
+ (*) command - This is the command you want to perform, below is a list
+ of all the default commands you can choose from, however you can of course
+ send through your own custom command if using the function option (details
+ at bottom of page)
+
+ AVAILABLE VALUES:
+
+ 'addonid' : Returns the FOLDER id of the current add-on. Please note could differ from real add-on id.
+ 'addonname' : Returns the current name of the add-on
+ 'builddate' : Return the build date for the current running version of Kodi
+ 'cpu' : Returns the CPU usage as a percentage
+ 'cputemp' : Returns the CPU temperature in farenheit or celcius depending on system settings
+ 'currentlabel' : Returns the current label of the item in focus
+ 'currenticon' : Returns the name of the current icon
+ 'currentpos' : Returns the current list position of focused item
+ 'currentpath' : Returns the url called by Kodi for the focused item
+ 'currentrepo' : Returns the repo of the current focused item
+ 'currentskin' : Returns the FOLDER id of the skin. Please note could differ from actual add-on id
+ 'date' : Returns the date (Tuesday, April 11, 2017)
+ 'debug' : Toggles debug mode on/off
+ 'freeram' : Returns the amount of free memory available (in MB)
+ 'freespace' : Returns amount of free space on storage in this format: 10848 MB Free
+ 'hibernate' : Hibernate system, please note not all systems are capable of waking from hibernation
+ 'internetstate' : Returns True or False on whether device is connected to internet
+ 'ip' : Return the current LOCAL IP address (not your public IP)
+ 'kernel' : Return details of the system kernel
+ 'language' : Return the language currently in use
+ 'mac' : Return the mac address, will only return the mac currently in use (Wi-Fi OR ethernet, not both)
+ 'numitems' : Return the total amount of list items curently in focus
+ 'profile' : Return the currently running profile name
+ 'quit' : Quit Kodi
+ 'reboot' : Reboot the system
+ 'restart' : Restart Kodi (Windows/Linux only)
+ 'shutdown' : Shutdown the system
+ 'sortmethod' : Return the current list sort method
+ 'sortorder' : Return the current list sort order
+ 'systemname' : Return a clean friendly name for the system
+ 'time' : Return the current time in this format: 2:05 PM
+ 'usedspace' : Return the amount of used space on the storage in this format: 74982 MB Used
+ 'version' : Return the current version of Kodi, this may need cleaning up as it contains full file details
+ 'viewmode' : Return the current list viewmode
+ 'weatheraddon' : Return the current plugin being used for weather
+
+
+ function - This is optional and default is set to a blank string which will
+ allow you to use the commands listed above but if set you can use your own
+ custom commands by setting this to one of the values below.
+
+ AVAILABLE VALUES:
+
+ 'bool' : This will allow you to send through a xbmc.getCondVisibility() command
+ 'info' : This will allow you to send through a xbmc.getInfoLabel() command
+ 'exec' : This will allow you to send through a xbmc.executebuiltin() command
+
+ EXAMPLE CODE:
+ -------------
+ current_time = koding.System(command='time')
+ current_label = koding.System(command='currentlabel')
+ is_folder = koding.System(command='ListItem.IsFolder', function='bool')
+ dialog.ok('PULLED DETAILS','The current time is %s' % current_time, 'Folder status of list item [COLOR=dodgerblue]%s[/COLOR]: %s' % (current_label, is_folder),'^ A zero means False, as in it\'s not a folder.')
+
+------------------------------------------------------------------------------------------
+
+V A R I A B L E / S T R I N G B A S E D F U N C T I O N S
+
+------------------------------------------------------------------------------------------
+
+ASCII CHECK:
+Return a list of files found containing non ASCII characters in the filename.
+
+ CODE: ASCII_Check([sourcefile, dp])
+
+ AVAILABLE PARAMS:
+
+ sourcefile - The folder you want to scan, by default it's set to the
+ Kodi home folder.
+
+ dp - Optional DialogProgress, by default this is False. If you want
+ to show a dp make sure you initiate an instance of xbmcgui.DialogProgress()
+ and send through as the param.
+
+ EXAMPLE CODE:
+ -------------
+ home = koding.Physical_Path('special://home')
+ progress = xbmcgui.DialogProgress()
+ progress.create('ASCII CHECK')
+ my_return = ASCII_Check(sourcefile=home, dp=progress)
+ if len(my_return) > 0:
+ dialog.select('NON ASCII FILES', my_return)
+ else:
+ dialog.ok('ASCII CHECK CLEAN','Congratulations!','There weren\'t any non-ASCII files found on this system.')
+
+------------------------------------------------------------------------------------------
+
+CLEANUP A STRING:
+Clean a string, removes whitespaces and common buggy formatting when pulling from websites
+
+ CODE: Cleanup_String(my_string)
+
+ AVAILABLE PARAMS:
+
+ (*) my_string - This is the main text you want cleaned up.
+
+ EXAMPLE CODE:
+ -------------
+ current_text = '" This is a string of text which should be cleaned up /'
+ dialog.ok('ORIGINAL STRING', '[COLOR dodgerblue]%s[/COLOR]\n\nPress OK to view the cleaned up version.'%current_text)
+ clean_text = koding.Cleanup_String(current_text)
+ dialog.ok('CLEAN STRING', '[COLOR dodgerblue]%s[/COLOR]'%clean_text)
+
+------------------------------------------------------------------------------------------
+
+DATA TYPE:
+This will return whether the item received is a dictionary, list, string, integer etc.
+
+ CODE: Data_Type(data)
+
+ AVAILABLE PARAMS:
+
+ (*) data - This is the variable you want to check.
+
+ RETURN VALUES:
+ list, dict, str, int, float, bool
+
+ EXAMPLE CODE:
+ -------------
+ test1 = ['this','is','a','list']
+ test2 = {"a" : "1", "b" : "2", "c" : 3}
+ test3 = 'this is a test string'
+ test4 = 12
+ test5 = 4.3
+ test6 = True
+
+ my_return = '[COLOR dodgerblue]%s[/COLOR] : %s\n' % (test1, koding.Data_Type(test1))
+ my_return += '[COLOR dodgerblue]%s[/COLOR] : %s\n' % (test2, koding.Data_Type(test2))
+ my_return += '[COLOR dodgerblue]%s[/COLOR] : %s\n' % (test3, koding.Data_Type(test3))
+ my_return += '[COLOR dodgerblue]%s[/COLOR] : %s\n' % (test4, koding.Data_Type(test4))
+ my_return += '[COLOR dodgerblue]%s[/COLOR] : %s\n' % (test5, koding.Data_Type(test5))
+ my_return += '[COLOR dodgerblue]%s[/COLOR] : %s\n' % (test6, koding.Data_Type(test6))
+
+ koding.Text_Box('TEST RESULTS', my_return)
+
+------------------------------------------------------------------------------------------
+
+DECODE STRING TO CLEAN TEXT:
+This will allow you to send a string which contains a variety of special characters (including
+non ascii, unicode etc.) and it will convert into a nice clean string which plays nicely
+with Python and Kodi.
+
+ CODE: Decode_String(string)
+
+ AVAILABLE PARAMS:
+
+ (*) string - This is the string you want to convert
+
+ EXAMPLE CODE:
+ -------------
+ my_string = 'symbols like [COLOR dodgerblue]¥¨˚∆ƒπø“¬∂≈óõřĖė[/COLOR] can cause errors \nnormal chars like [COLOR dodgerblue]asfasdf[/COLOR] are fine'
+ dialog.ok('ORIGINAL TEXT',my_string)
+ my_string = koding.Decode_String(my_string)
+ dialog.ok('DECODED/STRIPPED',my_string)
+
+------------------------------------------------------------------------------------------
+
+FANCY TEXT:
+Capitalize a string and make the first colour of each string blue and the rest of text white
+That's the default colours but you can change to whatever colours you want.
+
+ CODE: Colour_Text(text, [color1, color2])
+
+ AVAILABLE PARAMS:
+
+ (*) text - This is the main text you want to change
+
+ colour1 - This is optional and is set as dodgerblue by default.
+ This is the first letter of each word in the string
+
+ colour2 - This is optional and is set as white by default.
+ This is the colour of the text
+
+ IMPORTANT: I use the Queens English so please note the word "colour" has a 'u' in it!
+
+ EXAMPLE CODE:
+ -------------
+ current_text = 'This is a string of text which should be changed to dodgerblue and white with every first letter capitalised'
+ mytext = koding.Colour_Text(text=current_text, colour1='dodgerblue', colour2='white')
+ xbmc.log(current_text)
+ xbmc.log(mytext)
+ dialog.ok('CURRENT TEXT', current_text)
+ dialog.ok('NEW TEXT', mytext)
+
+------------------------------------------------------------------------------------------
+
+FIND IN TEXT:
+Regex through some text and return a list of matches.
+Please note this will return a LIST so even if only one item is found
+you will still need to access it as a list, see example below.
+
+ CODE: Find_In_Text(content, start, end, [show_errors])
+
+ AVAILABLE PARAMS:
+
+ (*) content - This is the string to search
+
+ (*) start - The start search string
+
+ (*) end - The end search string
+
+ show_errors - Default is False, if set to True the code will show help
+ dialogs for bad code.
+
+ EXAMPLE CODE:
+ -------------
+ textsearch = 'This is some text so lets have a look and see if we can find the words "lets have a look"'
+ dialog.ok('ORIGINAL TEXT','Below is the text we\'re going to use for our search:','[COLOR dodgerblue]%s[/COLOR]'%textsearch)
+ search_result = koding.Find_In_Text(textsearch, 'text so ', ' and see')
+ dialog.ok('SEARCH RESULT','You searched for the start string of "text so " and the end string of " and see".','','Your result is: [COLOR dodgerblue]%s[/COLOR]' % search_result[0])
+
+ # Please note: we know for a fact there is only one result which is why we're only accessing list item zero.
+ # If we were expecting more than one return we would probably do something more useful and loop through in a for loop.
+
+------------------------------------------------------------------------------------------
+
+FUZZY MATCH:
+Send through a list of items and try to match against the search string.
+This will match where the search_string exists in the list or an item in
+the list exists in the search_string.
+
+ CODE: Fuzzy_Search(search_string, search_list, [strip])
+
+ AVAILABLE PARAMS:
+
+ (*) search_string - This is the string to search for
+
+ (*) search_list - The list of items to search through
+
+ replace_strings - Optionally send through a list of strings you want to
+ replace. For example you may want to search for "West Ham United" but in
+ the list you've sent through they've abbreviated it to "West Ham Utd FC". In
+ this case we might want to send through a replace_strings list of:
+
+ (["united","utd"], ["fc",""])
+
+ This will remove any instances of "FC" from the search and it will replace
+ instances of "united" to "utd". The code will convert everythig to lowercase
+ so it doesn't matter what case you use in these searches.
+
+ EXAMPLE CODE:
+ -------------
+ my_search = 'west ham utd'
+ my_list = ['west ham united', 'west ham utd', 'rangers fc', 'Man City', 'West Ham United FC', 'Fulham FC', 'West Ham f.c']
+ my_replace = (["united","utd"], ["fc",""], ["f.c",""])
+ dialog.ok('FUZZY SEARCH','Let\'s search for matches similar to "west ham utd" in the list:\n\n%s'%my_list)
+ search_result = koding.Fuzzy_Search(my_search, my_list, my_replace)
+ good = ', '.join(search_result)
+ bad = ''
+ for item in my_list:
+ if item not in search_result:
+ bad += item+', '
+ dialog.ok('RESULTS FOUND','[COLOR=dodgerblue]SEARCH:[/COLOR] %s\n[COLOR=lime]GOOD:[/COLOR] %s\n[COLOR=cyan]BAD:[/COLOR] %s'%(my_search,good,bad))
+
+------------------------------------------------------------------------------------------
+
+GENERATE RANDOM PASSWORD:
+This will generate a random string made up of uppercase & lowercase ASCII
+characters and digits - it does not contain special characters.
+
+ CODE: ID_Generator([size])
+ size is an optional paramater.
+
+ AVAILABLE PARAMS:
+
+ size - just send through an integer, this is the length of the string you'll get returned.
+ So if you want a password generated that's 20 characters long just use ID_Generator(20). The default is 15.
+
+ EXAMPLE CODE:
+ -------------
+ my_password = koding.ID_Generator(20)
+ dialog.ok('ID GENERATOR','Password generated:', '', '[COLOR=dodgerblue]%s[/COLOR]' % my_password)
+
+------------------------------------------------------------------------------------------
+
+LANGUAGE STRINGS:
+This will return the relevant language skin as set in the
+resources/language folder for your add-on. By default you'll get
+the language string returned from your current running add-on
+but if you send through another add-on id you can grab from
+any add-on or even the built-in kodi language strings.
+
+ CODE: String(code, [source])
+
+ AVAILABLE PARAMS:
+
+ (*) code - This is the language string code set in your strings.po file.
+
+ source - By default this is set to a blank string and will
+ use your current add-on id. However if you want to pull the string
+ from another add-on just enter the add-on id in here. If you'd prefer
+ to pull from the built-in kodi resources files just set as 'system'.
+
+ EXAMPLE CODE:
+ -------------
+ kodi_string = koding.String(code=10140, source='system')
+ koding_string = koding.String(code=30825, source='script.module.python.koding.aio')
+ dialog.ok('SYSTEM STRING','The string [COLOR=dodgerblue]10140[/COLOR] pulled from the default system language resources is:','[COLOR=gold]%s[/COLOR]' % kodi_string)
+ dialog.ok('PYTHON KODING STRING','The string [COLOR=dodgerblue]30825[/COLOR] pulled from the Python Koding language resources is:','[COLOR=gold]%s[/COLOR]' % koding_string)
+
+------------------------------------------------------------------------------------------
+
+LIST FROM DICTIONARY:
+Send through a dictionary and return a list of either the keys or values.
+Please note: The returned list will be sorted in alphabetical order.
+
+ CODE: List_From_Dict(mydict,[use_key])
+
+ AVAILABLE PARAMS:
+
+ (*) mydict - This is the dictionary (original data) you want to traverse through.
+
+ use_key - By default this is set to True and a list of all your dictionary keys
+ will be returned. Set to False if you'd prefer to have a list of the values returned.
+
+ EXAMPLE CODE:
+ -------------
+ raw_data = {'test1':'one','test2':'two','test3':'three','test4':'four','test5':'five'}
+ mylist1 = koding.List_From_Dict(mydict=raw_data)
+ mylist2 = koding.List_From_Dict(mydict=raw_data,use_key=False)
+ koding.Text_Box('LIST_FROM_DICT','Original dictionary: [COLOR dodgerblue]%s[/COLOR][CR][CR]Returned List (use_key=True): [COLOR dodgerblue]%s[/COLOR][CR]Returned List (use_key=False): [COLOR dodgerblue]%s[/COLOR]'%(raw_data,mylist1,mylist2))
+
+------------------------------------------------------------------------------------------
+
+MERGE DICTIONARIES:
+Send through any number of dictionaries and get a return of one merged dictionary.
+Please note: If you have duplicate keys the value will be overwritten by the final
+dictionary to be checked. So if you send through dicts a-f and the same key exists
+in dicts a,e,f the final value for that key would be whatever is set in 'f'.
+
+ CODE: Merge_Dicts(*dict_args)
+
+ AVAILABLE PARAMS:
+
+ (*) *dict_args - Enter as many dictionaries as you want, these will be merged
+ into one final dictionary. Please send each dictionary through as a new paramater.
+
+ EXAMPLE CODE:
+ -------------
+ dict1 = {'1':'one','2':'two'}
+ dict2 = {'3':'three','4':'four','5':'five'}
+ dict3 = {'6':'six','7':'seven'}
+ dict4 = {'1':'three','8':'eight'}
+
+ mytext = 'Original Dicts:\ndict1 = %s\ndict2 = %s\ndict3 = %s\ndict4 = %s\n\n'%(repr(dict1),repr(dict2),repr(dict3),repr(dict4))
+ mytext += 'Merged dictionaries (1-3): %s\n\n'%repr(koding.Merge_Dicts(dict1,dict2,dict3))
+ mytext += 'Merged dictionaries (1-4): %s\n\n'%repr(koding.Merge_Dicts(dict1,dict2,dict3,dict4))
+ mytext += "[COLOR = gold]IMPORTANT:[/COLOR]\nNotice how on the last run the key '1'now has a value of three.\nThis is because dict4 also contains that same key."
+ Text_Box('Merge_Dicts',mytext)
+
+------------------------------------------------------------------------------------------
+
+MD5 CHECK:
+Return the md5 value of string/file/directory, this will return just one unique value.
+
+ CODE: md5_check(src,[string])
+
+ AVAILABLE PARAMS:
+
+ (*) src - This is the source you want the md5 value of.
+ This can be a string, path of a file or path to a folder.
+
+ string - By default this is set to False but if you want to send
+ through a string rather than a path set this to True.
+
+ EXAMPLE CODE:
+ -------------
+ home = koding.Physical_Path('special://home')
+ home_md5 = koding.md5_check(home)
+ dialog.ok('md5 Check', 'The md5 of your home folder is:', '[COLOR=dodgerblue]%s[/COLOR]'%home_md5)
+
+ guisettings = xbmc.translatePath('special://profile/guisettings.xml')
+ guisettings_md5 = koding.md5_check(guisettings)
+ dialog.ok('md5 Check', 'The md5 of your guisettings.xml:', '[COLOR=dodgerblue]%s[/COLOR]'%guisettings_md5)
+
+ mystring = 'This is just a random text string we\'ll get the md5 value of'
+ myvalue = koding.md5_check(src=mystring,string=True)
+ dialog.ok('md5 String Check', 'String to get md5 value of:', '[COLOR=dodgerblue]%s[/COLOR]'%mystring)
+ dialog.ok('md5 String Check', 'The md5 value of your string:', '[COLOR=dodgerblue]%s[/COLOR]'%myvalue)
+
+------------------------------------------------------------------------------------------
+
+PARSE XML:
+Send through the contents of an XML file and pull out a list of matching
+items in the form of dictionaries. When checking your results you should
+allow for lists to be returned, by default each tag found in the xml will
+be returned as a string but if multiple entries of the same tag exists your
+dictionary item will be a list. Although this can be used for many uses this
+was predominantly added for support of XML's which contain multiple links to video
+files using things like . When checking to see if a string or list has been
+returned you can use the Data_Type function from Koding which will return 'str' or 'list'.
+
+ CODE: Parse_XML(source, block, tags)
+
+ AVAILABLE PARAMS:
+
+ source - This is the original source file, this must already be read into
+ memory as a string so made sure you've either used Open_URL or Text_File to
+ read the contents before sending through.
+
+ block - This is the master tag you want to use for creating a dictionary of items.
+ For example if you have an xml which contains multiple tags called and you wanted
+ to create a dictionary of sub items found in each of these you would just use 'item'.
+
+ tags - This is a list of tags you want to return in your dictionary, so lets say each
+ section contains , and tags you can return a dictionary of all those
+ items by sending through ['link','title','thumb']
+
+ EXAMPLE CODE:
+ -------------
+ dialog.ok('DICTIONARY OF ITEMS','We will now attempt to return a list of the source details pulled from the official Kodi repository addon.xml')
+ xml_file = koding.Physical_Path('special://xbmc/addons/repository.xbmc.org/addon.xml')
+ xml_file = koding.Text_File(xml_file,'r')
+ xbmc.log(xml_file,2)
+ repo_details = koding.Parse_XML(source=xml_file, block='extension', tags=['info','checksum','datadir'])
+ counter = 0
+ for item in repo_details:
+ dialog.ok( 'REPO %s'%(counter+1),'info path: [COLOR dodgerblue]%s[/COLOR]\nchecksum path: [COLOR dodgerblue]%s[/COLOR]\ndatadir: [COLOR dodgerblue]%s[/COLOR]' % (repo_details[counter]['info'],repo_details[counter]['checksum'],repo_details[counter]['datadir']) )
+ counter += 1
+
+------------------------------------------------------------------------------------------
+
+REMOVE FORMATTING FROM TEXT:
+This will cleanup a Kodi string, it can remove color, bold and italic tags as well as
+preceding spaces, dots and dashes. Particularly useful if you want to show the names of
+add-ons in alphabetical order where add-on names have deliberately had certain formatting
+added to them to get them to always show at the top of lists.
+
+ CODE: Remove_Formatting(string, [color, bold, italic, spaces, dots, dashes])
+
+ AVAILABLE PARAMS:
+
+ (*) string - This is string you want to remove formatting from.
+
+ color - By default this is set to true and all references to the color tag
+ will be removed, set this to false if you don't want color formatting removed.
+
+ bold - By default this is set to true and all references to the bold tag
+ will be removed, set this to false if you don't want bold formatting removed.
+
+ italic - By default this is set to true and all references to the italic tag
+ will be removed, set this to false if you don't want italic formatting removed.
+
+ spaces - By default this is set to true and any spaces at the start of the text
+ will be removed, set this to false if you don't want the spaces removed.
+
+ dots - By default this is set to true and any dots (.) at the start of the text
+ will be removed, set this to false if you don't want the dots removed.
+
+ dashes - By default this is set to true and any dashes (-) at the start of the text
+ will be removed, set this to false if you don't want the dashes removed.
+
+ EXAMPLE CODE:
+ -------------
+ mystring = '...-- [I]This[/I] is the [COLOR dodgerblue]ORIGINAL[/COLOR] [B][COLOR cyan]TEXT[/COLOR][/B]'
+ dialog.ok('ORIGINAL TEXT','Below is the original text we\'re going to try and clean up:[CR]%s'%mystring)
+ dialog.ok('DOTS REMOVED','[COLOR gold]Original:[/COLOR][CR]%s[CR][COLOR gold]This is with only dots set to True:[/COLOR][CR]%s'%(mystring,koding.Remove_Formatting(mystring, color=False, bold=False, italic=False, spaces=False, dots=True, dashes=False)))
+ dialog.ok('DOTS & DASHES REMOVED','[COLOR gold]Original:[/COLOR][CR]%s[CR][COLOR gold]This is with dots & dashes set to True:[/COLOR][CR]%s'%(mystring,koding.Remove_Formatting(mystring, color=False, bold=False, italic=False, spaces=False, dots=True, dashes=True)))
+ dialog.ok('DOTS, DASHES & SPACES REMOVED','[COLOR gold]Original:[/COLOR][CR]%s[CR][COLOR gold]This is with dots, dashes & spaces set to True:[/COLOR][CR]%s'%(mystring,koding.Remove_Formatting(mystring, color=False, bold=False, italic=False, spaces=True, dots=True, dashes=True)))
+ dialog.ok('ALL FORMATTING REMOVED','[COLOR gold]Original:[/COLOR][CR]%s[CR][COLOR gold]This is with all options set to True:[/COLOR][CR]%s'%(mystring,koding.Remove_Formatting(mystring)))
+
+------------------------------------------------------------------------------------------
+
+SPLIT LIST:
+Send through a list and split it up into multiple lists. You can choose to create
+lists of every x amount of items or you can split at every nth item and only include
+specific items in your new list.
+
+ CODE: Split_List(source, split_point, include)
+
+ AVAILABLE PARAMS:
+
+ source - This is the original list you want split
+
+ split_point - This is the postition you want to split your list at. For example:
+ original list: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]
+ Lets say we want to split it at every 5 items the split point would be 5
+
+ include - You have 3 options here:
+
+ 'all' - This will add all items to your lists, so in the example above you will
+ get a return of ([1,2,3,4,5],[6,7,8,9,10],[11,12,13,14,15])
+
+ [] - Send through a list of positions you want to include, based on the example
+ above and using include=[0,1,3] you will get a return of ([1,2,4],[6,7,9],[11,12,14])
+
+ int - Send through an integer and it will return everything up to that position,
+ based on the example above and using include=3 you will get a return of
+ ([1,2,3],[6,7,8],[11,12,13])
+
+
+ EXAMPLE CODE:
+ -------------
+ my_list = ['one','two','three','four','five','six','seven','eight','nine','ten','eleven','twelve','thirteen','fourteen','fifteen']
+ dialog.ok('SPLIT LIST 1','We will now attempt to split the following list into blocks of 5:\n%s'%my_list)
+ newlist = koding.Split_List(source=my_list, split_point=5)
+ dialog.ok('RESULTS','Our returned var:\n%s'%newlist)
+ dialog.ok('SPLIT LIST 2','We will now attempt to split the same list at every 5th item position and only show items [0,1,3]')
+ newlist = koding.Split_List(source=my_list, split_point=5, include=[0,1,3])
+ dialog.ok('RESULTS','Our returned var:\n%s'%newlist)
+ dialog.ok('SPLIT LIST 3','We will now attempt to split the same list at every 5th item position and only show the first 3 items.')
+ newlist = koding.Split_List(source=my_list, split_point=5, include=3)
+ dialog.ok('RESULTS','Our returned var:\n%s'%newlist)
+
+------------------------------------------------------------------------------------------
+
+SPLIT STRING INTO LINES OF X AMOUNT OF CHARACTERS:
+Splits up a piece of text into a list of lines x amount of chars in length.
+
+ CODE: koding.Split_Lines(raw_string, size)
+
+ AVAILABLE PARAMS:
+
+ (*) raw_string - This is the text you want split up into lines
+
+ (*) size - This is the maximum size you want the line length to be (in characters)
+
+ EXAMPLE CODE:
+ -------------
+ raw_string = 'This is some test code, let\'s take a look and see what happens if we split this up into lines of 20 chars per line'
+ dialog.ok('ORIGINAL TEXT',raw_string)
+ my_list = koding.Split_Lines(raw_string,20)
+ koding.Text_Box('List of lines',str(my_list))
+
+------------------------------------------------------------------------------------------
+
+TABLE CONVERT:
+Open a web page which a table and pull out the contents of that table
+into a list of dictionaries with your own custom keys.
+
+ CODE: Table_Convert(url, contents, table)
+
+ AVAILABLE PARAMS:
+
+ url - The url you want to open and pull data from
+
+ contents - Send through a dictionary of the keys you want to assign to
+ each of the cells. The format would be: {my_key : position}
+ You can pull out as many cells as you want, so if your table has 10 columns
+ but you only wanted to pull data for cells 2,4,5,6,8 then you could do so
+ by setting contents to the following params:
+ contents = {"name 1":2, "name 2":4, "name 3":5, "name 4":6, "name 5":8}
+
+ table - By default this is set to zero, this is to be used if there's
+ multiple tables on the page you're accessing. Remeber to start at zero,
+ so if you want to access the 2nd table on the page it will be table=1.
+
+ EXAMPLE CODE:
+ -------------
+ dialog.ok('TABLE DATA','Let\'s pull some details from the proxy list table found at:\nhttps://free-proxy-list.net.')
+ proxies = koding.Table_Convert(url='https://free-proxy-list.net', contents={"ip":0,"port":1}, table=0)
+ mytext = '[COLOR dodgerblue]Here are some proxies:[/COLOR]\n'
+ for item in proxies:
+ mytext += '\nIP: %s\nPort: %s\n[COLOR steelblue]----------------[/COLOR]'%(item['ip'],item['port'])
+ Text_Box('MASTER PROXY LIST',mytext)
+
+------------------------------------------------------------------------------------------
+
+V I D E O T O O L S
+
+------------------------------------------------------------------------------------------
+
+CHECK IF VIDEO PLAYBACK IS SUCCESSFUL:
+This function will return true or false based on video playback. Simply start a stream
+(whether via an add-on, direct link to URL or local storage doesn't matter), the code will
+then work out if playback is successful. This uses a number of checks and should take into
+account all potential glitches which can occur during playback. The return should happen
+within a second or two of playback being successful (or not).
+
+ CODE: Check_Playback()
+
+ AVAILABLE PARAMS:
+
+ ignore_dp - By default this is set to True but if set to False
+ this will ignore the DialogProgress window. If you use a DP while
+ waiting for the stream to start then you'll want to set this True.
+ Please bare in mind the reason this check is in place and enabled
+ by default is because some streams do bring up a DialogProgress
+ when initiated (such as f4m proxy links) and disabling this check
+ in those circumstances can cause false positives.
+
+ timeout - This is the amount of time you want to allow for playback
+ to start before sending back a response of False. Please note if
+ ignore_dp is set to True then it will also add a potential 10s extra
+ to this amount if a DialogProgress window is open. The default setting
+ for this is 10s.
+
+ EXAMPLE CODE:
+ -------------
+ xbmc.Player().play('http://totalrevolution.tv/videos/python_koding/Browse_To_Folder.mov')
+ isplaying = koding.Check_Playback()
+ if isplaying:
+ dialog.ok('PLAYBACK SUCCESSFUL','Congratulations, playback was successful')
+ xbmc.Player().stop()
+ else:
+ dialog.ok('PLAYBACK FAILED','Sorry, playback failed :(')
+
+------------------------------------------------------------------------------------------
+
+LAST PLAYED (OR CURRENTLY PLAYING) FILE:
+Return the link of the last played (or currently playing) video.
+This differs to the built in getPlayingFile command as that only shows details
+of the current playing file, these details can differ to the url which was
+originally sent through to initiate the stream. This Last_Played function
+directly accesses the database to get the REAL link which was initiated and
+will even return the plugin path if it's been played through an external add-on.
+
+ CODE: Last_Played()
+
+ EXAMPLE CODE:
+ -------------
+ if koding.Play_Video('http://totalrevolution.tv/videos/python_koding/Browse_To_Folder.mov'):
+ xbmc.sleep(3000)
+ xbmc.Player().stop()
+ last_vid = Last_Played()
+ dialog.ok('VIDEO LINK','The link we just played is:\n\n%s'%last_vid)
+ else:
+ dialog.ok('PLAYBACK FAILED','Sorry this video is no longer available, please try using a different video link.')
+
+------------------------------------------------------------------------------------------
+
+M3U / M3U8 PLAYLIST SELECTOR:
+Send through an m3u/m3u8 playlist and have the contents displayed via a dialog select.
+The return will be a dictionary of 'name' and 'url'. You can send through either
+a locally stored filepath or an online URL.
+
+This function will try it's best to pull out the relevant playlist details even if the
+web page isn't a correctly formatted m3u playlist (e.g. an m3u playlist embedded into
+a blog page).
+
+ CODE: M3U_Selector(url, [post_type, header])
+
+ AVAILABLE PARAMS:
+ (*) url - The location of your m3u file, this can be local or online
+
+ post_type - If you need to use POST rather than a standard query string
+ in your url set this to 'post', by default it's set to 'get'.
+
+ header - This is the header you want to appear at the top of your dialog
+ selection window, by default it's set to "Stream Selection"
+
+ EXAMPLE CODE:
+ -------------
+ dialog.ok('M3U SELECTOR','We will now call this function using the following url:','','[COLOR dodgerblue]http://totalrevolution.tv/videos/playlists/youtube.m3u[/COLOR]')
+
+ # This example uses YouTube plugin paths but any playable paths will work
+ vid = koding.M3U_Selector(url='http://totalrevolution.tv/videos/playlists/youtube.m3u')
+
+
+ # Make sure there is a valid link returned
+ if vid:
+ playback = koding.Play_Video(video=vid['url'], showbusy=False)
+ if playback:
+ dialog.ok('SUCCESS!','Congratulations the playback was successful!')
+ xbmc.Player().stop()
+ else:
+ dialog.ok('OOPS!','Looks like something went wrong there, the playback failed. Check the links are still valid.')
+
+------------------------------------------------------------------------------------------
+
+PLAY VIDEO:
+This will attempt to play a video and return True or False on
+whether or not playback was successful. This function is similar
+to Check_Playback but this actually tries a number of methods to
+play the video whereas Check_Playback does not actually try to
+play a video - it will just return True/False on whether or not
+a video is currently playing.
+
+If you have m3u or m3u8 playlist links please use the M3U_Selector
+function to get the final resolved url.
+
+ CODE: Play_Video(video, [showbusy, content, ignore_dp, timeout, item])
+
+ AVAILABLE PARAMS:
+
+ (*) video - This is the path to the video, this can be a local
+ path, online path or a channel number from the PVR.
+
+ showbusy - By default this is set to True which means while the
+ function is attempting to playback the video the user will see the
+ busy dialog. Set to False if you prefer this not to appear but do
+ bare in mind a user may navigate to another section and try playing
+ something else if they think this isn't doing anything.
+
+ content - By default this is set to 'video', however if you're
+ passing through audio you may want to set this to 'music' so the
+ system can correctly set the tags for artist, song etc.
+
+ ignore_dp - By default this is set to True but if set to False
+ this will ignore the DialogProgress window. If you use a DP while
+ waiting for the stream to start then you'll want to set this True.
+ Please bare in mind the reason this check is in place and enabled
+ by default is because some streams do bring up a DialogProgress
+ when initiated (such as f4m proxy links) and disabling this check
+ in those circumstances can cause false positives.
+
+ timeout - This is the amount of time you want to allow for playback
+ to start before sending back a response of False. Please note if
+ ignore_dp is set to True then it will also add a potential 10s extra
+ to this amount if a DialogProgress window is open. The default setting
+ for this is 10s.
+
+ item - By default this is set to None and in this case the metadata
+ will be auto-populated from the previous Add_Dir so you'll just get the
+ basics like title, thumb and description. If you want to send through your
+ own metadata in the form of a dictionary you can do so and it will override
+ the auto-generation. If anything else sent through no metadata will be set,
+ you would use this option if you've already set metadata in a previous function.
+
+ player - By default this is set to xbmc.Player() but you can send through
+ a different class/function if required.
+
+ resolver - By default this is set to urlresolver but if you prefer to use
+ your own custom resolver then just send through that class when calling this
+ function and the link sent through will be resolved by your custom resolver.
+
+ EXAMPLE CODE:
+ -------------
+ isplaying = koding.Play_Video('http://totalrevolution.tv/videos/python_koding/Browse_To_Folder.mov')
+ if isplaying:
+ dialog.ok('PLAYBACK SUCCESSFUL','Congratulations, playback was successful')
+ xbmc.Player().stop()
+ else:
+ dialog.ok('PLAYBACK FAILED','Sorry, playback failed :(')
+------------------------------------------------------------------------------------------
+
+SLEEP IF PLAYBACK IS ACTIVE:
+This will allow you to pause code while kodi is playing audio or video
+
+ CODE: Sleep_If_Playback_Active()
+
+ EXAMPLE CODE:
+ -------------
+ dialog.ok('PLAY A VIDEO','We will now attempt to play a video, once you stop this video you should see a dialog.ok message.')
+ xbmc.Player().play('http://download.blender.org/peach/bigbuckbunny_movies/big_buck_bunny_720p_stereo.avi')
+ xbmc.sleep(3000) # Give kodi enough time to load up the video
+ koding.Sleep_If_Playback_Active()
+ dialog.ok('PLAYBACK FINISHED','The playback has now been finished so this dialog code has now been initiated')
+
+------------------------------------------------------------------------------------------
+
+TEST LINKS:
+Send through a link and test whether or not it's playable on other devices.
+Many links include items in the query string which lock the content down to your
+IP only so what may open fine for you may not open for anyone else!
+
+This function will attempt to load the page using a proxy. If when trying to access
+the link via a proxy the header size and content-type match then we assume the
+link will play on any device. This is not fool proof and could potentially return
+false positives depending on the security used on the website being accessed.
+
+The return you'll get is a dictionary of the following items:
+
+ 'plugin_path' - This will have the path for a plugin, it means the stream was
+ originally passed through an add-on to get the final link. If this is not set
+ to None then it "should" work on any device so long as that add-on is installed
+ (e.g. YouTube).
+
+ 'url' - This is the final resolved url which Kodi was playing, you need to check
+ the status though to find out whether or not that link is locked to your IP only.
+
+ 'status' - This will return one of the following status codes:
+ good - The link should work on all IPs.
+
+ bad_link - The link was not valid, won't even play on your current Kodi setup.
+
+ proxy_fail - None of the proxies sent through worked.
+
+ locked - The url only works on this device, if this is the case consider using
+ the plugin_path which should generally work on all devices (although this does
+ depend on how the developer of that add-on coded up their add-on).
+
+ CODE: Link_Tester([proxy_list, url, ip_col, port_col, table])
+
+ AVAILABLE PARAMS:
+
+ video - This is the url of the video you want to check
+
+ local_check - By default this is set to True and this function will first of
+ all attempt to play the video locally with no proxy just to make sure the
+ link is valid in the first place. If you want to skip this step then set
+ this to False.
+
+ proxy_list - If you already have a list of proxies you want to test with
+ send them through in the form of a list of dictionaries. Use the following
+ format: [{"ip":"0.0.0.0","port":"80"},{"ip":"127.0.0.1","port":"8080"}]
+
+ proxy_url - If you want to scrape for online proxies and loop through until a
+ working one has been found you can set the url here. If using this then
+ proxy_list can be left as the default (None). If you open this Link_Tester
+ function with no params the defaults are setup to grab from:
+ free-proxy-list.net but there is no guarantee this will always
+ work, the website may well change it's layout/security over time.
+
+ ip_col - If you've sent through a proxy_url then you'll need to set a column number
+ for where in the table the IP address is stored. The default is 0
+
+ port_col - If you've sent through a proxy_url then you'll need to set a column number
+ for where in the table the port details are stored. The default is 1
+
+ table - If you've sent through a proxy_url then you'll need to set a table number.
+ The default is 0 - this presumes we need to use the first html table found on the
+ page, if you require a different table then alter accordingly - remember zero is the
+ first instance so if you want the 3rd table on the page you would set to 2.
+
+ EXAMPLE CODE:
+ -------------
+ vid_test = Link_Tester(video='http://totalrevolution.tv/videos/python_koding/Browse_To_Folder.mov')
+ if vid_test['status'] == 'bad_link':
+ dialog.ok('BAD LINK','The link you sent through cannot even be played on this device let alone another one!')
+ elif vid_test['status'] == 'proxy_fail':
+ dialog.ok('PROXIES EXHAUSTED','It was not possible to get any working proxies as a result it\'s not possible to fully test whether this link will work on other devices.')
+ elif vid_test['status'] == 'locked':
+ dialog.ok('NOT PLAYABLE','Although you can play this link locally the tester was unable to play it when using a proxy so this is no good.')
+ if vid_test['plugin_path']:
+ dialog.ok('THERE IS SOME GOOD NEWS!','Although the direct link for this video won\'t work on other IPs it "should" be possible to open this using the following path:\n[COLOR dodgerblue]%s[/COLOR]'%vid_test['plugin_path'])
+ else:
+ dialog.ok('WORKING!!!','Congratulations this link can be resolved and added to your playlist.')
+
+------------------------------------------------------------------------------------------
+
+W E B T O O L S
+
+------------------------------------------------------------------------------------------
+
+CLEANUO URL:
+Clean a url, removes whitespaces and common buggy formatting when pulling from websites
+
+ CODE: Cleanup_URL(url)
+
+ AVAILABLE PARAMS:
+
+ (*) url - This is the main url you want cleaned up.
+
+ EXAMPLE CODE:
+ -------------
+ raw_url = '" http://test.com/video/"/'
+ clean_url = koding.Cleanup_URL(raw_url)
+ dialog.ok('CLEANUP URL', 'Orig: %s'%raw_url,'Clean: %s'%clean_url)
+
+------------------------------------------------------------------------------------------
+
+DOWNLOAD:
+This will download a file, currently this has to be a standard download link which doesn't require cookies/login.
+
+ CODE: Download(src,dst,[dp])
+ dp is optional, by default it is set to false
+
+ AVAILABLE PARAMS:
+
+ (*) src - This is the source file, the URL to your download. If you attempted to download an item but it's not behaving the way you think it should (e.g. a zip file not unzipping) then change the extension of the downloaded file to .txt and open up in a text editor. You'll most likely find it's just a piece of text that was returned from the URL you gave and it should have details explaining why it failed. Could be that's the wrong URL, it requires some kind of login, it only accepts certain user-agents etc.
+
+ (*) dst - This is the destination file, make sure it's a physical path and not "special://...". Also remember you need to add the actual filename to the end of the path, so if we were downloading something to the "downloads" folder and we wanted the file to be called "test.txt" we would use this path: dst = "downloads/test.txt". Of course the downloads folder would actually need to exist otherwise it would fail and based on this poor example the downloads folder would be at root level of your device as we've not specified a path prior to that so it just uses the first level that's accessible.
+
+ dp - This is optional, if you pass through the dp function as a DialogProgress() then you'll get to see the progress of the download. If you choose not to add this paramater then you'll just get a busy spinning circle icon until it's completed. See the example below for a dp example.
+
+ timeout - By default this is set to 5. This is the max. amount of time you want to allow for checking whether or
+ not the url is a valid link and can be accessed via the system.
+
+ EXAMPLE CODE:
+ -------------
+ src = 'http://noobsandnerds.com/portal/Bits%20and%20bobs/Documents/user%20guide%20of%20the%20gyro%20remote.pdf'
+ dst = 'special://home/remote.pdf'
+ dp = xbmcgui.DialogProgress()
+ dp.create('Downloading File','Please Wait')
+ koding.Download(src,dst,dp)
+ dialog.ok('DOWNLOAD COMPLETE','Your download is complete, please check your home Kodi folder. There should be a new file called remote.pdf.')
+ dialog.ok('DELETE FILE','Click OK to delete the downloaded file.')
+ xbmcvfs.delete(dst)
+
+------------------------------------------------------------------------------------------
+
+GRAB THE EXTENSION OF A URL:
+Return the extension of a url
+
+ CODE: Get_Extension(url)
+
+ AVAILABLE PARAMS:
+
+ (*) url - This is the url you want to grab the extension from
+
+ EXAMPLE CODE:
+ -------------
+ dialog.ok('ONLINE FILE','We will now try and get the extension of the file found at this URL:','','[COLOR=dodgerblue]http://www.sample-videos.com/video/mp4/720/big_buck_bunny_720p_1mb.mp4[/COLOR]')
+ url_extension = koding.Get_Extension('http://www.sample-videos.com/video/mp4/720/big_buck_bunny_720p_1mb.mp4')
+ dialog.ok('FILE EXTENSION','The file extension of this Big Buck Bunny sample is:','','[COLOR=dodgerblue]%s[/COLOR]'%url_extension)
+
+------------------------------------------------------------------------------------------
+
+OPEN URL:
+If you need to pull the contents of a webpage it's very simple to do so by using this function.
+This uses the Python Requests module, for more detailed info on how the params work
+please look at the following link: http://docs.python-requests.org/en/master/user/advanced/
+
+IMPORTANT: This function will attempt to convert a url with a query string into the
+correct params for a post or get command but I highly recommend sending through your
+query string as a dictionary using the payload params. It's much cleaner and is a
+safer way of doing things, if you send through your url with a query string attached
+then I take no responsibility if it doesn't work!
+
+ CODE: Open_URL(url,[post_type,payload,headers,cookies,auth,timeout,cookiejar])
+
+ AVAILABLE PARAMS:
+
+ url - This is the main url you want to send through. Send it through
+ as a query string format even if it's a post.
+
+ post_type - By default this is set to 'get' but this can be set to 'post',
+ if set to post the query string will be split up into a post format automatically.
+
+ payload - By default this is not used but if you just want a standard
+ basic Open_URL function you can add a dictionary of params here. If you
+ don't enter anything in here the function will just split up your url
+ accordingly. Make sure you read the important information at the top
+ of this tutorial text.
+
+ headers - Optionally send through headers in form of a dictionary.
+
+ cookies - If set to true your request will send through and store cookies.
+
+ auth - User/pass details
+
+ timeout - Optionally set a timeout for the request.
+
+ cookiejar - An name for the location to store cookies. By default it's
+ set to addon_data//cookies/cookiejar but if you have multiple
+ websites you access then you may want to use a separate filename for each site.
+
+ proxies - Use a proxy for accessing the link, see requests documentation for full
+ information but essentially you would send through a dictionary like this:
+ proxies = {"http":"http://10.10.1.10:3128","htts":"https://10.10.1.10:3128"}
+
+ EXAMPLE CODE:
+ -------------
+ dialog.ok('OPEN FORUM PAGE','We will attempt to open the noobsandnerds forum page and return the contents. You will now be asked for your forum credentials.')
+ myurl = 'http://noobsandnerds.com/support/index.php'
+ username = koding.Keyboard('ENTER USERNAME')
+ password = koding.Keyboard('ENTER PASSWORD')
+ params = {"username":username,"password":password}
+ xbmc.log(repr(params),2)
+ url_contents = koding.Open_URL(url=myurl, payload=params, post_type='get')
+ koding.Text_Box('CONTENTS OF WEB PAGE',url_contents)
+
+------------------------------------------------------------------------------------------
+
+VALIDATE LINK:
+Returns the code for a particular link, so for example 200 is a good link and 404 is a URL not found
+
+ CODE: Validate_Link(url,[timeout])
+
+ AVAILABLE PARAMS:
+
+ (*) url - This is url you want to check the header code for
+
+ timeout - An optional timeout integer for checking url (default is 30 seconds)
+
+ EXAMPLE CODE:
+ -------------
+ url_code = koding.Validate_Link('http://totalrevolution.tv')
+ if url_code == 200:
+ dialog.ok('WEBSITE STATUS','The website [COLOR=dodgerblue]totalrevolution.tv[/COLOR] is [COLOR=lime]ONLINE[/COLOR]')
+ else:
+ dialog.ok('WEBSITE STATUS','The website [COLOR=dodgerblue]totalrevolution.tv[/COLOR] is [COLOR=red]OFFLINE[/COLOR]')
+
+------------------------------------------------------------------------------------------
\ No newline at end of file
diff --git a/script.module.python.koding.aio/addon.xml b/script.module.python.koding.aio/addon.xml
new file mode 100644
index 0000000..d3ec895
--- /dev/null
+++ b/script.module.python.koding.aio/addon.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+ all
+ Python Koding All In One
+ Python Koding AIO contains a bunch of time saving modules that allows for quick and simple development.
+
+
+ Creative Commons 4.0-NC-ND
+ http://totalrevolution.tv/forum
+ http://totalrevolution.tv
+ 1.0 Stable release
+ https://github.com/totalrevolution/python-koding/tree/master/script.module.python.koding.aio
+
+
diff --git a/script.module.python.koding.aio/fanart.jpg b/script.module.python.koding.aio/fanart.jpg
new file mode 100644
index 0000000..57671dc
Binary files /dev/null and b/script.module.python.koding.aio/fanart.jpg differ
diff --git a/script.module.python.koding.aio/icon.png b/script.module.python.koding.aio/icon.png
new file mode 100644
index 0000000..cbfc634
Binary files /dev/null and b/script.module.python.koding.aio/icon.png differ
diff --git a/script.module.python.koding.aio/lib/.DS_Store b/script.module.python.koding.aio/lib/.DS_Store
new file mode 100644
index 0000000..e008e3b
Binary files /dev/null and b/script.module.python.koding.aio/lib/.DS_Store differ
diff --git a/script.module.python.koding.aio/lib/koding/__init__.py b/script.module.python.koding.aio/lib/koding/__init__.py
new file mode 100644
index 0000000..235273e
--- /dev/null
+++ b/script.module.python.koding.aio/lib/koding/__init__.py
@@ -0,0 +1,118 @@
+# -*- coding: utf-8 -*-
+
+# script.module.python.koding.aio
+# Python Koding AIO (c) by TOTALREVOLUTION LTD (support@trmc.freshdesk.com)
+
+# Python Koding AIO is licensed under a
+# Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.
+
+# You should have received a copy of the license along with this
+# work. If not, see http://creativecommons.org/licenses/by-nc-nd/4.0.
+
+# Please make sure you've read and understood the license, this code can NOT be used commercially
+# and it can NOT be modified and redistributed. If you're found to be in breach of this license
+# then any affected add-ons will be blacklisted and will not be able to work on the same system
+# as any other add-ons which use this code. Thank you for your cooperation.
+
+import os
+import re
+import shutil
+import sys
+import time
+import urllib
+import urllib2
+import xbmc
+import xbmcaddon
+import xbmcgui
+import xbmcvfs
+import inspect
+try:
+ import simplejson as json
+except:
+ import json
+
+from addons import *
+from android import *
+from database import *
+from directory import *
+from filetools import *
+from guitools import *
+from router import *
+from systemtools import *
+from tutorials import *
+from video import *
+from vartools import *
+from web import *
+
+def converthex(url):
+ """ internal command ~"""
+ import binascii
+ return binascii.unhexlify(url)
+
+try:
+ ADDON_ID = xbmcaddon.Addon().getAddonInfo('id')
+except:
+ ADDON_ID = Caller()
+
+AddonVersion = xbmcaddon.Addon(id=ADDON_ID).getAddonInfo('version')
+ORIG_ID = ADDON_ID
+
+TestID = ADDON_ID
+if not ADDON_ID.endswith(converthex('2e74657374')):
+ TestID = ADDON_ID+converthex('2e74657374')
+
+MODULE_ID = 'script.module.python.koding.aio'
+ADDON = xbmcaddon.Addon(id=ADDON_ID)
+THIS_MODULE = xbmcaddon.Addon(id=MODULE_ID)
+USERDATA = 'special://profile'
+ADDON_DATA = os.path.join(USERDATA,'addon_data')
+ADDONS = 'special://home/addons'
+PACKAGES = os.path.join(ADDONS,'packages')
+UPDATE_ICON = os.path.join(ADDONS,MODULE_ID,'resources','update.png')
+DEBUG = Addon_Setting(addon_id=ORIG_ID,setting=converthex('6465627567'))
+KODI_VER = int(float(xbmc.getInfoLabel("System.BuildVersion")[:2]))
+
+dialog = xbmcgui.Dialog()
+dp = xbmcgui.DialogProgress()
+
+if not xbmcvfs.exists(os.path.join(ADDON_DATA,ORIG_ID,'cookies')):
+ xbmcvfs.mkdirs(os.path.join(ADDON_DATA,ORIG_ID,'cookies'))
+#----------------------------------------------------------------
+# TUTORIAL #
+def dolog(string, my_debug=False, line_info=False):
+ """
+Print to the Kodi log but only if debugging is enabled in settings.xml
+
+CODE: koding.dolog(string, [my_debug])
+
+AVAILABLE PARAMS:
+
+ (*) string - This is your text you want printed to log.
+
+ my_debug - This is optional, if you set this to True you will print
+ to the log regardless of what the debug setting is set at in add-on settings.
+
+ line_info - By default this is set to True and will show the line number where
+ the dolog command was called from along with the filepath it was called from.
+
+EXAMPLE CODE:
+koding.dolog(string='Quick test to see if this gets printed to the log', my_debug=True, line_info=True)
+dialog.ok('[COLOR gold]CHECK LOGFILE 1[/COLOR]','If you check your log file you should be able to see a new test line we printed \
+and immediately below that should be details of where it was called from.')
+koding.dolog(string='This one should print without the line and file info', my_debug=True, line_info=False)
+dialog.ok('[COLOR gold]CHECK LOGFILE 2[/COLOR]','If you check your log file again you should now be able to see a new line printed \
+but without the file/line details.')
+~"""
+ import xbmc
+ if DEBUG == 'true' or my_debug:
+ try:
+ xbmc.log('### %s (%s) : %s'%(ADDON_ID,AddonVersion,string), level=xbmc.LOGNOTICE)
+ except:
+ xbmc.log(Last_Error(),level=xbmc.LOGNOTICE)
+ if line_info:
+ try:
+ from inspect import getframeinfo, stack
+ caller = getframeinfo(stack()[1][0])
+ xbmc.log('^ Line No. %s | File: %s'%(caller.lineno,caller.filename),level=xbmc.LOGNOTICE)
+ except:
+ xbmc.log(Last_Error(),level=xbmc.LOGNOTICE)
\ No newline at end of file
diff --git a/script.module.python.koding.aio/lib/koding/addons.py b/script.module.python.koding.aio/lib/koding/addons.py
new file mode 100644
index 0000000..a77aa57
--- /dev/null
+++ b/script.module.python.koding.aio/lib/koding/addons.py
@@ -0,0 +1,977 @@
+# -*- coding: utf-8 -*-
+
+# script.module.python.koding.aio
+# Python Koding AIO (c) by TOTALREVOLUTION LTD (support@trmc.freshdesk.com)
+
+# Python Koding AIO is licensed under a
+# Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.
+
+# You should have received a copy of the license along with this
+# work. If not, see http://creativecommons.org/licenses/by-nc-nd/4.0.
+
+# Please make sure you've read and understood the license, this code can NOT be used commercially
+# and it can NOT be modified and redistributed. If you're found to be in breach of this license
+# then any affected add-ons will be blacklisted and will not be able to work on the same system
+# as any other add-ons which use this code. Thank you for your cooperation.
+
+import datetime
+import os
+import sys
+import shutil
+import xbmc
+import xbmcaddon
+import xbmcgui
+import xbmcvfs
+
+import filetools
+
+ADDONS = 'special://home/addons'
+XBMC_PATH = xbmc.translatePath('special://xbmc')
+kodi_ver = int(float(xbmc.getInfoLabel("System.BuildVersion")[:2]))
+dialog = xbmcgui.Dialog()
+
+#----------------------------------------------------------------
+# TUTORIAL #
+def Addon_Genre(genre='adult',custom_url=''):
+ """
+Return a dictionary of add-ons which match a specific genre.
+
+CODE: Addon_Genre([genre, custom_url])
+
+AVAILABLE PARAMS:
+
+ genre - By default this is set to 'adult' which will return a dictionary of all known
+ adult add-ons. We recommend using the genre labels listed below as they are already in use
+ by some add-on developers, however you can of course create your very own genre keys in
+ your custom genre file if you wish.
+
+ custom_url - If you have your own custom url which returns a dictionary
+ of genres and add-ons you can enter it here. The page must adhere to the format shown below.
+
+ Recommended Genre Keys:
+ adult, anime, audiobooks, comedy, comics, documentary, food, gaming, health, howto, kids,
+ livetv, movies, music, news, podcasts, radio, religion, space, sports, subscription,
+ tech, trailers, tvshows, world
+
+ Correct Genre Dictionary Structure:
+ The dictionary must be a dictionary of genres with each genre dictionary containing keys for
+ each add-on ID and the value being the name you want displayed. See below for an example:
+ { "movies":{"plugin.video.mymovie":"My movie add-on","plugin.video.trailers":"My Trailer add-on"}, "sports":{"plugin.video.sports":"Sport Clips"} }
+
+EXAMPLE CODE:
+dialog.ok('ADD-ON GENRES','We will now list all known comedy based add-ons. If you have add-ons installed which you feel should be categorised as supplying comedy but they aren\'t then you can help tag them up correctly via the Add-on Portal at NaN.')
+comedy_addons = koding.Addon_Genre(genre='comedy')
+if comedy_addons:
+ my_return = 'LIST OF AVAILABLE COMEDY BASED ADD-ONS:\n\n'
+
+# Convert the dictionary into a list:
+ comedy_addons = comedy_addons.items()
+ for item in comedy_addons:
+ my_return += '[COLOR=gold]Name:[/COLOR] %s | [COLOR=dodgerblue]ID:[/COLOR] %s\n' % (item[0],item[1])
+ koding.Text_Box('[COLOR gold]COMEDY ADD-ONS[/COLOR]',my_return)
+~"""
+ import binascii
+ from __init__ import converthex
+ from filetools import Text_File
+ from systemtools import Timestamp
+ from vartools import Merge_Dicts
+ from web import Open_URL
+
+ download_new = True
+ local_path = binascii.hexlify('genre_list')
+ cookie_path = "special://profile/addon_data/script.module.python.koding.aio/cookies/"
+ custom_genres= "special://profile/addon_data/script.module.python.koding.aio/genres.txt"
+ final_path = os.path.join(cookie_path,local_path)
+ if not xbmcvfs.exists(cookie_path):
+ xbmcvfs.mkdirs(cookie_path)
+
+ if xbmcvfs.exists(final_path):
+ modified = xbmcvfs.Stat(final_path).st_mtime()
+ old = int(modified)
+ now = int(Timestamp('epoch'))
+# Add a 24hr wait so we don't kill server
+ if now < (modified+86400):
+ download_new = False
+
+# Create new file
+ if download_new and custom_url != '':
+ addon_list = Open_URL(custom_url)
+ Text_File(final_path, "w", addon_list)
+
+# Grab details of the relevant genre
+ if xbmcvfs.exists(final_path):
+ try:
+ addon_list = eval( Text_File(final_path, 'r') )
+ addon_list = addon_list[genre]
+ except:
+ xbmcvfs.delete(final_path)
+ addon_list = {}
+
+ if xbmcvfs.exists(custom_genres):
+ try:
+ custom_list = eval( Text_File(custom_genres, 'r') )
+ custom_list = custom_list[genre]
+ addon_list = Merge_Dicts(addon_list,custom_list)
+ except:
+ pass
+ return addon_list
+#----------------------------------------------------------------
+# TUTORIAL #
+def Addon_Info(id='',addon_id=''):
+ """
+Retrieve details about an add-on, lots of built-in values are available
+such as path, version, name etc.
+
+CODE: Addon_Setting(id, [addon_id])
+
+AVAILABLE PARAMS:
+
+ (*) id - This is the name of the id you want to retrieve.
+ The list of built in id's you can use (current as of 15th April 2017)
+ are: author, changelog, description, disclaimer, fanart, icon, id, name,
+ path, profile, stars, summary, type, version
+
+ addon_id - By default this will use your current add-on id but you
+ can access any add-on you want by entering an id in here.
+
+EXAMPLE CODE:
+dialog.ok('ADD-ON INFO','We will now try and pull name and version details for our current running add-on.')
+version = koding.Addon_Info(id='version')
+name = koding.Addon_Info(id='name')
+dialog.ok('NAME AND VERSION','[COLOR=dodgerblue]Add-on Name:[/COLOR] %s' % name,'[COLOR=dodgerblue]Version:[/COLOR] %s' % version)
+~"""
+ import xbmcaddon
+ if addon_id == '':
+ addon_id = Caller()
+ ADDON = xbmcaddon.Addon(id=addon_id)
+ if id == '':
+ dialog.ok('ENTER A VALID ID','You\'ve called the Addon_Info function but forgot to add an ID. Please correct your code and enter a valid id to pull info on (e.g. "version")')
+ else:
+ return ADDON.getAddonInfo(id=id)
+#----------------------------------------------------------------
+# TUTORIAL #
+def Addon_List(enabled=True, inc_new=False):
+ """
+Return a list of enabled or disabled add-ons found in the database.
+
+CODE: Addon_List([enabled, inc_new])
+
+AVAILABLE PARAMS:
+
+ enabled - By default this is set to True which means you'll
+ get a list of all the enabled add-ons found in addons*.db but
+ if you want a list of all the disabled ones just set this to
+ False.
+
+ inc_new - This will also add any new add-on folders found on
+ your system that aren't yet in the database (ie ones that have
+ been recently been manually extracted but not scanned in). By
+ default this is set to False.
+
+EXAMPLE CODE:
+enabled_list = Addon_List(enabled=True)
+disabled_list = Addon_List(enabled=False)
+my_return = ''
+
+for item in enabled_list:
+ my_return += '[COLOR=lime]ENABLED:[/COLOR] %s\n' % item
+for item in disabled_list:
+ my_return += '[COLOR=red]DISABLED:[/COLOR] %s\n' % item
+koding.Text_Box('ADDON STATUS',my_return)
+~"""
+ from database import DB_Query
+ from guitools import Text_Box
+ from filetools import DB_Path_Check, Get_Contents
+
+ enabled_list = []
+ disabled_list = []
+ addons_db = DB_Path_Check('addons')
+ on_system = DB_Query(addons_db,'SELECT addonID, enabled from installed')
+
+# Create a list of enabled and disabled add-ons already on system
+ for item in on_system:
+ if item["enabled"]:
+ enabled_list.append(item["addonID"])
+ else:
+ disabled_list.append(item["addonID"])
+
+ if inc_new:
+ my_addons = Get_Contents(path=ADDONS, exclude_list=['packages','temp'])
+ for item in my_addons:
+ addon_id = Get_Addon_ID(item)
+ if not addon_id in enabled_list and not addon_id in disabled_list:
+ disabled_list.append(addon_id)
+
+ if enabled:
+ return enabled_list
+ else:
+ return disabled_list
+#----------------------------------------------------------------
+# TUTORIAL #
+def Addon_Service(addons='all', mode='list', skip_service=[]):
+ """
+Send through an add-on id, list of id's or leave as the default which is "all". This
+will loop through the list of add-ons and return the ones which are run as services.
+
+This enable/disable feature will comment out the service lines, and does not stop a running
+service or start a service. This is designed more for if you've manually extracted a new
+add-on into your system and it isn't yet enabled. Occasionally if the add-ons have dependencies
+which are run as services then trying to enable them can cause Kodi to freeze.
+
+CODE: Addon_Service([addon,disable])
+
+AVAILABLE PARAMS:
+
+ addons - By default this is set to "all" but if there's a sepcific set of add-ons you
+ want to disable the service for just send through the id's in the form of a list.
+
+ mode - By default this is set to 'list' meaning you'll get a return of add-on folders
+ which contain an instance of service in the add-on.xml. You can set this to "disable" to
+ comment out the instances of service and similarly when you need to re-enable you can use
+ "enable" and that will uncomment out the service item. Please note that by uncommenting
+ the service will not automatically start - you'll need to reload the profile for that.
+
+ skip_service - When running the enable or disable mode you can choose to add a list of
+ add-ons you'd like to skip the process for. Of course you may be thinking why would I send
+ through a list of addons I want the service enabled/disabled for but then I also add them
+ to the skip_service list to say DON'T enable/disable - it makes no sense?! Well you'd be
+ correct that doesn't make any sense as presumably you've already filtered out the add-ons
+ you don't want affected, this command is designed more for those who don't send through a
+ list of add-ons and instead use the default "all" value for the addons paramater. This
+ then makes it very easy to just skip a handful of add-on services and enable all others.
+
+EXAMPLE CODE:
+dialog.ok('CHECKING FOR SERVICES','We will now check for all add-ons installed which contain services')
+service_addons = Addon_Service(mode='list')
+my_text = 'List of add-ons running as a service:\n\n'
+for item in service_addons:
+ my_text += item+'\n'
+koding.Text_Box('[COLOR gold]SERVICE ADDONS[/COLOR]',my_text)
+~"""
+ from filetools import Get_Contents, Physical_Path, Text_File
+ from vartools import Data_Type
+ from guitools import Text_Box
+ service_addons = []
+ if addons=='all':
+ addons = Get_Contents(path=ADDONS, exclude_list=['packages','temp'],full_path=False)
+ else:
+ if Data_Type(addons) == 'str':
+ addons = [addons]
+
+ if Data_Type(skip_service) == 'str':
+ skip_service = [skip_service]
+
+ service_line = '','')
+ Text_File(addon_path,'w',content.replace(line,replace_line))
+ break
+ return service_addons
+#----------------------------------------------------------------
+# TUTORIAL #
+def Addon_Setting(setting='',value='return_default',addon_id=''):
+ """
+Change or retrieve an add-on setting.
+
+CODE: Addon_Setting(setting, [value, addon_id])
+
+AVAILABLE PARAMS:
+
+ (*) setting - This is the name of the setting you want to access, by
+ default this function will return the value but if you add the
+ value param shown below it will CHANGE the setting.
+
+ value - If set this will change the setting above to whatever value
+ is in here.
+
+ addon_id - By default this will use your current add-on id but you
+ can access any add-on you want by entering an id in here.
+
+EXAMPLE CODE:
+dialog.ok('ADDON SETTING','We will now try and pull the language settings for the YouTube add-on')
+if os.path.exists(xbmc.translatePath('special://home/addons/plugin.video.youtube')):
+ my_setting = koding.Addon_Setting(setting='youtube.language',addon_id='plugin.video.youtube')
+ dialog.ok('YOUTUBE SETTING','[COLOR=dodgerblue]Setting name:[/COLOR] youtube.language','[COLOR=dodgerblue]Value:[/COLOR] %s' % my_setting)
+else:
+ dialog.ok('YOUTUBE NOT INSTALLED','Sorry we cannot run this example as you don\'t have YouTube installed.')
+~"""
+ import xbmcaddon
+ if addon_id == '':
+ addon_id = Caller()
+ ADDON = xbmcaddon.Addon(id=addon_id)
+ if value == 'return_default':
+ mysetting = ADDON.getSetting(setting)
+ return mysetting
+ else:
+ ADDON.setSetting(id=setting, value=value)
+#----------------------------------------------------------------
+# TUTORIAL #
+def Adult_Toggle(adult_list=[], disable=True, update_status=0):
+ """
+Remove/Enable a list of add-ons, these are put into a containment area until enabled again.
+
+CODE: Adult_Toggle(adult_list, [disable, update_status])
+
+AVAILABLE PARAMS:
+
+ (*) adult_list - A list containing all the add-ons you want to be disabled.
+
+ disable - By default this is set to true so any add-ons in the list sent
+ through will be disabled. Set to False if you want to enable the hidden add-ons.
+
+ update_status - When running this function it needs to disable the
+ auto-update of add-ons by Kodi otherwise it risks crashing. This
+ update_status paramater is the state you want Kodi to revert back to
+ once the toggle of add-ons has completed. By default this is set to 0
+ which is auto-update. You can also choose 1 (notify of updates) or 2
+ (disable auto updates).
+
+~"""
+ from filetools import End_Path, Move_Tree, Physical_Path
+
+ adult_store = Physical_Path("special://profile/addon_data/script.module.python.koding.aio/adult_store")
+ disable_list = []
+ if not xbmcvfs.exists(adult_store):
+ xbmcvfs.mkdirs(adult_store)
+ my_addons = Installed_Addons()
+ if disable:
+ for item in my_addons:
+ if item != None:
+ item = item["addonid"]
+ if item in adult_list:
+ disable_list.append(item)
+
+ Toggle_Addons(addon=disable_list, enable=False, safe_mode=True, refresh=True, update_status=update_status)
+ for item in disable_list:
+ try:
+ addon_path = xbmcaddon.Addon(id=item).getAddonInfo("path")
+ except:
+ addon_path = Physical_Path(os.path.join(ADDONS,item))
+ path_id = End_Path(addon_path)
+ if os.path.exists(addon_path):
+ Move_Tree(addon_path,os.path.join(adult_store,path_id))
+ else:
+ KODI_VER = int(float(xbmc.getInfoLabel("System.BuildVersion")[:2]))
+ addon_vault = []
+ if os.path.exists(adult_store):
+ for item in os.listdir(adult_store):
+ store_dir = os.path.join(adult_store,item)
+ addon_dir = os.path.join(ADDONS, item)
+ if os.path.exists(store_dir):
+ Move_Tree(store_dir,addon_dir)
+ addon_vault.append(item)
+
+ if KODI_VER >= 16:
+ Toggle_Addons(addon=addon_vault, safe_mode=True, refresh=True, update_status=update_status)
+ else:
+ Refresh(['addons','repos'])
+#----------------------------------------------------------------
+# TUTORIAL #
+def Caller(my_return='addon'):
+ """
+Return the add-on id or path of the script which originally called
+your function. If it's been called through a number of add-ons/scripts
+you can grab a list of paths that have been called.
+
+CODE: Caller(my_return)
+
+AVAILABLE PARAMS:
+
+ my_return - By default this is set to 'addon', view the options below:
+
+ 'addon' : Return the add-on id of the add-on to call this function.
+
+ 'addons': Return a list of all add-on id's called to get to this function.
+
+ 'path' : Return the full path to the script which called this function.
+
+ 'paths' : Return a list of paths which have been called to get to this
+ final function.
+
+EXAMPLE CODE:
+my_addon = koding.Caller(my_return='addon')
+my_addons = koding.Caller(my_return='addons')
+my_path = koding.Caller(my_return='path')
+my_paths = koding.Caller(my_return='paths')
+
+dialog.ok('ADD-ON ID', 'Addon id you called this function from:','[COLOR=dodgerblue]%s[/COLOR]' % my_addon)
+dialog.ok('SCRIPT PATH', 'Script which called this function:','[COLOR=dodgerblue]%s[/COLOR]' % my_path)
+
+addon_list = 'Below is a list of add-on id\'s which have been called to get to this final piece of code:\n\n'
+for item in my_addons:
+ addon_list += item+'\n'
+koding.Text_Box('ADD-ON LIST', addon_list)
+koding.Sleep_If_Window_Active(10147)
+path_list = 'Below is a list of scripts which have been called to get to this final piece of code:\n\n'
+for item in my_paths:
+ path_list += item+'\n'
+koding.Text_Box('ADD-ON LIST', path_list)
+~"""
+ import inspect
+ stack = inspect.stack()
+ last_stack = len(stack)-1
+ stack_array = []
+ addon_array = []
+ for item in stack:
+ last_stack = item[1].replace('','')
+ last_stack = last_stack.strip()
+ stack_array.append(last_stack)
+ try:
+ scrap,addon_id = last_stack.split('addons%s'%os.sep)
+ addon_id = addon_id.split(os.sep)[0]
+ addon_id = Get_Addon_ID(addon_id)
+ if addon_id not in addon_array:
+ addon_array.append(addon_id)
+ except:
+ pass
+
+ if my_return == 'addons':
+ return addon_array
+ if my_return == 'addon':
+ return addon_array[len(addon_array)-1]
+ if my_return == 'path':
+ return stack_array[len(stack_array)-1]
+ if my_return == 'paths':
+ return stack_array
+#----------------------------------------------------------------
+def Check_Deps(addon_path, depfiles = []):
+ import re
+ from filetools import Text_File
+ from __init__ import dolog
+ exclude_list = ['xbmc.gui', 'script.module.metahandler', 'metadata.common.allmusic.com',\
+ 'kodi.resource','xbmc.core','xbmc.metadata','xbmc.addon','xbmc.json','xbmc.python']
+ file_location = os.path.join(addon_path,'addon.xml')
+ if xbmcvfs.exists(file_location):
+ readxml = Text_File(file_location,'r')
+ dmatch = re.compile('import addon="(.+?)"').findall(readxml)
+ for requires in dmatch:
+ if not requires in exclude_list and not requires in depfiles:
+ depfiles.append(requires)
+ return depfiles
+#----------------------------------------------------------------
+# TUTORIAL #
+def Check_Repo(repo,show_busy=True,timeout=10):
+ """
+This will check the status of repo and return True if the repo is online or False
+if it contains paths that are no longer accessible online.
+
+IMPORTANT: If you're running an old version of Kodi which uses the old Python 2.6
+(OSX and Android lower than Kodi 17 or a linux install with old Python installed on system)
+you will get a return of False on https links regardless of their real status. This is due
+to the fact Python 2.6 cannot access secure links. Any still using standard http links
+will return the correct results.
+
+CODE: Check_Repo(repo, [show_busy, timeout])
+
+AVAILABLE PARAMS:
+
+ (*) repo - This is the name of the folder the repository resides in.
+ You can either use the full path or just the folder name which in 99.99%
+ of cases is the add-on id. If only using the folder name DOUBLE check first as
+ there are a handful which have used a different folder name to the actual add-on id!
+
+ show_busy - By default this is set to True and a busy dialog will show during the check
+
+ timeout - By default this is set to 10 (seconds) - this is the maximum each request
+ to the repo url will take before timing out and returning False.
+
+EXAMPLE CODE:
+repo_status = Check_Repo('special://xbmc',show_busy=False,timeout=10)
+if repo_status:
+ dialog.ok('REPO STATUS','The repository modules4all is: [COLOR=lime]ONLINE[/COLOR]')
+else:
+ dialog.ok('REPO STATUS','The repository modules4all is: [COLOR=red]OFFLINE[/COLOR]')
+~"""
+ import re
+
+ from __init__ import dolog
+ from filetools import Text_File
+ from guitools import Show_Busy
+ from web import Validate_Link
+ xbmc.log('### CHECKING %s'%repo,2)
+ status = True
+ if show_busy:
+ Show_Busy()
+ if not ADDONS in repo and not XBMC_PATH in repo:
+ repo_path = os.path.join(ADDONS,repo)
+ else:
+ repo_path = repo
+ repo_path = Physical_Path(repo_path)
+ xbmc.log(repo_path,2)
+ repo_path = os.path.join(repo_path,'addon.xml')
+ xbmc.log(repo_path,2)
+ if os.path.exists(repo_path):
+ content = Text_File(repo_path,'r')
+ md5_urls = re.findall(r'(.+?)', content, re.DOTALL)
+ for item in md5_urls:
+ link_status = Validate_Link(item,timeout)
+ dolog(item)
+ dolog('STATUS: %s'%link_status)
+ if link_status < 200 or link_status >= 400:
+ status = False
+ break
+ if show_busy:
+ Show_Busy(False)
+ return status
+ else:
+ if show_busy:
+ Show_Busy(False)
+ return False
+#----------------------------------------------------------------
+# TUTORIAL #
+def Default_Setting(setting='',addon_id='',reset=False):
+ """
+This will return the DEFAULT value for a setting (as set in resources/settings.xml)
+and optionally reset the current value back to this default. If you pass through
+the setting as blank it will return a dictionary of all default settings.
+
+CODE: Default_Setting(setting, [addon_id, reset])
+
+AVAILABLE PARAMS:
+
+ setting - The setting you want to retreive the value for.
+ Leave blank to return a dictionary of all settings
+
+ addon_id - This is optional, if not set it will use the current id.
+
+ reset - By default this is set to False but if set to true and it will
+ reset the current value to the default.
+
+EXAMPLE CODE:
+youtube_path = xbmc.translatePath('special://home/addons/plugin.video.youtube')
+if os.path.exists(youtube_path):
+ my_value = koding.Default_Setting(setting='youtube.region', addon_id='plugin.video.youtube', reset=False)
+ dialog.ok('YOUTUBE SETTING','Below is a default setting for plugin.video.youtube:','Setting: [COLOR=dodgerblue]youtube.region[/COLOR]','Value: [COLOR=dodgerblue]%s[/COLOR]' % my_value)
+else:
+ dialog.ok('YOUTUBE NOT INSTALLED','We cannot run this example as it uses the YouTube add-on which has not been found on your system.')
+~"""
+ import re
+ from filetools import Text_File
+ from vartools import Data_Type
+
+ if addon_id == '':
+ addon_id = Caller()
+ values = {}
+ addon_path = Addon_Info(id='path',addon_id=addon_id)
+ settings_path = os.path.join(addon_path,'resources','settings.xml')
+ content = Text_File(settings_path,'r').splitlines()
+ for line in content:
+ if 'id="' in line and 'default="' in line:
+ idx = re.compile('id="(.*?)"').findall(line)
+ idx = idx[0] if (len(idx) > 0) else ''
+ value = re.compile('default="(.*?)"').findall(line)
+ value = value[0] if (len(value) > 0) else ''
+ if setting != '' and idx == setting:
+ values = value
+ break
+ elif idx != '' and value != '' and setting == '':
+ values[idx] = value
+ if reset:
+ if Data_Type(values) == 'dict':
+ for item in values.items():
+ Addon_Setting(addon_id=addon_id,setting=item[0],value=item[1])
+ elif setting != '':
+ Addon_Setting(addon_id=addon_id,setting=setting,value=value)
+ return values
+#----------------------------------------------------------------
+# TUTORIAL #
+def Dependency_Check(addon_id = 'all', recursive = False):
+ """
+This will return a list of all dependencies required by an add-on.
+This information is grabbed directly from the currently installed addon.xml,
+an individual add-on id or a list of add-on id's.
+
+CODE: Dependency_Check([addon_id, recursive])
+
+AVAILABLE PARAMS:
+
+ addon_id - This is optional, if not set it will return a list of every
+ dependency required from all installed add-ons. If you only want to
+ return results of one particular add-on then send through the id.
+
+ recursive - By default this is set to False but if set to true and you
+ also send through an individual addon_id it will return all dependencies
+ required for that addon id AND the dependencies of the dependencies.
+
+EXAMPLE CODE:
+current_id = xbmcaddon.Addon().getAddonInfo('id')
+dependencies = koding.Dependency_Check(addon_id=current_id, recursive=True)
+clean_text = ''
+for item in dependencies:
+ clean_text += item+'\n'
+koding.Text_Box('Modules required for %s'%current_id,clean_text)
+~"""
+ import xbmcaddon
+ import re
+ from filetools import Text_File
+ from vartools import Data_Type
+
+ processed = []
+ depfiles = []
+
+ if addon_id == 'all':
+ addon_id = xbmcvfs.listdir(ADDONS)
+ elif Data_Type(addon_id) == 'str':
+ addon_id = [addon_id]
+
+ for name in addon_id:
+ try:
+ addon_path = xbmcaddon.Addon(id=name).getAddonInfo('path')
+ except:
+ addon_path = os.path.join(ADDONS, name)
+ if not name in processed:
+ processed.append(name)
+
+ # Get list of master dependencies
+ depfiles = Check_Deps(addon_path,[name])
+
+ # Recursively check all other dependencies
+ depchecks = depfiles
+ if recursive:
+ while len(depchecks):
+ for depfile in depfiles:
+ if depfile not in processed:
+ try:
+ dep_path = xbmcaddon.Addon(id=depfile).getAddonInfo('path')
+ except:
+ dep_path = os.path.join(ADDONS,depfile)
+ newdepfiles = Check_Deps(dep_path, depfiles)
+ # Pass through the path of sub-dependency and add items to master list and list to check
+ for newdep in newdepfiles:
+ if not (newdep in depchecks) and not (newdep in processed):
+ depchecks.append(newdep)
+ if not newdep in depfiles:
+ depfiles.append(newdep)
+ processed.append(depfile)
+ depchecks.remove(depfile)
+ if name in depchecks:
+ depchecks.remove(name)
+ return processed[1:]
+#----------------------------------------------------------------
+# TUTORIAL #
+def Get_Addon_ID(folder):
+ """
+If you know the folder name of an add-on but want to find out the
+addon id (it may not necessarily be the same as folder name) then
+you can use this function. Even if the add-on isn't enabled on the
+system this will regex out the add-on id.
+
+CODE: Get_Addon_ID(folder)
+
+AVAILABLE PARAMS:
+
+ folder - This is folder name of the add-on. Just the name not the path.
+
+EXAMPLE CODE:
+dialog.ok('ABOUT','This function allows us to pass through a folder name found in the addons folder and it will return the real id. The vast majority of add-ons use the same folder name as id but there are exceptions. Let\'s check Python Koding...')
+my_id = koding.Get_Addon_ID(folder='script.module.python.koding.aio')
+dialog.ok('PYTHON KODING ID','The add-on id found for this folder folder is:','[COLOR=dodgerblue]%s[/COLOR]'%my_id)
+~"""
+ from filetools import Text_File
+ import re
+ xmlpath = os.path.join(ADDONS, folder, 'addon.xml')
+ if xbmcvfs.exists(xmlpath):
+ contents = Text_File(xmlpath,'r')
+ addon_id = re.compile('id="(.+?)"').findall(contents)
+ addon_id = addon_id[0] if (len(addon_id) > 0) else ''
+ return addon_id
+ else:
+ return folder
+#----------------------------------------------------------------
+# TUTORIAL #
+def Installed_Addons(types='unknown', content ='unknown', properties = ''):
+ """
+This will send back a list of currently installed add-ons on the system.
+All the three paramaters you can send through to this function are optional,
+by default (without any params) this function will return a dictionary of all
+installed add-ons. The dictionary will contain "addonid" and "type" e.g. 'xbmc.python.pluginsource'.
+
+CODE: Installed_Addons([types, content, properties]):
+
+AVAILABLE PARAMS:
+
+ types - If you only want to retrieve details for specific types of add-ons
+ then use this filter. Unfortunately only one type can be filtered at a time,
+ it is not yet possible to filter multiple types all in one go. Please check
+ the official wiki for the add-on types avaialble but here is an example if
+ you only wanted to show installed repositories: koding.Installed_Addons(types='xbmc.addon.repository')
+
+ content - Just as above unfortunately only one content type can be filtered
+ at a time, you can filter by video,audio,image and executable. If you want to
+ only return installed add-ons which appear in the video add-ons section you
+ would use this: koding.Installed_Addons(content='video')
+
+ properties - By default a dictionary containing "addonid" and "type" will be
+ returned for all found add-ons meeting your criteria. However you can add any
+ properties in here available in the add-on xml (check official Wiki for properties
+ available). Unlike the above two options you can choose to add multiple properties
+ to your dictionary, see example below:
+ koding.Installed_Addons(properties='name,thumbnail,description')
+
+
+EXAMPLE CODE:
+my_video_plugins = koding.Installed_Addons(types='xbmc.python.pluginsource', content='video', properties='name')
+final_string = ''
+for item in my_video_plugins:
+ final_string += 'ID: %s | Name: %s\n'%(item["addonid"], item["name"])
+koding.Text_Box('LIST OF VIDEO PLUGINS',final_string)
+~"""
+ try: import simplejson as json
+ except: import json
+
+ addon_dict = []
+ if properties != '':
+ properties = properties.replace(' ','')
+ properties = '"%s"' % properties
+ properties = properties.replace(',','","')
+
+ query = '{"jsonrpc":"2.0", "method":"Addons.GetAddons","params":{"properties":[%s],"enabled":"all","type":"%s","content":"%s"}, "id":1}' % (properties,types,content)
+ response = xbmc.executeJSONRPC(query)
+ data = json.loads(response)
+ if "result" in data:
+ try:
+ addon_dict = data["result"]["addons"]
+ except:
+ pass
+ return addon_dict
+#----------------------------------------------------------------
+# TUTORIAL #
+def Open_Settings(addon_id='',focus='',click=False,stop_script=True):
+ """
+By default this will open the current add-on settings but if you pass through an addon_id it will open the settings for that add-on.
+
+CODE: Open_Settings([addon_id, focus, click, stop_script])
+
+AVAILABLE PARAMS:
+
+ addon_id - This optional, it can be any any installed add-on id. If nothing is passed
+ through the current add-on settings will be opened.
+
+ focus - This is optional, if not set the settings will just open to the first item
+ in the list (normal behaviour). However if you want to open to a specific category and
+ setting then enter the number in here separated by a dot. So for example if we want to
+ focus on the 2nd category and 3rd setting in the list we'd send through focus='2.3'
+
+ click - If you want the focused item to automatically be clicked set this to True.
+
+ stop_script - By default this is set to True, as soon as the addon settings are opened
+ the current script will stop running. If you pass through as False then the script will
+ continue running in the background - opening settings does not pause a script, Kodi just
+ see's it as another window being opened.
+
+EXAMPLE CODE:
+youtube_path = xbmc.translatePath('special://home/addons/plugin.video.youtube')
+if os.path.exists(youtube_path):
+ dialog.ok('YOUTUBE SETTINGS','We will now open the YouTube settings.','We will focus on category 2, setting 3 AND send a click.')
+ koding.Open_Settings(addon_id='plugin.video.youtube',focus='2.3',click=True,stop_script=True)
+else:
+ dialog.ok('YOUTUBE NOT INSTALLED','We cannot run this example as it uses the YouTube add-on which has not been found on your system.')
+~"""
+ import xbmcaddon
+ if addon_id == '':
+ addon_id = Caller()
+ xbmc.log('ADDON ID: %s'%addon_id,2)
+ xbmc.executebuiltin('Addon.OpenSettings(%s)' % addon_id)
+ if focus != '':
+ category, setting = focus.split('.')
+ xbmc.executebuiltin('SetFocus(%d)' % (int(category) + 99))
+ xbmc.executebuiltin('SetFocus(%d)' % (int(setting) + 199))
+ if click:
+ xbmc.sleep(500)
+ xbmc.executebuiltin('Action(Select,10140)')
+ if stop_script:
+ try:
+ sys.exit()
+ except:
+ pass
+#----------------------------------------------------------------
+# TUTORIAL #
+def Toggle_Addons(addon='all', enable=True, safe_mode=True, exclude_list=[], new_only=True, refresh=True, update_status=0):
+ """
+Send through either a list of add-on ids or one single add-on id.
+The add-ons sent through will then be added to the addons*.db
+and enabled or disabled (depending on state sent through).
+
+WARNING: If safe_mode is set to False this directly edits the
+addons*.db rather than using JSON-RPC. Although directly amending
+the db is a lot quicker there is no guarantee it won't cause
+severe problems in later versions of Kodi (this was created for v17).
+DO NOT set safe_mode to False unless you 100% understand the consequences!
+
+CODE: Toggle_Addons([addon, enable, safe_mode, exclude_list, new_only, refresh])
+
+AVAILABLE PARAMS:
+ (*) addon - This can be a list of addon ids, one single id or
+ 'all' to enable/disable all. If enabling all you can still use
+ the exclude_list for any you want excluded from this function.
+ enable - By default this is set to True, if you want to disable
+ the add-on(s) then set this to False.
+
+ safe_mode - By default this is set to True which means the add-ons
+ are enabled/disabled via JSON-RPC which is the method recommended by
+ the XBMC foundation. Setting this to False will result in a much
+ quicker function BUT there is no guarantee this will work on future
+ versions of Kodi and it may even cause corruption in future versions.
+ Setting to False is NOT recommended and you should ONLY use this if
+ you 100% understand the risks that you could break multiple setups.
+
+ exclude_list - Send through a list of any add-on id's you do not
+ want to be included in this command.
+
+ new_only - By default this is set to True so only newly extracted
+ add-on folders will be enabled/disabled. This means that any existing
+ add-ons which have deliberately been disabled by the end user are
+ not affected.
+
+ refresh - By default this is set to True, it will refresh the
+ current container and also force a local update on your add-ons db.
+
+ update_status - When running this function it needs to disable the
+ auto-update of add-ons by Kodi otherwise it risks crashing. This
+ update_status paramater is the state you want Kodi to revert back to
+ once the toggle of add-ons has completed. By default this is set to 0
+ which is auto-update. You can also choose 1 (notify of updates) or 2
+ (disable auto updates).
+
+EXAMPLE CODE:
+from systemtools import Refresh
+xbmc.executebuiltin('ActivateWindow(Videos, addons://sources/video/)')
+xbmc.sleep(2000)
+dialog.ok('DISABLE YOUTUBE','We will now disable YouTube (if installed)')
+koding.Toggle_Addons(addon='plugin.video.youtube', enable=False, safe_mode=True, exclude_list=[], new_only=False)
+koding.Refresh('container')
+xbmc.sleep(2000)
+dialog.ok('ENABLE YOUTUBE','When you click OK we will enable YouTube (if installed)')
+koding.Toggle_Addons(addon='plugin.video.youtube', enable=True, safe_mode=True, exclude_list=[], new_only=False)
+koding.Refresh('container')
+~"""
+ from __init__ import dolog
+ from filetools import DB_Path_Check, Get_Contents
+ from database import DB_Query
+ from systemtools import Last_Error, Refresh, Set_Setting, Sleep_If_Function_Active, Timestamp
+ from vartools import Data_Type
+
+ Set_Setting('general.addonupdates', 'kodi_setting', '2')
+ dolog('disabled auto updates for add-ons')
+ kodi_ver = int(float(xbmc.getInfoLabel("System.BuildVersion")[:2]))
+ addons_db = DB_Path_Check('addons')
+ data_type = Data_Type(addon)
+ state = int(bool(enable))
+ enabled_list = []
+ disabled_list = []
+ if kodi_ver >= 17:
+ on_system = DB_Query(addons_db,'SELECT addonID, enabled from installed')
+# Create a list of enabled and disabled add-ons already on system
+ enabled_list = Addon_List(enabled=True)
+ disabled_list = Addon_List(enabled=False)
+
+# If addon has been sent through as a string we add into a list
+ if data_type == 'unicode':
+ addon = addon.encode('utf8')
+ data_type = Data_Type(addon)
+
+ if data_type == 'str' and addon!= 'all':
+ addon = [addon]
+
+# Grab all the add-on ids from addons folder
+ if addon == 'all':
+ addon = []
+ ADDONS = xbmc.translatePath('special://home/addons')
+ my_addons = Get_Contents(path=ADDONS, exclude_list=['packages','temp'])
+ for item in my_addons:
+ addon_id = Get_Addon_ID(item)
+ addon.append(addon_id)
+
+# Find out what is and isn't enabled in the addons*.db
+ temp_list = []
+ for addon_id in addon:
+ if not addon_id in exclude_list and addon_id != '':
+ if addon_id in disabled_list and not new_only and enable:
+ temp_list.append(addon_id)
+ elif addon_id not in disabled_list and addon_id not in enabled_list:
+ temp_list.append(addon_id)
+ elif addon_id in enabled_list and not enable:
+ temp_list.append(addon_id)
+ elif addon_id in disabled_list and enable:
+ temp_list.append(addon_id)
+ addon = temp_list
+
+# If you want to bypass the JSON-RPC mode and directly modify the db (READ WARNING ABOVE!!!)
+ if not safe_mode and kodi_ver >= 17:
+ installedtime = Timestamp('date_time')
+ insert_query = 'INSERT or IGNORE into installed (addonID , enabled, installDate) VALUES (?,?,?)'
+ update_query = 'UPDATE installed SET enabled = ? WHERE addonID = ? '
+ insert_values = [addon, state, installedtime]
+ try:
+ for item in addon:
+ DB_Query(addons_db, insert_query, [item, state, installedtime])
+ DB_Query(addons_db, update_query, [state, item])
+ except:
+ dolog(Last_Error())
+ if refresh:
+ Refresh()
+
+# Using the safe_mode (JSON-RPC)
+ else:
+ mydeps = []
+ final_enabled = []
+ if state:
+ my_value = 'true'
+ log_value = 'ENABLED'
+ final_addons = []
+ else:
+ my_value = 'false'
+ log_value = 'DISABLED'
+ final_addons = addon
+
+ for my_addon in addon:
+
+ # If enabling the add-on then we also check for dependencies and enable them first
+ if state:
+ dependencies = Dependency_Check(addon_id=my_addon, recursive=True)
+ mydeps.append(dependencies)
+
+ # if enable selected we traverse through the dependencies enabling addons with lowest amount of deps to highest
+ if state:
+ mydeps = sorted(mydeps, key=len)
+ for dep in mydeps:
+ counter = 0
+ for item in dep:
+ enable_dep = True
+ if counter == 0:
+ final_addons.append(item)
+ enable_dep = False
+ elif item in final_enabled:
+ enable_dep = False
+ else:
+ enable_dep = True
+ if enable_dep:
+ if not item in exclude_list and not item in final_enabled and not item in enabled_list:
+ if Set_Setting(setting_type='addon_enable', setting=item, value = 'true'):
+ final_enabled.append(item)
+ counter += 1
+
+ # Now the dependencies are enabled we need to enable the actual main add-ons
+ for my_addon in final_addons:
+ if not my_addon in final_enabled:
+ if Set_Setting(setting_type='addon_enable', setting=my_addon, value = my_value):
+ final_enabled.append(addon)
+ if refresh:
+ Refresh(['addons','container'])
+ Set_Setting('general.addonupdates', 'kodi_setting', '%s'%update_status)
+#----------------------------------------------------------------
\ No newline at end of file
diff --git a/script.module.python.koding.aio/lib/koding/android.py b/script.module.python.koding.aio/lib/koding/android.py
new file mode 100644
index 0000000..1e803a8
--- /dev/null
+++ b/script.module.python.koding.aio/lib/koding/android.py
@@ -0,0 +1,93 @@
+# -*- coding: utf-8 -*-
+
+# script.module.python.koding.aio
+# Python Koding AIO (c) by TOTALREVOLUTION LTD (support@trmc.freshdesk.com)
+
+# Python Koding AIO is licensed under a
+# Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.
+
+# You should have received a copy of the license along with this
+# work. If not, see http://creativecommons.org/licenses/by-nc-nd/4.0.
+
+# Please make sure you've read and understood the license, this code can NOT be used commercially
+# and it can NOT be modified and redistributed. If you're found to be in breach of this license
+# then any affected add-ons will be blacklisted and will not be able to work on the same system
+# as any other add-ons which use this code. Thank you for your cooperation.
+
+import xbmc
+import subprocess
+#----------------------------------------------------------------
+# TUTORIAL #
+def App_Settings(apk_id):
+ """
+Open up the settings for an installed Android app.
+
+CODE: App_Settings(apk_id)
+
+AVAILABLE PARAMS:
+
+ (*) apk_id - The id of the app you want to open the settings for.
+
+EXAMPLE CODE:
+my_apps = koding.My_Apps()
+choice = dialog.select('CHOOSE AN APK', my_apps)
+koding.App_Settings(apk_id=my_apps[choice])
+~"""
+ xbmc.executebuiltin('StartAndroidActivity("","android.settings.APPLICATION_DETAILS_SETTINGS","","package:%s")' % apk_id)
+#----------------------------------------------------------------
+# TUTORIAL #
+def My_Apps():
+ """
+Return a list of apk id's installed on system
+
+CODE: My_Apps()
+
+EXAMPLE CODE:
+my_apps = koding.My_Apps()
+choice = dialog.select('CHOOSE AN APK', my_apps)
+if choice >= 0:
+ koding.App_Settings(apk_id=my_apps[choice])
+~"""
+ Installed_APK = []
+ if xbmc.getCondVisibility('system.platform.android'):
+ try:
+ Installed_APK = subprocess.Popen(['exec ''/system/bin/pm list packages -3'''], executable='/system/bin/sh', shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT).communicate()[0].rstrip('\n').splitlines()
+ except Exception as e:
+ xbmc.log('Failed to grab installed app details: %s' % e)
+ Installed_APK = []
+
+ for i in range(len(Installed_APK)):
+ Installed_APK[i] = Installed_APK[i].partition(':')[2]
+
+ return Installed_APK
+#----------------------------------------------------------------
+# TUTORIAL #
+def Start_App(apk_id):
+ """
+Open an Android application
+
+CODE: Start_App(apk_id)
+
+AVAILABLE PARAMS:
+
+ (*) apk_id - The id of the app you want to open.
+
+EXAMPLE CODE:
+dialog.ok('OPEN FACEBOOK','Presuming you have Facebook installed and this is an Android system we will now open that apk')
+koding.Start_App(apk_id='com.facebook.katana')
+~"""
+ xbmc.executebuiltin('StartAndroidActivity(%s)' % apk_id)
+#----------------------------------------------------------------
+# TUTORIAL #
+def Uninstall_APK(apk_id):
+ """
+Uninstall and Android app
+
+CODE: Uninstall_APK(apk_id)
+
+EXAMPLE CODE:
+if dialog.yesno('UNINSTALL FACEBOOK','Would you like to uninstall the Facebook app from your system?'):
+ koding.Uninstall_APK(apk_id='com.facebook.katana')
+~"""
+ xbmc.executebuiltin('StartAndroidActivity("","android.intent.action.DELETE","","package:%s")' % apk_id)
+#----------------------------------------------------------------
\ No newline at end of file
diff --git a/script.module.python.koding.aio/lib/koding/database.py b/script.module.python.koding.aio/lib/koding/database.py
new file mode 100644
index 0000000..2ce9891
--- /dev/null
+++ b/script.module.python.koding.aio/lib/koding/database.py
@@ -0,0 +1,458 @@
+# -*- coding: utf-8 -*-
+
+# script.module.python.koding.aio
+# Python Koding AIO (c) by TOTALREVOLUTION LTD (support@trmc.freshdesk.com)
+
+# Python Koding AIO is licensed under a
+# Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.
+
+# You should have received a copy of the license along with this
+# work. If not, see http://creativecommons.org/licenses/by-nc-nd/4.0.
+
+# Please make sure you've read and understood the license, this code can NOT be used commercially
+# and it can NOT be modified and redistributed. If you're found to be in breach of this license
+# then any affected add-ons will be blacklisted and will not be able to work on the same system
+# as any other add-ons which use this code. Thank you for your cooperation.
+
+import os
+import sys
+try: from sqlite3 import dbapi2 as database
+except: from pysqlite2 import dbapi2 as database
+
+import xbmc
+import xbmcaddon
+import xbmcgui
+import xbmcvfs
+from __init__ import Caller
+from filetools import Physical_Path
+
+# Put this in a try statement, when called from a service it will throw an error otherwise
+try:
+ try:
+ ADDON_ID = xbmcaddon.Addon().getAddonInfo('id')
+ except:
+ ADDON_ID = Caller()
+
+ AddonVersion = xbmcaddon.Addon(id=ADDON_ID).getAddonInfo('version')
+ profile_path = xbmcaddon.Addon(id=ADDON_ID).getAddonInfo('profile')
+ addon_db_path = Physical_Path(os.path.join(profile_path,'database.db'))
+except:
+ pass
+
+dbcur, dbcon = None, None
+dialog = xbmcgui.Dialog()
+#----------------------------------------------------------------
+def _connect_to_db():
+ """ internal command ~"""
+ def dict_factory(cursor, row):
+ d = {}
+ for idx, col in enumerate(cursor.description):
+ d[col[0]] = row[idx]
+ return d
+
+ xbmcvfs.mkdirs(profile_path)
+ db_location = os.path.join(profile_path.decode('utf-8'),'database.db')
+ db_location = Physical_Path(db_location)
+ dbcon = database.connect(db_location)
+ dbcon.row_factory = dict_factory
+ dbcur = dbcon.cursor()
+ return (dbcur, dbcon)
+#----------------------------------------------------------------
+def _execute_db_string(sql_string, commit = True):
+ """ internal command ~"""
+ global dbcur, dbcon
+ if dbcur is None or dbcon is None:
+ dbcur, dbcon = _connect_to_db()
+ dbcur.execute(sql_string)
+ if commit:
+ dbcon.commit()
+ results = []
+ for result in dbcur:
+ results.append(result)
+ return results
+#----------------------------------------------------------------
+# TUTORIAL #
+def Add_To_Table(table, spec, abort_on_error=False):
+ """
+Add a row to the table in /userdata/addon_data//database.db
+
+CODE: Add_To_Table(table, spec)
+
+AVAILABLE PARAMS:
+
+ (*) table - The table name you want to query
+
+ (*) spec - Sent through as a dictionary this is the colums and constraints.
+
+ abort_on_error - Default is set to False but set to True if you want to abort
+ the process when it hits an error.
+
+EXAMPLE CODE:
+create_specs = {"columns":{"name":"TEXT", "id":"TEXT"}}
+koding.Create_Table("test_table", create_specs)
+add_specs1 = {"name":"YouTube", "id":"plugin.video.youtube"}
+add_specs2 = {"name":"vimeo","id":"plugin.video.vimeo"}
+koding.Add_To_Table("test_table", add_specs1)
+koding.Add_To_Table("test_table", add_specs2)
+results = koding.Get_All_From_Table("test_table")
+final_results = ''
+for item in results:
+ final_results += 'ID: %s | Name: %s\n'%(item["id"], item["name"])
+koding.Text_Box('DB RESULTS', final_results)
+koding.Remove_Table('test_table')
+~"""
+ global dbcon
+ sql_string = "INSERT INTO %s (" % table
+ keys = []
+ values = []
+ if type(spec) != list:
+ spec = [spec]
+ for item in spec:
+ for key in item.keys():
+ keys.append(key)
+ values.append(item[key])
+ for key in keys:
+ sql_string += "%s, " % key
+ sql_string = sql_string[:-2]
+ sql_string += ") Values ("
+ for value in values:
+ sql_string += "\"%s\", " % value
+ sql_string = sql_string[:-2]
+ sql_string += ")"
+ try:
+ _execute_db_string(sql_string, commit=False)
+ except:
+ if abort_on_error:
+ dbcon.rollback()
+ raise Exception()
+ continue
+ dbcon.commit()
+#----------------------------------------------------------------
+# TUTORIAL #
+def Add_Multiple_To_Table(table, keys=[], values=[]):
+ """
+This will allow you to add multiple rows to a table in one big (fast) bulk command
+The db file is: /userdata/addon_data//database.db
+
+CODE: Add_To_Table(table, spec)
+
+AVAILABLE PARAMS:
+
+ (*) table - The table name you want to query
+
+ (*) keys - Send through a list of keys you want to add to
+
+ (*) values - A list of values you want to add, this needs to be
+ a list of lists (see example below)
+
+EXAMPLE CODE:
+create_specs = {"columns":{"name":"TEXT", "id":"TEXT"}}
+koding.Create_Table("test_table", create_specs)
+dialog.ok('ADD TO TABLE','Lets add the details of 3 add-ons to "test_table" in our database.')
+mykeys = ["name","id"]
+myvalues = [("YouTube","plugin.video.youtube"), ("vimeo","plugin.video.vimeo"), ("test2","plugin.video.test2")]
+koding.Add_Multiple_To_Table(table="test_table", keys=mykeys, values=myvalues)
+results = koding.Get_All_From_Table("test_table")
+final_results = ''
+for item in results:
+ final_results += 'ID: %s | Name: %s\n'%(item["id"], item["name"])
+koding.Text_Box('DB RESULTS', 'Below are details of the items pulled from our db:\n\n%s'%final_results)
+koding.Remove_Table('test_table')
+~"""
+ dbcur, dbcon = _connect_to_db()
+ sql_string = "INSERT INTO %s (" % table
+ sql_2 = ''
+ if type(keys) != list:
+ keys = [keys]
+ if type(values) != list:
+ values = [values]
+ for item in keys:
+ if not item.startswith('`'):
+ item = r'`'+item
+ if not item.endswith('`'):
+ item = item+r'`'
+ xbmc.log('ITEM: %s'%item,2)
+ sql_string += "%s, " % item
+ sql_2 += "?,"
+ sql_string = "%s) VALUES (%s)"%(sql_string[:-2], sql_2[:-1])
+ dbcur.executemany(sql_string, values)
+ dbcon.commit()
+#----------------------------------------------------------------
+# TUTORIAL #
+def Create_Table(table, spec):
+ """
+Create a new table in the database at /userdata/addon_data//database.db
+
+CODE: Create_Table(table, spec)
+
+AVAILABLE PARAMS:
+
+ (*) table - The table name you want to query
+
+ (*) spec - Sent through as a dictionary this is the colums and constraints.
+
+EXAMPLE CODE:
+create_specs = { "columns":{"name":"TEXT", "id":"TEXT"}, "constraints":{"unique":"id"} }
+koding.Create_Table("test_table", create_specs)
+dialog.ok('TABLE CREATED','A new table has been created in your database and the id column has been set as UNIQUE.')
+my_specs = {"name":"YouTube", "id":"plugin.video.youtube"}
+try:
+ koding.Add_To_Table("test_table", my_specs)
+ koding.Add_To_Table("test_table", my_specs)
+except:
+ dialog.ok('FAILED TO ADD','Could not add duplicate items because the the column "id" is set to be UNIQUE')
+results = koding.Get_All_From_Table("test_table")
+final_results = ''
+for item in results:
+ final_results += 'ID: %s | Name: %s\n'%(item["id"], item["name"])
+koding.Text_Box('DB RESULTS', final_results)
+koding.Remove_Table('test_table')
+~"""
+ sql_string = "CREATE TABLE IF NOT EXISTS %s (" % table
+ columns = spec.get("columns", {})
+ constraints = spec.get("constraints", {})
+ for key in columns.keys():
+ if not columns[key]:
+ columns[key] = "TEXT"
+ sql_string += "%s %s, " % (key, columns[key])
+
+ for key in constraints.keys():
+ sql_string += "%s(%s), " % (key, constraints[key])
+ sql_string = sql_string[:-2]
+ sql_string += ");"
+ _execute_db_string(sql_string)
+#----------------------------------------------------------------
+# TUTORIAL #
+def DB_Query(db_path, query, values=''):
+ """
+Open a database and either return an array of results with the SELECT SQL command or perform an action such as INSERT, UPDATE, CREATE.
+
+CODE: DB_Query(db_path, query, [values])
+
+AVAILABLE PARAMS:
+
+ (*) db_path - the full path to the database file you want to access.
+
+ (*) query - this is the actual db query you want to process, use question marks for values
+
+ values - a list of values, even if there's only one value it must be sent through as a list item.
+
+IMPORTANT: Directly accessing databases which are outside of your add-ons domain is very much frowned
+upon. If you need to access a built-in kodi database (as shown in example below) you should always use
+the JSON-RPC commands where possible.
+
+EXAMPLE CODE:
+import filetools
+dbpath = filetools.DB_Path_Check('addons')
+db_table = 'addon'
+kodi_version = int(float(xbmc.getInfoLabel("System.BuildVersion")[:2]))
+if kodi_version >= 17:
+ db_table = 'addons'
+db_query = koding.DB_Query(db_path=dbpath, query='SELECT * FROM %s WHERE addonID LIKE ? AND addonID NOT LIKE ?'%db_table, values=['%youtube%','%script.module%'])
+koding.Text_Box('DB SEARCH RESULTS',str(db_query))
+~"""
+ db_dict = []
+ db_path = Physical_Path(db_path)
+ con = database.connect(db_path)
+ cur = con.cursor()
+
+ if query.upper().startswith('SELECT'):
+ if values == '':
+ cur.execute(query)
+ else:
+ cur.execute(query, values)
+
+ names = list(map(lambda x: x[0], cur.description))
+
+ for rows in iter(cur.fetchmany, []):
+ for row in rows:
+ temp_dict = {}
+ for idx, col in enumerate(cur.description):
+ temp_dict[col[0]] = row[idx]
+ db_dict.append(temp_dict)
+ return db_dict
+
+ elif query.upper().startswith('CREATE'):
+ cur.execute(query)
+ con.commit()
+
+# ANY NON SELECT QUERY (UPDATE, INSERT ETC.)
+ else:
+ try:
+ if values == '':
+ cur.executemany(query)
+ con.commit()
+ else:
+ cur.executemany(query, values)
+ con.commit()
+ except:
+ if values == '':
+ cur.execute(query)
+ con.commit()
+ else:
+ cur.execute(query, values)
+ con.commit()
+
+ cur.close()
+#----------------------------------------------------------------
+# TUTORIAL #
+def Get_All_From_Table(table):
+ """
+Return a list of all entries from a specific table in /userdata/addon_data//database.db
+
+CODE: Get_All_From_Table(table)
+
+AVAILABLE PARAMS:
+
+ (*) table - The table name you want to query
+
+EXAMPLE CODE:
+create_specs = {"columns":{"name":"TEXT", "id":"TEXT"}}
+koding.Create_Table("test_table", create_specs)
+add_specs1 = {"name":"YouTube", "id":"plugin.video.youtube"}
+add_specs2 = {"name":"vimeo","id":"plugin.video.vimeo"}
+koding.Add_To_Table("test_table", add_specs1)
+koding.Add_To_Table("test_table", add_specs2)
+results = koding.Get_All_From_Table("test_table")
+final_results = ''
+for item in results:
+ final_results += 'ID: %s | Name: %s\n'%(item["id"], item["name"])
+koding.Text_Box('DB RESULTS', final_results)
+koding.Remove_Table('test_table')
+~"""
+ try:
+ return _execute_db_string("SELECT * FROM %s" % table)
+ except:
+ return []
+#----------------------------------------------------------------
+# TUTORIAL #
+def Get_From_Table(table, spec=None, default_compare_operator="="):
+ """
+Return a list of all entries matching a specific criteria from the
+database stored at: /userdata/addon_data//database.db
+
+CODE: Get_From_Table(table, spec, compare_operator)
+
+AVAILABLE PARAMS:
+
+ (*) table - The table name you want to query
+
+ spec - This is the query value, sent through as a dictionary.
+
+ default_compare_operator - By default this is set to '=' but could be any
+ other SQL query string such as 'LIKE', 'NOT LIKE', '!=' etc.
+
+EXAMPLE CODE:
+create_specs = {"columns":{"name":"TEXT", "id":"TEXT"}}
+koding.Create_Table("test_table", create_specs)
+add_specs1 = {"name":"YouTube", "id":"plugin.video.youtube"}
+add_specs2 = {"name":"vimeo","id":"plugin.video.vimeo"}
+koding.Add_To_Table("test_table", add_specs1)
+koding.Add_To_Table("test_table", add_specs2)
+results = koding.Get_From_Table(table="test_table", spec={"name":"%vim%"}, default_compare_operator="LIKE")
+final_results = ''
+for item in results:
+ final_results += 'ID: %s | Name: %s\n'%(item["id"], item["name"])
+koding.Text_Box('DB CONTENTS', final_results)
+koding.Remove_Table('test_table')
+~"""
+ if spec == None:
+ return Get_All_From_Table()
+ sql_string = "SELECT * FROM %s WHERE " % table
+ for key in spec.keys():
+ if type(spec[key]) == dict:
+ value = spec[key]["value"]
+ column_compare_operator = spec[key].get("compare_operator", default_compare_operator)
+ else:
+ value = spec[key]
+ column_compare_operator = default_compare_operator
+ sql_string += "%s %s \"%s\" AND " % (key, column_compare_operator, value)
+ sql_string = sql_string[:-5]
+ try:
+ return _execute_db_string(sql_string, commit=False)
+ except:
+ return []
+#----------------------------------------------------------------
+# TUTORIAL #
+def Remove_From_Table(table, spec, default_compare_operator="=", abort_on_error=False):
+ """
+Remove entries in the db table at /userdata/addon_data//database.db
+
+CODE: Remove_From_Table(table, spec, [compare_operator])
+
+AVAILABLE PARAMS:
+
+ (*) table - The table name you want to query
+
+ spec - This is the query value, sent through as a dictionary.
+
+ default_compare_operator - By default this is set to '=' but could be any
+ other SQL query string such as 'LIKE', 'NOT LIKE', '!=' etc.
+
+EXAMPLE CODE:
+create_specs = {"columns":{"name":"TEXT", "id":"TEXT"}}
+koding.Create_Table(table="test_table", spec=create_specs)
+add_specs1 = {"name":"YouTube", "id":"plugin.video.youtube"}
+add_specs2 = {"name":"vimeo","id":"plugin.video.vimeo"}
+koding.Add_To_Table(table="test_table", spec=add_specs1)
+koding.Add_To_Table(table="test_table", spec=add_specs2)
+results = koding.Get_All_From_Table(table="test_table")
+final_results = ''
+for item in results:
+ final_results += 'ID: %s | Name: %s\n'%(item["id"], item["name"])
+koding.Text_Box('DB CONTENTS', final_results)
+dialog.ok('REMOVE ITEM','We will now remove vimeo from the table, lets see if it worked...')
+koding.Remove_From_Table(table="test_table", spec={"name":"vimeo"})
+results = koding.Get_All_From_Table(table="test_table")
+final_results = ''
+for item in results:
+ final_results += 'ID: %s | Name: %s\n'%(item["id"], item["name"])
+koding.Text_Box('NEW DB CONTENTS', final_results)
+koding.Remove_Table('test_table')
+~"""
+ global dbcon
+ sql_string = "DELETE FROM %s WHERE " % table
+ if type(spec) != list:
+ spec = [spec]
+ for item in spec:
+ for key in item.keys():
+ if type(item[key]) == dict:
+ value = item[key]["value"]
+ column_compare_operator = item[key].get("compare_operator", default_compare_operator)
+ else:
+ value = item[key]
+ column_compare_operator = default_compare_operator
+ sql_string += "%s %s \"%s\" AND " % (key, column_compare_operator, value)
+ sql_string = sql_string[:-4]
+ try:
+ _execute_db_string(sql_string, commit=False)
+ except:
+ if abort_on_error:
+ dbcon.rollback()
+ raise Exception()
+ continue
+ dbcon.commit()
+#----------------------------------------------------------------
+# TUTORIAL #
+def Remove_Table(table):
+ """
+Use with caution, this will completely remove a database table and
+all of it's contents. The only database you can access with this command
+is your add-ons own db file called database.db
+
+CODE: Remove_Table(table)
+
+AVAILABLE PARAMS:
+
+ (*) table - This is the name of the table you want to permanently delete.
+
+EXAMPLE CODE:
+dialog.ok('REMOVE TABLE','It\'s a bit pointless doing this as you can\'t physically see what\'s happening so you\'ll just have to take our word it works!')
+koding.Remove_Table('test_table')
+~"""
+ sql_string = "DROP TABLE IF EXISTS %s;" % table
+ _execute_db_string(sql_string)
+#----------------------------------------------------------------
+def reset_db():
+ global dbcon, dbcur
+ dbcur, dbcon = None, None
\ No newline at end of file
diff --git a/script.module.python.koding.aio/lib/koding/directory.py b/script.module.python.koding.aio/lib/koding/directory.py
new file mode 100644
index 0000000..e7d196f
--- /dev/null
+++ b/script.module.python.koding.aio/lib/koding/directory.py
@@ -0,0 +1,225 @@
+# -*- coding: utf-8 -*-
+
+# script.module.python.koding.aio
+# Python Koding AIO (c) by TOTALREVOLUTION LTD (support@trmc.freshdesk.com)
+
+# Python Koding AIO is licensed under a
+# Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.
+
+# You should have received a copy of the license along with this
+# work. If not, see http://creativecommons.org/licenses/by-nc-nd/4.0.
+
+# Please make sure you've read and understood the license, this code can NOT be used commercially
+# and it can NOT be modified and redistributed. If you're found to be in breach of this license
+# then any affected add-ons will be blacklisted and will not be able to work on the same system
+# as any other add-ons which use this code. Thank you for your cooperation.
+
+import sys
+import urllib
+import xbmc
+import xbmcaddon
+import xbmcgui
+import xbmcplugin
+
+dialog = xbmcgui.Dialog()
+mode = ''
+#----------------------------------------------------------------
+# TUTORIAL #
+def Add_Dir(name, url='', mode='', folder=False, icon='', fanart='', description='', info_labels={}, set_art={}, set_property={}, content_type='', context_items=None, context_override=False, playable=False):
+ """
+This allows you to create a list item/folder inside your add-on.
+Please take a look at your addon default.py comments for more information
+(presuming you created one at http://totalrevolution.tv)
+
+TOP TIP: If you want to send multiple variables through to a function just
+send through as a dictionary encapsulated in quotation marks. In the function
+you can then use the following code to access them:
+
+params = eval(url)
+^ That will then give you a dictionary where you can just pull each variable and value from.
+
+CODE: Add_Dir(name, url, mode, [folder, icon, fanart, description, info_labels, content_type, context_items, context_override, playable])
+
+AVAILABLE PARAMS:
+
+ (*) name - This is the name you want to show for the list item
+
+ url - If the route (mode) you're calling requires extra paramaters
+ to be sent through then this is where you add them. If the function is
+ only expecting one item then you can send through as a simple string.
+ Unlike many other Add_Dir functions Python Koding does allow for multiple
+ params to be sent through in the form of a dictionary so let's say your
+ function is expecting the 2 params my_time & my_date. You would send this info
+ through as a dictionary like this:
+ url={'my_time':'10:00', 'my_date':'01.01.1970'}
+
+ If you send through a url starting with plugin:// the item will open up into
+ that plugin path so for example:
+ url='plugin://plugin.video.youtube/play/?video_id=FTI16i7APhU'
+
+ mode - The mode you want to open when this item is clicked, this is set
+ in your master_modes dictionary (see template add-on linked above)
+
+ folder - This is an optional boolean, by default it's set to False.
+ True will open into a folder rather than an executable command
+
+ icon - The path to the thumbnail you want to use for this list item
+
+ fanart - The path to the fanart you want to use for this list item
+
+ description - A description of your list item, it's skin dependant but this
+ usually appears below the thumbnail
+
+ info_labels - You can send through any number of info_labels via this option.
+ For full details on the infolabels available please check the pydocs here:
+ http://mirrors.kodi.tv/docs/python-docs/16.x-jarvis/xbmcgui.html#ListItem-setInfo
+
+ When passing through infolabels you need to use a dictionary in this format:
+ {"genre":"comedy", "title":"test video"}
+
+ set_art - Using the same format as info_labels you can set your artwork via
+ a dictionary here. Full details can be found here:
+ http://mirrors.kodi.tv/docs/python-docs/16.x-jarvis/xbmcgui.html#ListItem-setArt
+
+ set_property - Using the same format as info_labels you can set your artwork via
+ a dictionary here. Full details can be found here:
+ http://kodi.wiki/view/InfoLabels#ListItem
+
+ content_type - By default this will set the content_type for kodi to a blank string
+ which is what Kodi expects for generic category listings. There are plenty of different
+ types though and when set Kodi will perform different actions (such as access the
+ database looking for season/episode information for the list item).
+
+ WARNING: Setting the wrong content type for your listing can cause the system to
+ log thousands of error reports in your log, cause the system to lag and make
+ thousands of unnecessary db calls - sometimes resulting in a crash. You can find
+ details on the content_types available here: http://forum.kodi.tv/showthread.php?tid=299107
+
+ context_items - Add context items to your directory. The params you need to send through
+ need to be in a list format of [(label, action,),] look at the example code below for
+ more details.
+
+ context_override - By default your context items will be added to the global context
+ menu items but you can override this by setting this to True and then only your
+ context menu items will show.
+
+ playable - By default this is set to False but if set to True kodi will just try
+ and play this item natively with no extra fancy functions.
+
+EXAMPLE:
+my_context = [('Music','xbmc.executebuiltin("ActivateWindow(music)")'),('Programs','xbmc.executebuiltin("ActivateWindow(programs)")')]
+# ^ This is our two basic context menu items (music and programs)
+
+Add_Dir(name='TEST DIRECTORY', url='', mode='test_directory', folder=True, context_items=my_context, context_override=True)
+# ^ This will add a folder AND a context menu item for when bring up the menu (when focused on this directory).
+# ^^ The context_override is set to True which means it will override the default Kodi context menu items.
+
+Add_Dir(name='TEST ITEM', url='', mode='test_item', folder=False, context_items=my_context, context_override=False)
+# ^ This will add an item to the list AND a context menu item for when bring up the menu (when focused on this item).
+# ^^ The context_override is set to False which means the new items will appear alongside the default Kodi context menu items.
+~"""
+ from vartools import Convert_Special, Data_Type
+
+ module_id = 'script.module.python.koding.aio'
+ this_module = xbmcaddon.Addon(id=module_id)
+
+ addon_handle = int(sys.argv[1])
+# Check we're in an appropriate section for the content type set
+ song_only_modes = ['songs','artist','album','song','music']
+ video_only_modes = ['sets','tvshows','seasons','actors','directors','unknown','video','set','movie','tvshow','season','episode']
+ if xbmc.getInfoLabel('Window.Property(xmlfile)') == 'MyVideoNav.xml' and content_type in song_only_modes:
+ content_type = ''
+ if xbmc.getInfoLabel('Window.Property(xmlfile)') == 'MyMusicNav.xml' and content_type in video_only_modes:
+ content_type = ''
+
+ if description == '':
+ description = this_module.getLocalizedString(30837)
+
+ if Data_Type(url) == 'dict':
+ url = repr(url)
+
+ if Data_Type(info_labels) != 'dict':
+ dialog.ok('WRONG INFO LABELS', 'Please check documentation, these should be sent through as a dictionary.')
+
+ if Data_Type(set_art) != 'dict':
+ dialog.ok('WRONG SET_ART', 'Please check documentation, these should be sent through as a dictionary.')
+
+ if Data_Type(set_property) != 'dict':
+ dialog.ok('WRONG SET_PROPERTY', 'Please check documentation, these should be sent through as a dictionary.')
+
+# Set the default title, filename and plot if not sent through already via info_labels
+ try:
+ title = info_labels["Title"]
+ if title == '':
+ info_labels["Title"] = name
+ except:
+ info_labels["Title"] = name
+
+ try:
+ filename = info_labels["FileName"]
+ # if filename == '':
+ # info_labels["FileName"] = name
+ except:
+ info_labels["FileName"] = name
+
+ try:
+ plot = info_labels["plot"]
+ if plot == '':
+ info_labels["plot"] = description
+ except:
+ info_labels["plot"] = description
+# Set default thumbnail image used for listing (if not sent through via set_art)
+ try:
+ set_art["icon"]
+ except:
+ set_art["icon"] = icon
+
+# Set default Fanart if not already sent through via set_property
+ try:
+ set_property["Fanart_Image"] = fanart
+ except:
+ set_property["Fanart_Image"]
+
+# Set the main listitem properties
+ liz = xbmcgui.ListItem(label=str(name), iconImage=str(icon), thumbnailImage=str(icon))
+
+# Set the infolabels
+ liz.setInfo(type=content_type, infoLabels=info_labels)
+
+# Set the artwork
+ liz.setArt(set_art)
+
+# Loop through the set_property list and set each item in there
+ for item in set_property.items():
+ liz.setProperty(item[0], item[1])
+
+# Add a context item (if details for context items are sent through)
+ if context_items:
+ liz.addContextMenuItems(context_items, context_override)
+
+ u = sys.argv[0]
+ u += "?mode=" +str(mode)
+ u += "&url=" +Convert_Special(url,string=True)
+ u += "&name=" +urllib.quote_plus(name)
+ u += "&iconimage=" +urllib.quote_plus(icon)
+ u += "&fanart=" +urllib.quote_plus(fanart)
+ u += "&description=" +urllib.quote_plus(description)
+
+ if url.startswith('plugin://'):
+ xbmcplugin.addDirectoryItem(handle=addon_handle,url=url,listitem=liz,isFolder=True)
+
+ elif folder:
+ xbmcplugin.addDirectoryItem(handle=addon_handle,url=u,listitem=liz,isFolder=True)
+
+ elif playable:
+ liz.setProperty('IsPlayable', 'true')
+ xbmcplugin.addDirectoryItem(handle=addon_handle,url=url,listitem=liz,isFolder=False)
+
+ else:
+ xbmcplugin.addDirectoryItem(handle=addon_handle,url=u,listitem=liz,isFolder=False)
+#----------------------------------------------------------------
+def Default_Mode():
+ """ internal command ~"""
+ dialog = xbmcgui.Dialog()
+ dialog.ok('MODE ERROR','You\'ve tried to call Add_Dir() without a valid mode, check you\'ve added the mode into the master_modes dictionary')
+#----------------------------------------------------------------
\ No newline at end of file
diff --git a/script.module.python.koding.aio/lib/koding/filetools.py b/script.module.python.koding.aio/lib/koding/filetools.py
new file mode 100644
index 0000000..6aba79e
--- /dev/null
+++ b/script.module.python.koding.aio/lib/koding/filetools.py
@@ -0,0 +1,1076 @@
+# -*- coding: utf-8 -*-
+
+# script.module.python.koding.aio
+# Python Koding AIO (c) by TOTALREVOLUTION LTD (support@trmc.freshdesk.com)
+
+# Python Koding AIO is licensed under a
+# Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.
+
+# You should have received a copy of the license along with this
+# work. If not, see http://creativecommons.org/licenses/by-nc-nd/4.0.
+
+# Please make sure you've read and understood the license, this code can NOT be used commercially
+# and it can NOT be modified and redistributed. If you're found to be in breach of this license
+# then any affected add-ons will be blacklisted and will not be able to work on the same system
+# as any other add-ons which use this code. Thank you for your cooperation.
+
+import os
+import shutil
+import os
+import sys
+import xbmc
+import xbmcaddon
+import xbmcgui
+import xbmcvfs
+
+from systemtools import Last_Error
+from xml.etree import ElementTree
+
+dp = xbmcgui.DialogProgress()
+dialog = xbmcgui.Dialog()
+HOME = 'special://home'
+PROFILE = 'special://profile'
+DATABASE = os.path.join(PROFILE,'Database')
+
+#----------------------------------------------------------------
+# TUTORIAL #
+class xml(object):
+ """
+SETTINGS - CREATE CUSTOM ADD-ON SETTINGS:
+All credit goes to OptimusGREEN for this module.
+
+This will create a new settings file for your add-on which you can read and write to. This is separate
+to the standard settings.xml and you can call the file whatever you want, however you would presumably
+call it something other than settings.xml as that is already used by Kodi add-ons.
+
+CODE: XML(path)
+
+AVAILABLE CLASSES:
+
+ParseValue - This class will allow you to get the value of an item in these custom settings.
+
+SetValue - This class allows you to set a value to the custom settings. If the settings.xml doesn't exist it will be automatically created so long as the path given in XML is writeable.
+
+
+EXAMPLE CODE:
+myXmlFile = "special://userdata/addon_data/script.module.python.koding.aio/timefile.xml"
+timefile = koding.xml(myXmlFile)
+getSetting = timefile.ParseValue
+setSetting = timefile.SetValue
+dialog.ok('CHECK SETTINGS','If you take a look in the addon_data folder for python koding a new file called timefile.xml will be created when you click OK.')
+setSetting("autorun", "true")
+autoRun = getSetting("autorun")
+dialog.ok('AUTORUN VALUE','The value of autorun in these new settings is [COLOR dodgerblue]%s[/COLOR].[CR][CR]Press OK to delete this file.'%autoRun)
+os.remove(koding.Physical_Path(myXmlFile))
+~"""
+
+ def __init__(self, xmlFile, masterTag="settings", childTag="setting"):
+ self.xmlFile = xmlFile
+ self.masterTag = masterTag
+ self.childTag = childTag
+ self.xmlFile = Physical_Path(self.xmlFile)
+
+ def ParseValue(self, settingID, settingIDTag="id", settingValueTag="value", addChild=False, formatXML=True):
+ if not os.path.exists(self.xmlFile):
+ return
+ tree = ElementTree.parse(self.xmlFile)
+ root = tree.getroot()
+ for child in root:
+ if child.attrib[settingIDTag] == settingID:
+ return child.attrib.get(settingValueTag)
+
+ def SetValue(self, settingID, newValue, settingIDTag="id", settingValueTag="value", addChild=False, asString=False, formatXML=True):
+ if not os.path.exists(self.xmlFile):
+ self.CreateXML(settingIDTag=settingIDTag, settingValueTag=settingValueTag, addChild=addChild, formatXML=formatXML)
+ tree = ElementTree.parse(self.xmlFile)
+ root = tree.getroot()
+ targetChild = None
+ for child in root:
+ if child.attrib[settingIDTag] == settingID:
+ targetChild = child
+ if targetChild is None:
+ self.AppendChild(root, settingID=settingID, newValue=newValue, settingIDTag=settingIDTag, settingValueTag=settingValueTag)
+ else:
+ for child in root:
+ if child.attrib[settingIDTag] == settingID:
+ child.attrib['%s' % (settingValueTag)] = '%s' % (newValue)
+ tree.write(self.xmlFile)
+ if asString:
+ readfile = open(self.xmlFile, 'r')
+ content = readfile.read()
+ readfile.close()
+ pretty = self.Prettify(content, asString=True)
+ else:
+ pretty = self.Prettify(self.xmlFile)
+ f = open(self.xmlFile, "w")
+ f.write(pretty)
+ f.close()
+
+ def CreateXML(self, settingIDTag="id", settingValueTag="value", addChild=False, formatXML=True):
+ root = ElementTree.Element("%s" % self.masterTag)
+ if addChild:
+ sub = ElementTree.SubElement(root, "%s" % self.childTag)
+ sub.set(settingIDTag, "")
+ sub.set(settingValueTag, "")
+
+ tree = ElementTree.ElementTree(root)
+ tree.write(self.xmlFile)
+ if formatXML:
+ pretty = self.Prettify(self.xmlFile)
+ f = open(self.xmlFile, "w")
+ f.write(pretty)
+ f.close()
+
+ def AppendChild(self, root, settingID, newValue, settingIDTag="id", settingValueTag="value"):
+ ElementTree.SubElement(root, self.childTag, attrib={settingIDTag: settingID, settingValueTag: newValue})
+ return root
+
+ def Prettify(self, elem, asString=False):
+ import xml.dom.minidom
+ if asString:
+ xml = xml.dom.minidom.parseString(elem)
+ pretty_xml_as_string = '\n'.join([line for line in xml.toprettyxml(indent=' ' * 2).split('\n') if line.strip()])
+ else:
+ pretty_xml_as_string = '\n'.join([line for line in xml.dom.minidom.parse(open(elem)).toprettyxml(indent=' ' * 2).split('\n') if line.strip()])
+ return pretty_xml_as_string
+#----------------------------------------------------------------
+# Legacy code, now use new function Compress
+def Archive_Tree(sourcefile, destfile, exclude_dirs=['temp'], exclude_files=['kodi.log','kodi.old.log','xbmc.log','xbmc.old.log','spmc.log','spmc.old.log'], message_header = 'ARCHIVING', message = 'Creating archive'):
+ Compress(src=sourcefile, dst=destfile, exclude_dirs=exclude_dirs, exclude_files=exclude_files)
+#----------------------------------------------------------------
+# TUTORIAL #
+def Compress(src,dst,compression='zip',parent=False, exclude_dirs=['temp'], exclude_files=['kodi.log','kodi.old.log','xbmc.log','xbmc.old.log','spmc.log','spmc.old.log'], message_header = 'ARCHIVING', message = 'Creating archive'):
+ """
+Compress files in either zip or tar format. This will most likely be replacing
+Archive_Tree longer term as this has better functionality but it's currently
+missing the custom message and exclude files options.
+
+IMPORTANT: There was a known bug where some certain compressed tar.gz files can cause the system to hang
+and a bad zipfile will continue to be made until it runs out of space on your storage device. In the unlikely
+event you encounter this issue just add the problematic file(s) to your exclude list. I think this has since
+been fixed since a complete re-code to this function, or at least I've been unable to recreate it. If you
+find this problem is still occuring please let me know on the forum at http://totalrevolution.tv/forum
+(user: trevdev), thankyou.
+
+CODE: Compress(src,dst,[compression,parent])
+
+AVAILABLE PARAMS:
+
+ (*) src - This is the source folder you want to compress
+
+ (*) dst - This is the destination file you want to create
+
+ compression - By default this is set to 'zip' but you can also use 'tar'
+
+ parent - By default this is set to False which means it will compress
+ everything inside the path given. If set to True it will do the same but
+ it will include the parent folder name - ideal if you want to zip up
+ an add-on folder and be able to install via Kodi Settings.
+
+ exclude_dirs - This is optional, if you have folder names you want to exclude just
+ add them here as a list item. By default the folder 'temp' is added to this list so
+ if you need to include folders called temp make sure you send through a list, even
+ if it's an empty one. The reason for leaving temp out is that's where Kodi logfiles
+ and crashlogs are stored on a lot of devices and these are generally not needed in
+ backup zips.
+
+ exclude_files - This is optional, if you have specific file names you want to
+ exclude just add them here as a list item. By default the list consists of:
+ 'kodi.log','kodi.old.log','xbmc.log','xbmc.old.log','spmc.log','spmc.old.log'
+
+EXAMPLE CODE:
+koding_path = koding.Physical_Path('special://home/addons/script.module.python.koding.aio')
+zip_dest = koding.Physical_Path('special://home/test_addon.zip')
+zip_dest2 = koding.Physical_Path('special://home/test_addon2.zip')
+tar_dest = koding.Physical_Path('special://home/test_addon.tar')
+tar_dest2 = koding.Physical_Path('special://home/test_addon2.tar')
+koding.Compress(src=koding_path,dst=zip_dest,compression='zip',parent=True)
+koding.Compress(src=koding_path,dst=zip_dest2,compression='zip',parent=False)
+koding.Compress(src=koding_path,dst=tar_dest,compression='tar',parent=True)
+koding.Compress(src=koding_path,dst=tar_dest2,compression='tar',parent=False)
+koding.Text_Box('CHECK HOME FOLDER','If you check your Kodi home folder you should now have 4 different compressed versions of the Python Koding add-on.\n\ntest_addon.zip: This has been zipped up with parent set to True\n\ntest_addon2.zip: This has been zipped up with parent set to False.\n\ntest_addon.tar: This has been compressed using tar format and parent set to True\n\ntest_addon2.tar: This has been compressed using tar format and parent set to False.\n\nFeel free to manually delete these.')
+~"""
+ import zipfile
+ import tarfile
+ directory = os.path.dirname(dst)
+ if not os.path.exists(directory):
+ try:
+ os.makedirs(directory)
+ except:
+ dialog.ok('ERROR','The destination directory you gave does not exist and it wasn\'t possible to create it.')
+ return
+ if compression == 'zip':
+ zip = zipfile.ZipFile(dst, 'w', compression=zipfile.ZIP_DEFLATED)
+ elif compression == 'tar':
+ zip = tarfile.open(dst, mode='w')
+ module_id = 'script.module.python.koding.aio'
+ this_module = xbmcaddon.Addon(id=module_id)
+ folder_size = Folder_Size(src,'mb')
+ available_space = Free_Space(HOME,'mb')
+ if os.path.exists(src):
+ choice = True
+ if float(available_space) < float(folder_size):
+ choice = dialog.yesno(this_module.getLocalizedString(30809), this_module.getLocalizedString(30810), this_module.getLocalizedString(30811) % folder_size, this_module.getLocalizedString(30812) % available_space, yeslabel = this_module.getLocalizedString(30813), nolabel = this_module.getLocalizedString(30814))
+ if choice:
+ root_len = len(os.path.dirname(os.path.abspath(src)))
+ for base, dirs, files in os.walk(src):
+ dirs[:] = [d for d in dirs if d not in exclude_dirs]
+ files[:] = [f for f in files if f not in exclude_files and not 'crashlog' in f and not 'stacktrace' in f]
+ archive_root = os.path.abspath(base)[root_len:]
+
+ for f in files:
+ fullpath = os.path.join(base, f)
+ if parent:
+ archive_name = os.path.join(archive_root, f)
+ if compression == 'zip':
+ zip.write(fullpath, archive_name, zipfile.ZIP_DEFLATED)
+ elif compression == 'tar':
+ zip.add(fullpath, archive_name)
+ else:
+ newpath = fullpath.split(src)[1]
+ if compression == 'zip':
+ zip.write(fullpath, newpath, zipfile.ZIP_DEFLATED)
+ elif compression == 'tar':
+ zip.add(fullpath, newpath)
+ zip.close()
+#----------------------------------------------------------------
+# TUTORIAL #
+def Create_Paths(path=''):
+ """
+Send through a path to a file, if the directories required do not exist this will create them.
+
+CODE: Create_Paths(path)
+
+AVAILABLE PARAMS:
+
+ (*) path - This is the full path including the filename. The path
+ sent through will be split up at every instance of '/'
+
+EXAMPLE CODE:
+my_path = xbmc.translatePath('special://home/test/testing/readme.txt')
+koding.Create_Paths(path=my_path)
+dialog.ok('PATH CREATED','Check in your Kodi home folder and you should now have sub-folders of /test/testing/.','[COLOR=gold]Press ok to remove these folders.[/COLOR]')
+shutil.rmtree(xbmc.translatePath('special://home/test'))
+~"""
+ home_path = Physical_Path('special://home')
+ path = path.replace(home_path,'')
+ newpath = os.path.join('special://home',path)
+ if path != '' and not os.path.exists(Physical_Path(newpath)):
+ root_path = path.split(os.sep)
+ if root_path[-1] == '':
+ root_path.pop()
+ root_path.pop()
+ final_path = ''
+ for item in root_path:
+ final_path = os.path.join(final_path,item)
+ final_path = os.path.join('special://home',final_path)
+ xbmcvfs.mkdirs(final_path)
+#----------------------------------------------------------------
+# TUTORIAL #
+def DB_Path_Check(db_path):
+ """
+If you need to find out the current "real" database in use then this is the function for you.
+It will scan for a specific database type (e.g. addons) and return the path to the one which was last updated.
+This is particularly useful if the system has previously updated to a newer version rather than a fresh install
+or if they've installed a "build" which contained old databases.
+
+CODE: DB_Path_Check(db_path)
+
+AVAILABLE VALUES:
+
+ (*) db_path - This is the string the database starts with.
+ If you want to find the path for the addons*.db you would use "addons"
+ as the value, if you wanted to find the path of the MyVideos*.db you would use
+ "myvideos" etc. - it is not case sensitive.
+
+EXAMPLE CODE:
+dbpath = koding.DB_Path_Check(db_path='addons')
+dialog.ok('ADDONS DB','The path to the current addons database is:',dbpath)
+~"""
+ finalfile = 0
+ dirs,databasepath = xbmcvfs.listdir(DATABASE)
+ for item in databasepath:
+ if item.lower().endswith('.db') and item.lower().startswith(db_path.lower()):
+ mydb = os.path.join(DATABASE,item)
+ lastmodified = xbmcvfs.Stat(mydb).st_mtime()
+ if lastmodified>finalfile:
+ finalfile = lastmodified
+ gooddb = mydb
+ return Physical_Path(gooddb)
+#---------------------------------------------------------------------------------------------------
+# TUTORIAL #
+def Delete_Crashlogs(extra_paths=[]):
+ """
+Delete all kodi crashlogs. This function will retun the amount of successfully removed crashlogs.
+
+CODE: Delete_Crashlogs([extra_paths])
+
+AVAILABLE PARAMS:
+ extra_paths - By default this will search for crashlogs for xbmc,
+ kodi and spmc. If you want to add compatibility for other forks of
+ Kodi please send through a list of the files you want deleted. The
+ format to use needs to be like example shown below.
+
+EXAMPLE CODE:
+# Lets setup some extra crashlog types for tvmc and ftmc kodi forks
+log_path = xbmc.translatePath('special://logpath/')
+tvmc_path = os.path.join(log_path,'tvmc_crashlog*.*')
+ftmc_path = os.path.join(log_path,'ftmc_crashlog*.*')
+
+
+deleted_files = koding.Delete_Crashlogs(extra_paths=[tvmc_path, ftmc_path])
+if deleted_files > 0:
+ dialog.ok('CRASHLOGS DELETED','Congratulations, a total of %s crashlogs have been deleted.')
+else:
+ dialog.ok('NO CRASHLOGS','No crashlogs could be found on the system.')
+~"""
+ import glob
+ log_path = 'special://logpath/'
+ xbmc_path = (os.path.join(log_path, 'xbmc_crashlog*.*'))
+ kodi_path = (os.path.join(log_path, 'kodi_crashlog*.*'))
+ spmc_path = (os.path.join(log_path, 'spmc_crashlog*.*'))
+ paths = [xbmc_path, kodi_path, spmc_path]
+ total = 0
+ for items in paths:
+ for file in glob.glob(items):
+ try:
+ xbmcvfs.delete(file)
+ total+=1
+ except:
+ pass
+ return total
+#----------------------------------------------------------------
+# TUTORIAL #
+def Delete_Files(filepath = HOME, filetype = '*.txt', subdirectories=False):
+ """
+Delete all specific filetypes in a path (including sub-directories)
+
+CODE: Delete_Files([filepath, filetype, subdirectories])
+
+AVAILABLE PARAMS:
+
+ (*) filepath - By default this points to the Kodi HOME folder (special://home).
+ The path you send through must be a physical path and not special://
+
+ (*) filetype - The type of files you want to delete, by default it's set to *.txt
+
+ subdirectories - By default it will only search the folder given, if set to True
+ all filetypes listed above will be deleted in the sub-directories too.
+
+WARNING: This is an extremely powerful and dangerous tool! If you wipe your whole system
+by putting in the wrong path then it's your own stupid fault!
+
+EXAMPLE CODE:
+delete_path = 'special://profile/addon_data/test'
+xbmcvfs.mkdirs(delete_path)
+test1 = os.path.join(delete_path,'test1.txt')
+test2 = os.path.join(delete_path,'test2.txt')
+koding.Text_File(test1,'w','testing1')
+koding.Text_File(test2,'w','testing2')
+dialog.ok('DELETE FILES','All *.txt files will be deleted from:', '', '/userdata/addon_data/test/')
+koding.Delete_Files(filepath=delete_path, filetype='.txt', subdirectories=True)
+~"""
+ filepath = Physical_Path(filepath)
+ if filepath == '/' or filepath == '.' or filepath == '' or (filepath[1]==':' and len(filepath)<4):
+ dialog.ok('IDTenT ERROR!!!','You are trying to wipe your whole system!!!','Be more careful in future, not everyone puts checks like this in their code!')
+ return
+
+ if os.path.exists(filepath):
+ filetype = filetype.replace('*','')
+
+ if subdirectories:
+ for parent, dirnames, filenames in os.walk(filepath):
+ for fn in filenames:
+ if fn.lower().endswith(filetype):
+ xbmcvfs.delete(os.path.join(parent, fn))
+
+ else:
+ for delete_file in xbmcvfs.listdir(filepath):
+ delete_path = os.path.join(filepath,delete_file)
+ if delete_path.endswith(filetype):
+ try:
+ xbmcvfs.delete(delete_path)
+ except:
+ xbmc.log(Last_Error(),2)
+ else:
+ xbmc.log('### Cannot delete files as directory does not exist: %s' % filepath,2)
+#----------------------------------------------------------------
+# TUTORIAL #
+def Delete_Folders(filepath='', ignore=[]):
+ """
+Completely delete a folder and all it's sub-folders. With the ability to add
+an ignore list for any folders/files you don't want removed.
+
+CODE: Delete_Folders(filepath, [ignore])
+
+AVAILABLE PARAMS:
+
+ (*) filepath - Use the physical path you want to remove, this must be converted
+ to the physical path and will not work with special://
+
+ ignore - A list of paths you want to ignore. These need to be sent
+ through as physical paths so just use koding.Physical_Path() when creating
+ your list and these can be folder paths or filepaths.
+
+WARNING: This is an extremely powerful and dangerous tool! If you wipe important
+system files from your system by putting in the wrong path then I'm afraid that's
+your own stupid fault! A check has been put in place so you can't accidentally
+wipe the whole root.
+
+EXAMPLE CODE:
+delete_path = koding.Physical_Path('special://profile/py_koding_test')
+
+# Create new test directory to remove
+if not os.path.exists(delete_path):
+ os.makedirs(delete_path)
+
+# Fill it with some dummy files
+file1 = os.path.join(delete_path,'file1.txt')
+file2 = os.path.join(delete_path,'file2.txt')
+file3 = os.path.join(delete_path,'file3.txt')
+koding.Dummy_File(dst=file1, size=10, size_format='kb')
+koding.Dummy_File(dst=file2, size=10, size_format='kb')
+koding.Dummy_File(dst=file3, size=10, size_format='kb')
+
+dialog.ok('TEST FILE CREATED','If you look in your userdata folder you should now see a new test folder containing 3 dummy files. The folder name is \'py_koding_test\'.')
+if dialog.yesno('[COLOR gold]DELETE FOLDER[/COLOR]','Everything except file1.txt will now be removed from:', '/userdata/py_koding_test/','Do you want to continue?'):
+ koding.Delete_Folders(filepath=delete_path, ignore=[file1])
+ dialog.ok('DELETE LEFTOVERS','When you press OK we will delete the whole temporary folder we created including it\'s contents')
+ koding.Delete_Folders(filepath=delete_path)
+~"""
+ exclude_list = ['','/','\\','C:/','storage']
+
+# Check you're not trying to wipe root!
+ if filepath in exclude_list:
+ dialog.ok('FILEPATH REQUIRED','You\'ve attempted to remove files but forgot to pass through a valid filepath. Luckily this failsafe check is in place or you could have wiped your whole system!')
+
+# If there's some ignore files we run through deleting everything but those files
+ elif len(ignore) > 0:
+ for root, dirs, files in os.walk(filepath, topdown=False):
+ cont = True
+ if not root in ignore:
+ for item in ignore:
+ if item in root:
+ cont=False
+ break
+ if cont:
+ for file in files:
+ file_path = os.path.join(root,file)
+ if file_path not in ignore:
+ try:
+ xbmcvfs.delete(file_path)
+ except:
+ xbmc.log('Failed to delete: %s'%file_path,2)
+ if len(os.listdir(root)) == 0:
+ try:
+ xbmcvfs.rmdir(root)
+ except:
+ pass
+
+# If a simple complete wipe of a directory and all sub-directories is required we use this
+ elif os.path.exists(filepath):
+ shutil.rmtree(filepath, ignore_errors=True)
+ # xbmc.executebuiltin('Container.Refresh')
+#----------------------------------------------------------------
+# TUTORIAL #
+def Dummy_File(dst= 'special://home/dummy.txt', size='10', size_format='mb'):
+ """
+Create a dummy file in whatever location you want and with the size you want.
+Use very carefully, this is designed for testing purposes only. Accidental
+useage can result in the devices storage becoming completely full in just a
+few seconds. If using a cheap poor quality device (like many android units)
+then you could even end up killing the device as some of them are made
+with very poor components which are liable to irreversable corruption.
+
+CODE: koding.Dummy_File(dest, [size, size_format])
+
+AVAILABLE PARAMS:
+
+ dst - This is the destination folder. This needs to be a FULL path including
+ the file extension. By default this is set to special://home/dummy.txt
+
+ size - This is an optional integer, by default a file of 10 MB will be created.
+
+ size_format - By default this is set to 'mb' (megabytes) but you can change this to
+ 'b' (bytes), 'kb' (kilobytes), 'gb' (gigabytes)
+
+EXAMPLE CODE:
+dummy = 'special://home/test_dummy.txt'
+koding.Dummy_File(dst=dummy, size=100, size_format='b')
+dialog.ok('DUMMY FILE CREATED','Check your Kodi home folder and you should see a 100 byte test_dummy.txt file.','[COLOR=gold]Press OK to delete this file.[/COLOR]')
+xbmcvfs.delete(dummy)
+~"""
+ dst = Physical_Path(dst)
+ xbmc.log('dst: %s'%dst,2)
+ if size_format == 'kb':
+ size = float(size*1024)
+ elif size_format == 'mb':
+ size = float(size*1024) * 1024
+ elif size_format == 'gb':
+ size = float(size*1024) * 1024 * 1024
+
+ xbmc.log('format: %s size: %s'%(size_format, size), 2)
+
+ f = open(dst,"wb")
+ f.seek(size-1)
+ f.write("\0")
+ f.close()
+#----------------------------------------------------------------
+# TUTORIAL #
+def End_Path(path):
+ """
+Split the path at every '/' and return the final file/folder name.
+If your path uses backslashes rather than forward slashes it will use
+that as the separator.
+
+CODE: End_Path(path)
+
+AVAILABLE PARAMS:
+
+ path - This is the path where you want to grab the end item name.
+
+EXAMPLE CODE:
+addons_path = 'special://home/addons'
+file_name = koding.End_Path(path=addons_path)
+dialog.ok('ADDONS FOLDER','Path checked:',addons_path,'Folder Name: [COLOR=dodgerblue]%s[/COLOR]'%file_name)
+file_path = 'special://home/addons/script.module.python.koding.aio/addon.xml'
+file_name = koding.End_Path(path=file_path)
+dialog.ok('FILE NAME','Path checked:',file_path,'File Name: [COLOR=dodgerblue]%s[/COLOR]'%file_name)
+~"""
+ if '/' in path:
+ path_array = path.split('/')
+ if path_array[-1] == '':
+ path_array.pop()
+ elif '\\' in path:
+ path_array = path.split('\\')
+ if path_array[-1] == '':
+ path_array.pop()
+ else:
+ return path
+ return path_array[-1]
+#----------------------------------------------------------------
+# TUTORIAL #
+def Extract(_in, _out, dp=None, show_error=False):
+ """
+This function will extract a zip or tar file and return true or false so unlike the
+builtin xbmc function "Extract" this one will pause code until it's completed the action.
+
+CODE: koding.Extract(src,dst,[dp])
+dp is optional, by default it is set to false
+
+AVAILABLE PARAMS:
+
+ (*) src - This is the source file, the actual zip/tar. Make sure this is a full path to
+ your zip file and also make sure you're not using "special://". This extract function
+ is only compatible with .zip/.tar/.tar.gz files
+
+ (*) dst - This is the destination folder, make sure it's a physical path and not
+ "special://...". This needs to be a FULL path, if you want it to extract to the same
+ location as where the zip is located you still have to enter the full path.
+
+ dp - This is optional, if you pass through the dp function as a DialogProgress()
+ then you'll get to see the status of the extraction process. If you choose not to add
+ this paramater then you'll just get a busy spinning circle icon until it's completed.
+ See the example below for a dp example.
+
+ show_error - By default this is set to False, if set to True an error dialog
+ will appear showing details of the file which failed to extract.
+
+EXAMPLE CODE:
+koding_path = koding.Physical_Path('special://home/addons/script.module.python.koding.aio')
+zip_dest = koding.Physical_Path('special://home/test_addon.zip')
+extract_dest = koding.Physical_Path('special://home/TEST')
+koding.Compress(src=koding_path,dst=zip_dest,compression='zip',parent=True)
+dp = xbmcgui.DialogProgress()
+dp.create('Extracting Zip','Please Wait')
+if koding.Extract(_in=zip_dest,_out=extract_dest,dp=dp,show_error=True):
+ dialog.ok('YAY IT WORKED!','We just zipped up your python koding add-on then extracted it to a new folder in your Kodi root directory called TEST. Press OK to delete these files.')
+ xbmcvfs.delete(zip_dest)
+ shutil.rmtree(extract_dest)
+else:
+ dialog.ok('BAD NEWS!','UH OH SOMETHING WENT HORRIBLY WRONG')
+~"""
+ import tarfile
+ import xbmcaddon
+ import zipfile
+
+ module_id = 'script.module.python.koding.aio'
+ this_module = xbmcaddon.Addon(id=module_id)
+ nFiles = 0
+ count = 0
+
+ if xbmcvfs.exists(_in):
+ if zipfile.is_zipfile(_in):
+ zin = zipfile.ZipFile(_in, 'r')
+ nFiles = float(len(zin.infolist()))
+ contents = zin.infolist()
+
+ elif tarfile.is_tarfile(_in):
+ zin = tarfile.open(_in)
+ contents = [tarinfo for tarinfo in zin.getmembers()]
+ nFiles = float(len(contents))
+
+ if nFiles > 0:
+ if dp:
+ try:
+ for item in contents:
+ count += 1
+ update = count / nFiles * 100
+ dp.update(int(update))
+ zin.extract(item, _out)
+ zin.close()
+ return True
+
+ except:
+ xbmc.log(Last_Error(),2)
+ return False
+ else:
+ try:
+ zin.extractall(_out)
+ return True
+ except:
+ xbmc.log(Last_Error(),2)
+ return False
+
+ else:
+ xbmc.log('NOT A VALID ZIP OR TAR FILE: %s' % _in,2)
+ else:
+ if show_error:
+ dialog.ok(this_module.getLocalizedString(30965),this_module.getLocalizedString(30815) % _in)
+#----------------------------------------------------------------
+# TUTORIAL #
+def Free_Space(dirname = HOME, filesize = 'b'):
+ """
+Show the amount of available free space in a path, this can be returned in a number of different formats.
+
+CODE: Free_Space([dirname, filesize])
+
+AVAILABLE PARAMS:
+
+ dirname - This optional, by default it will tell you how much space is available in your special://home
+ folder. If you require information for another path (such as a different partition or storage device)
+ then enter the physical path. This currently only works for local paths and not networked drives.
+
+ filesize - By default you'll get a return of total bytes, however you can get the value as bytes,
+ kilobytes, megabytes, gigabytes and terabytes..
+
+ VALUES:
+ 'b' = bytes (integer)
+ 'kb' = kilobytes (float to 1 decimal place)
+ 'mb' = kilobytes (float to 2 decimal places)
+ 'gb' = kilobytes (float to 3 decimal places)
+ 'tb' = terabytes (float to 4 decimal places)
+
+EXAMPLE CODE:
+HOME = Physical_Path('special://home')
+my_space = koding.Free_Space(HOME, 'gb')
+dialog.ok('Free Space','Available space in HOME: %s GB' % my_space)
+~"""
+ import ctypes
+ dirname = Physical_Path(dirname)
+ filesize = filesize.lower()
+ if xbmc.getCondVisibility('system.platform.windows'):
+ free_bytes = ctypes.c_ulonglong(0)
+ ctypes.windll.kernel32.GetDiskFreeSpaceExW(ctypes.c_wchar_p(dirname), None, None, ctypes.pointer(free_bytes))
+ finalsize = free_bytes.value
+ else:
+ st = os.statvfs(dirname)
+ finalsize = st.f_bavail * st.f_frsize
+ if filesize == 'b':
+ return finalsize
+ elif filesize == 'kb':
+ return "%.1f" % (float(finalsize / 1024))
+ elif filesize == 'mb':
+ return "%.2f" % (float(finalsize / 1024) / 1024)
+ elif filesize == 'gb':
+ return "%.3f" % (float(finalsize / 1024) / 1024 / 1024)
+ elif filesize == 'tb':
+ return "%.4f" % (float(finalsize / 1024) / 1024 / 1024 / 1024)
+#----------------------------------------------------------------
+# TUTORIAL #
+def Folder_Size(dirname = HOME, filesize = 'b'):
+ """
+Return the size of a folder path including sub-directories,
+this can be returned in a number of different formats.
+
+CODE: koding.Folder_Size([dirname, filesize])
+
+AVAILABLE PARAMS:
+
+ dirname - This optional, by default it will tell you how much space is available in your
+ special://home folder. If you require information for another path (such as a different
+ partition or storage device) then enter the physical path. This currently only works for
+ local paths and not networked drives.
+
+ filesize - By default you'll get a return of total bytes, however you can get the value as
+ bytes, kilobytes, megabytes, gigabytes and terabytes..
+
+ VALUES:
+ 'b' = bytes (integer)
+ 'kb' = kilobytes (float to 1 decimal place)
+ 'mb' = kilobytes (float to 2 decimal places)
+ 'gb' = kilobytes (float to 3 decimal places)
+ 'tb' = terabytes (float to 4 decimal places)
+
+EXAMPLE CODE:
+HOME = Physical_Path('special://home')
+home_size = Folder_Size(HOME, 'mb')
+dialog.ok('Folder Size','KODI HOME: %s MB' % home_size)
+~"""
+ finalsize = 0
+ for dirpath, dirnames, filenames in os.walk(dirname):
+ for f in filenames:
+ fp = os.path.join(dirpath, f)
+ finalsize += os.path.getsize(fp)
+ if filesize == 'b':
+ return finalsize
+ elif filesize == 'kb':
+ return "%.1f" % (float(finalsize / 1024))
+ elif filesize == 'mb':
+ return "%.2f" % (float(finalsize / 1024) / 1024)
+ elif filesize == 'gb':
+ return "%.3f" % (float(finalsize / 1024) / 1024 / 1024)
+ elif filesize == 'tb':
+ return "%.4f" % (float(finalsize / 1024) / 1024 / 1024 / 1024)
+#----------------------------------------------------------------
+# TUTORIAL #
+def Fresh_Install(keep_addons=[],ignore=[],keepdb=True):
+ """
+Attempt to completely wipe your install. You can send through a list
+of addons or paths you want to ignore (leave in the setup) or you can
+leave blank. If left blank and the platform is OpenELEC or LibreELEC
+it will perform a hard reset command followed by a reboot.
+
+CODE: Fresh_Install([keep_addons, ignore, keepdb)
+
+AVAILABLE PARAMS:
+
+ keep_addons - This is optional, if you have specific add-ons you want to omit
+ from the wipe (leave intact) then just enter a list of add-on id's here. The code
+ will determine from the addon.xml file which dependencies and sub-dependencies are
+ required for that add-on so there's no need to create a huge list, you only need to
+ list the master add-on id's. For example if you want to keep the current skin and
+ your add-on you would use: keep_addons=['plugin.program.myaddon',System('currentskin')]
+ and all addons/dependencies associated with those two add-ons will be added to the ignore
+ list.
+
+ ignore - This is optional, you can send through a list of paths you want to omit from
+ the wipe. You can use folder paths to skip the whole folder or you can use individual
+ file paths. Please make sure you use the physical path and not special://
+ So before creating your list make sure you use xbmc.translatePath()
+
+ keepdb - By default this is set to True which means the code will keep all the Kodi databases
+ intact and perform a profile reload once wipe is complete. This will mean addons, video, music,
+ epg, ADSP and viewtypes databases will remain completely untouched and Kodi should be fine to use
+ without the need for a restart. If you set keepdb to False nothing will happen once the wipe has
+ completed and it's up to you to choose what to do in your main code. I would highly recommend an
+ ok dialog followed by xbmc.executebuiltin('Quit'). This will force Kodi to recreate all the relevant
+ databases when they re-open. If you try and continue using Kodi without restarting the databases
+ will not be recreated and you risk corruption.
+
+EXAMPLE CODE:
+if dialog.yesno('[COLOR gold]TOTAL WIPEOUT![/COLOR]','This will attempt give you a totally fresh install of Kodi.','Are you sure you want to continue?'):
+ if dialog.yesno('[COLOR gold]FINAL CHANCE!!![/COLOR]','If you click Yes this WILL attempt to wipe your install', '[COLOR=dodgerblue]ARE YOU 100% CERTAIN YOU WANT TO WIPE?[/COLOR]'):
+ clean_state = koding.Fresh_Install()
+~"""
+# If it's LE/OE and there are no files to ignore we do a hard reset
+ from systemtools import Cleanup_Textures
+ if ( len(ignore)==0 ) and ( len(keep_addons)==0 ) and ( xbmc.getCondVisibility("System.HasAddon(service.libreelec.settings)") or xbmc.getCondVisibility("System.HasAddon(service.openelec.settings)") ):
+ xbmc.log('OE DETECTED',2)
+ resetpath='storage/.cache/reset_oe'
+ Text_File(resetpath,'w')
+ xbmc.executebuiltin('reboot')
+ else:
+ from addons import Dependency_Check
+ xbmc.log('DOING MAIN WIPE',2)
+ skip_array = []
+ addonsdb = DB_Path_Check('addons')
+ textures = DB_Path_Check('Textures')
+ Cleanup_Textures(frequency=1,use_count=999999)
+ if len(keep_addons) > 0:
+ ignorelist = Dependency_Check(addon_id = keep_addons, recursive = True)
+ for item in ignorelist:
+ skip_array.append(xbmcaddon.Addon(id=item).getAddonInfo('path'))
+ skip_array.append(addonsdb)
+ skip_array.append(textures)
+ if keepdb:
+ try:
+ skip_array.append( DB_Path_Check('Epg') )
+ except:
+ xbmc.log('No EPG DB Found, skipping',2)
+ try:
+ skip_array.append( DB_Path_Check('MyVideos') )
+ except:
+ xbmc.log('No MyVideos DB Found, skipping',2)
+ try:
+ skip_array.append( DB_Path_Check('MyMusic') )
+ except:
+ xbmc.log('No MyMusic DB Found, skipping',2)
+ try:
+ skip_array.append( DB_Path_Check('TV') )
+ except:
+ xbmc.log('No TV DB Found, skipping',2)
+ try:
+ skip_array.append( DB_Path_Check('ViewModes') )
+ except:
+ xbmc.log('No ViewModes DB Found, skipping',2)
+ try:
+ skip_array.append( DB_Path_Check('ADSP') )
+ except:
+ xbmc.log('No ADSP DB Found, skipping',2)
+ for item in ignore:
+ skip_array.append(item)
+ Delete_Folders(filepath=HOME, ignore=skip_array)
+ Refresh()
+ if keepdb:
+ Refresh('profile')
+
+# Good option for wiping android data but not so good if using the app as a launcher!
+ # elif xbmc.getCondVisibility('System.Platform.Android'):
+ # import subprocess
+ # running = Running_App()
+ # cleanwipe = subprocess.Popen(['exec ''pm clear '+str(running)+''], executable='/system/bin/sh', shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, preexec_fn=Get_ID(setid=True)).communicate()[0]
+#----------------------------------------------------------------
+# TUTORIAL #
+def Get_Contents(path,folders=True,subfolders=False,exclude_list=[],full_path=True,filter=''):
+ """
+Return a list of either files or folders in a given path.
+
+CODE: Get_Contents(path, [folders, subfolders, exclude_list, full_path, filter])
+
+AVAILABLE PARAMS:
+
+ (*) path - This is the path you want to search, no sub-directories are scanned.
+
+ folders - By default this is set to True and the returned list will only
+ show folders. If set to False the returned list will show files only.
+
+ exclude_list - Optionally you can add a list of items you don't want returned
+
+ full_path - By default the entries in the returned list will contain the full
+ path to the folder/file. If you only want the file/folder name set this to False.
+
+ subfolders - By default this is set to False but if set to true it will check
+ all sub-directories and not just the directory sent through.
+
+ filter - If you want to only return files ending in a specific string you
+ can add details here. For example to only show '.xml' files you would send
+ through filter='.xml'.
+
+EXAMPLE CODE:
+ADDONS = Physical_Path('special://home/addons')
+addon_folders = koding.Get_Contents(path=ADDONS, folders=True, exclude_list=['packages','temp'], full_path=False)
+results = ''
+for item in addon_folders:
+ results += 'FOLDER: [COLOR=dodgerblue]%s[/COLOR]\n'%item
+koding.Text_Box('ADDON FOLDERS','Below is a list of folders found in the addons folder (excluding packages and temp):\n\n%s'%results)
+~"""
+ final_list = []
+ path = Physical_Path(path)
+# Check all items in the given path
+ if not subfolders:
+ dirs,files = xbmcvfs.listdir(path)
+ if folders:
+ active_list = dirs
+ else:
+ active_list = files
+ for item in active_list:
+ if item not in exclude_list:
+ if full_path:
+ final_list.append(os.path.join(path,item))
+ else:
+ final_list.append(item)
+
+# Traverse through all subfolders
+ else:
+ path = Physical_Path(path)
+ for root, dirnames, filenames in os.walk(path):
+ if not folders:
+ for filename in filenames:
+ file_path = os.path.join(root, filename)
+ if filter=='':
+ if full_path:
+ final_list.append(file_path)
+ else:
+ final_list.append(filename)
+
+ elif file_path.endswith(filter):
+ if full_path:
+ final_list.append(file_path)
+ else:
+ final_list.append(filename)
+ else:
+ for dirname in dirnames:
+ if full_path:
+ final_list.append(os.path.join(root, dirname))
+ else:
+ final_list.append(dirname)
+ return final_list
+#----------------------------------------------------------------
+# TUTORIAL #
+def Move_Tree(src, dst, dp=None):
+ """
+Move a directory including all sub-directories to a new location.
+This will automatically create the new location if it doesn't already
+exist and it wierwrite any existing entries if they exist.
+
+CODE: koding.Move_Tree(src, dst)
+
+AVAILABLE PARAMS:
+
+ (*) src - This is source directory that you want to copy
+
+ (*) dst - This is the destination location you want to copy a directory to.
+
+ dp - This is optional, if you pass through the dp function as a DialogProgress()
+ then you'll get to see the status of the move process. See the example below for a dp example.
+
+EXAMPLE CODE:
+dp = xbmcgui.DialogProgress()
+source = koding.Physical_Path('special://profile/move_test')
+
+# Lets create a 500MB dummy file so we can move and see dialog progress
+dummy = os.path.join(source,'dummy')
+if not os.path.exists(source):
+ os.makedirs(source)
+koding.Dummy_File(dst=dummy+'1.txt', size=10, size_format='mb')
+koding.Dummy_File(dst=dummy+'2.txt', size=10, size_format='mb')
+koding.Dummy_File(dst=dummy+'3.txt', size=10, size_format='mb')
+koding.Dummy_File(dst=dummy+'4.txt', size=10, size_format='mb')
+koding.Dummy_File(dst=dummy+'5.txt', size=10, size_format='mb')
+koding.Dummy_File(dst=dummy+'6.txt', size=10, size_format='mb')
+dialog.ok('DUMMY FILE CREATED','If you want to check in your userdata folder you should have a new folder called "move_test" which has 6x 10MB dummy files.')
+
+# This is optional but if you want to see a dialog progress then you'll need this
+dp.create('MOVING FILES','Please Wait')
+
+destination = koding.Physical_Path('special://home/My MOVED Dummy File')
+koding.Move_Tree(source, destination, dp)
+dialog.ok('CHECK YOUR KODI HOME FOLDER','Please check your Kodi home folder, the dummy file should now have moved in there. When you press OK it will be removed')
+shutil.rmtree(destination)
+~"""
+ src = Physical_Path(src)
+ dst = Physical_Path(dst)
+ if dp:
+ totalfiles = 0
+ for root, dirs, files in os.walk(src):
+ totalfiles += len(files)
+ count = 0
+
+ for src_dir, dirs, files in os.walk(src):
+ dst_dir = src_dir.replace(src, dst, 1)
+ if os.path.exists(dst_dir) and not os.path.isdir(dst_dir):
+ try:
+ os.remove(dst_dir)
+ except:
+ xbmc.log('File with same name as folder exists, need to manually delete:',2)
+ xbmc.log(dst_dir,2)
+ if not os.path.exists(dst_dir):
+ os.makedirs(dst_dir)
+ for file_ in files:
+ src_file = os.path.join(src_dir, file_)
+ dst_file = os.path.join(dst_dir, file_)
+ if os.path.exists(dst_file) and dst_file != dst:
+ os.remove(dst_file)
+ try:
+ os.rename(src_file,dst_file)
+ except:
+ shutil.move(src_file, dst_dir)
+ if dp:
+ try:
+ count += 1
+ update = count / totalfiles * 100
+ dp.update(int(update))
+ except:
+ pass
+ try:
+ shutil.rmtree(src)
+ except:
+ pass
+
+ if dp:
+ dp.close()
+#----------------------------------------------------------------
+# TUTORIAL #
+def Physical_Path(path='special://home'):
+ """
+Send through a special:// path and get the real physical path returned.
+This has been written due to the problem where if you're running the Windows Store
+version of Kodi the built-in xbmc.translatePath() function is returning bogus results
+making it impossible to access databases.
+
+CODE: koding.Physical_Path([path])
+
+AVAILABLE PARAMS:
+
+ path - This is the path to the folder/file you want returned. This is optional,
+ if you leave this out it will just return the path to the root directory (special://home)
+
+EXAMPLE CODE:
+location = 'special://home/addons/script.module.python.koding.aio'
+real_location = koding.Physical_Path(location)
+xbmc.log(real_location,2)
+dialog.ok('PHYSICAL PATH','The real path of special://home/addons/script.module.python.koding.aio is:','[COLOR=dodgerblue]%s[/COLOR]'%real_location)
+~"""
+ xbmc_install = xbmc.translatePath('special://xbmc')
+ if not "WindowsApps" in xbmc_install:
+ clean = xbmc.translatePath(path)
+ if sys.platform == 'win32':
+ clean = clean.replace('\/','\\')
+ else:
+ clean = xbmc.translatePath(path)
+ if clean.startswith(xbmc_install):
+ if sys.platform == 'win32':
+ clean = clean.replace('\/','\\')
+ else:
+ return clean.replace('AppData\\Roaming\\','AppData\\Local\\Packages\\XBMCFoundation.Kodi_4n2hpmxwrvr6p\\LocalCache\\Roaming\\')
+ if sys.platform == 'win32':
+ clean = clean.replace('\/','\\')
+ return clean
+#----------------------------------------------------------------
+# TUTORIAL #
+def Text_File(path, mode, text = ''):
+ """
+Open/create a text file and read/write to it.
+
+CODE: koding.Text_File(path, mode, [text])
+
+AVAILABLE PARAMS:
+
+ (*) path - This is the path to the text file
+
+ (*) mode - This can be 'r' (for reading) or 'w' (for writing)
+
+ text - This is only required if you're writing to a file, this
+ is the text you want to enter. This will completely overwrite any
+ text already in the file.
+
+EXAMPLE CODE:
+HOME = koding.Physical_Path('special://home')
+koding_test = os.path.join(HOME, 'koding_test.txt')
+koding.Text_File(path=koding_test, mode='w', text='Well done, you\'ve created a text file containing this text!')
+dialog.ok('CREATE TEXT FILE','If you check your home Kodi folder and you should now have a new koding_test.txt file in there.','[COLOR=gold]DO NOT DELETE IT YET![/COLOR]')
+mytext = koding.Text_File(path=koding_test, mode='r')
+dialog.ok('TEXT FILE CONTENTS','The text in the file created is:','[COLOR=dodgerblue]%s[/COLOR]'%mytext,'[COLOR=gold]CLICK OK TO DELETE THE FILE[/COLOR]')
+try:
+ os.remove(koding_test)
+except:
+ dialog.ok('FAILED TO REMOVE','Could not remove the file, looks like you might have it open in a text editor. Please manually remove yourself')
+~"""
+ try:
+ textfile = xbmcvfs.File(path, mode)
+
+ if mode == 'r':
+ content = textfile.read()
+ textfile.close()
+ return content
+
+ if mode == 'w':
+ textfile.write(text)
+ textfile.close()
+ return True
+
+ except:
+ xbmc.log(Last_Error(),2)
+ return False
+#----------------------------------------------------------------
\ No newline at end of file
diff --git a/script.module.python.koding.aio/lib/koding/guitools.py b/script.module.python.koding.aio/lib/koding/guitools.py
new file mode 100644
index 0000000..b397abf
--- /dev/null
+++ b/script.module.python.koding.aio/lib/koding/guitools.py
@@ -0,0 +1,796 @@
+# -*- coding: utf-8 -*-
+
+# script.module.python.koding.aio
+# Python Koding AIO (c) by TOTALREVOLUTION LTD (support@trmc.freshdesk.com)
+
+# Python Koding AIO is licensed under a
+# Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.
+
+# You should have received a copy of the license along with this
+# work. If not, see http://creativecommons.org/licenses/by-nc-nd/4.0.
+
+# Please make sure you've read and understood the license, this code can NOT be used commercially
+# and it can NOT be modified and redistributed. If you're found to be in breach of this license
+# then any affected add-ons will be blacklisted and will not be able to work on the same system
+# as any other add-ons which use this code. Thank you for your cooperation.
+
+import os
+import sys
+import xbmc
+import xbmcgui
+import xbmcvfs
+from systemtools import Last_Error
+from filetools import Physical_Path
+dialog = xbmcgui.Dialog()
+koding_path = Physical_Path("special://home/addons/script.module.python.koding.aio")
+#----------------------------------------------------------------
+# TUTORIAL #
+def Browse_To_Folder(header='Select the folder you want to use', path = 'special://home'):
+ """
+As the title suggests this will bring up a dialog that allows the user to browse to a folder
+and the path is then returned.
+
+IMPORTANT: Do not confuse this with the Browse_To_File function
+
+CODE: Browse_To_Folder(header, path)
+
+AVAILABLE PARAMS:
+header - As the name suggests this is a string to be used for the header/title
+of the window. The default is "Select the folder you want to use".
+
+path - Optionally you can add a default path for the browse start folder.
+The default start position is the Kodi HOME folder.
+
+EXAMPLE CODE:
+folder = koding.Browse_To_Folder(header='Choose a folder you want to use', path='special://home/userdata')
+dialog.ok('FOLDER DETAILS','Folder path: [COLOR=dodgerblue]%s[/COLOR]'%folder)
+~"""
+ text = dialog.browse(type=3, heading=header, shares='files', useThumbs=False, treatAsFolder=False, defaultt=path)
+ return text
+#----------------------------------------------------------------
+# TUTORIAL #
+def Browse_To_File(header='Select the file you want to use', path='special://home/addons/', extension='', browse_in_archives=False):
+ """
+This will allow the user to browse to a specific file and return the path.
+
+IMPORTANT: Do not confuse this with the Browse_To_Folder function
+
+CODE: koding.Browse_To_File([header, path, extension, browse_in_archives])
+
+AVAILABLE PARAMS:
+
+ header - As the name suggests this is a string to be used for the header/title
+ of the window. The default is "Select the file you want to use".
+
+ path - Optionally you can add a default path for the browse start folder.
+ The default start position is the Kodi HOME folder.
+
+ extension - Optionally set extensions to filter by, let's say you only wanted
+ zip and txt files to show you would send through '.zip|.txt'
+
+ browse_in_archives - Set to true if you want to be able to browse inside zips and
+ other archive files. By default this is set to False.
+
+EXAMPLE CODE:
+dialog.ok('BROWSE TO FILE 1','We will now browse to your addons folder with browse_in_archives set to [COLOR dodgerblue]False[/COLOR]. Try clicking on a zip file if you can find one (check packages folder).')
+folder = koding.Browse_To_File(header='Choose a file you want to use', path='special://home/addons')
+dialog.ok('FOLDER DETAILS','File path: [COLOR=dodgerblue]%s[/COLOR]'%folder)
+dialog.ok('BROWSE TO FILE 2','We will now browse to your addons folder with browse_in_archives set to [COLOR dodgerblue]True[/COLOR]. Try clicking on a zip file if you can find one (check packages folder).')
+folder = koding.Browse_To_File(header='Choose a file you want to use', path='special://home/addons', browse_in_archives=True)
+dialog.ok('FOLDER DETAILS','File path: [COLOR=dodgerblue]%s[/COLOR]'%folder)
+~"""
+ if not path.endswith(os.sep):
+ path += os.sep
+ try:
+ text = dialog.browse(type=1, heading=header, shares='myprograms', mask=extension, useThumbs=False, treatAsFolder=browse_in_archives, defaultt=path)
+ except:
+ text = dialog.browse(type=1, heading=header, s_shares='myprograms', mask=extension, useThumbs=False,
+ treatAsFolder=browse_in_archives, defaultt=path)
+ return text
+#----------------------------------------------------------------
+# TUTORIAL #
+def Countdown(title='COUNTDOWN STARTED', message='A quick simple countdown example.', update_msg='Please wait, %s seconds remaining.', wait_time=10, allow_cancel=True, cancel_msg='[COLOR=gold]Sorry, this process cannot be cancelled[/COLOR]'):
+ """
+Bring up a countdown timer and return true if waited or false if cancelled.
+
+CODE: Countdown(title, message, update_msg, wait_time, allow_cancel, cancel_msg):
+
+AVAILABLE PARAMS:
+
+ title - The header string in the dialog window, the default is:
+ 'COUNTDOWN STARTED'
+
+ message - A short line of info which will show on the first line
+ of the dialog window just below the title. Default is:
+ 'A quick simple countdown example.'
+
+ update_msg - The message you want to update during the countdown.
+ This must contain a %s which will be replaced by the current amount
+ of seconds that have passed. The default is:
+ 'Please wait, %s seconds remaining.'
+
+ wait_time - This is the amount of seconds you want the countdown to
+ run for. The default is 10.
+
+ allow_cancel - By default this is set to true and the user can cancel
+ which will result in False being returned. If this is set to True
+ they will be unable to cancel.
+
+ cancel_msg - If allow_cancel is set to False you can add a custom
+ message when the user tries to cancel. The default string is:
+ '[COLOR=gold]Sorry, this process cannot be cancelled[/COLOR]'
+
+EXAMPLE CODE:
+dialog.ok('COUNTDOWN EXAMPLE', 'Press OK to bring up a countdown timer', '', 'Try cancelling the process.')
+my_return = koding.Countdown(title='COUNTDOWN EXAMPLE', message='Quick simple countdown message (cancel enabled).', update_msg='%s seconds remaining', wait_time=5)
+if my_return:
+ dialog.ok('SUCCESS!','Congratulations you actually waited through the countdown timer without cancelling!')
+else:
+ dialog.ok('BORED MUCH?','What happened, did you get bored waiting?', '', '[COLOR=dodgerblue]Let\'s set off another countdown you CANNOT cancel...[/COLOR]')
+ koding.Countdown(title='COUNTDOWN EXAMPLE', message='Quick simple countdown message (cancel disabled).', update_msg='%s seconds remaining', wait_time=5, allow_cancel=False, cancel_msg='[COLOR=gold]Sorry, this process cannot be cancelled[/COLOR]')
+~"""
+ dp = xbmcgui.DialogProgress()
+ current = 0
+ increment = 100 / wait_time
+ cancelled = False
+
+ dp.create(title)
+ while current <= wait_time:
+ if (dp.iscanceled()):
+ if allow_cancel:
+ cancelled = True
+ break
+ else:
+ dp.create(title,cancel_msg)
+
+ if current != 0:
+ xbmc.sleep(1000)
+
+ remaining = wait_time - current
+ if remaining == 0:
+ percent = 100
+ else:
+ percent = increment * current
+
+ remaining_display = update_msg % remaining
+ dp.update(percent, message, remaining_display)
+
+ current += 1
+
+ if cancelled == True:
+ return False
+ else:
+ return True
+#----------------------------------------------------------------
+# TUTORIAL #
+def Custom_Dialog(pos='center', dialog='Text', size='700x500', button_width=200, icon='', fanart='',\
+ header='Disclaimer', main_content='Add some text here', buttons=['Decline','Agree'],\
+ header_color='gold', text_color='white', background='000000', transparency=100,\
+ highlight_color='gold', button_color_focused='4e91cf', button_trans_focused=100,\
+ button_color_nonfocused='586381', button_trans_nonfocused=50):
+ """
+A fully customisable dialog where you can have as many buttons as you want.
+Similar behaviour to the standard Kodi yesno dialog but this allows as many buttons
+as you want, as much text as you want (with a slider) as well as fully configurable
+sizing and positioning.
+
+CODE: Custom_Dialog([pos, dialog, size, button_width, header, main_content, buttons,\
+ header_color, text_color, background, transparency, highlight_color, button_color_focused,\
+ button_trans_focused, button_color_nonfocused, button_trans_nonfocused])
+
+AVAILABLE PARAMS:
+
+ pos - This is the co-ordinates of where on the screen you want the
+ dialog to appear. This needs to be sent through as a string so for
+ example if you want the dialog top left corner to be 20px in and
+ 10px down you would use pos='20x10'. By default this is set to 'center'
+ which will center the dialog on the screen.
+
+ dialog - By default this is set to 'Text'. Currently that is the
+ only custom dialog available but there are plans to improve upon this
+ and allow for image and even video dialogs.
+
+ size - Sent through as a string this is the dimensions you want the
+ dialog to be, by default it's set to '700x500' but you can set to any
+ size you want using that same format. Setting to 'fullscreen' will
+ use 1280x720 (fullscreen).
+
+ button_width - This is sent through as an integer and is the width you
+ want your buttons to be. By default this is set to 200 which is quite large
+ but looks quite nice if using only 2 or 3 buttons.
+
+ icon - If sent through this will be shown in the top right corner of your dialog,
+ make sure your first few lines of text aren't too long or they will overlap on top
+ of the image which is 150x150 pixels.
+
+ fanart - If sent through this will be the background image of your custom dialog.
+ Ideal if you want to only show an image, any text sent through will be overlayed
+ on top of this fanart.
+
+ header - Sent through as a string this is the header shown in the dialog.
+ The default is 'Disclaimer'.
+
+ header_color - Set the text colour, by default it's 'gold'
+
+ text_color - Set the text colour, by default it's 'white'
+
+ main_content - This is sent through as a string and is the main message text
+ you want to show in your dialog. When the ability to add videos, images etc.
+ is added there may well be new options added to this param but it will remain
+ backwards compatible.
+
+ buttons - Sent through as a list (tuple) this is a list of all your buttons.
+ Make sure you do not duplicate any names otherwise it will throw off the
+ formatting of the dialog and you'll get false positives with the results.
+
+ background - Optionally set the background colour (hex colour codes required).
+ The default is '000000' (black).
+
+ transparency - Set the percentage of transparency as an integer. By default
+ it's set to 100 which is a solid colour.
+
+ highlight_color - Set the highlighted text colour, by default it's 'gold'
+
+ button_color_focused - Using the same format as background you can set the
+ colour to use for a button when it's focused.
+
+ button_trans_focused - Using the same format as transparency you can set the
+ transparency amount to use on the button when in focus.
+
+ button_color_nonfocused - Using the same format as background you can set the
+ colour to use for buttons when they are not in focus.
+
+ button_trans_nonfocused - Using the same format as transparency you can set the
+ transparency amount to use on the buttons when not in focus.
+
+EXAMPLE CODE:
+main_text = 'This is my main text.\n\nYou can add anything you want in here and the slider will allow you to see all the contents.\n\nThis example shows using a blue background colour and a transparency of 90%.\n\nWe have also changed the highlighted_color to yellow.'
+my_buttons = ['button 1', 'button 2', 'button 3']
+my_choice = koding.Custom_Dialog(main_content=main_text,pos='center',buttons=my_buttons,background='213749',transparency=90,highlight_color='yellow')
+dialog.ok('CUSTOM DIALOG 1','You selected option %s'%my_choice,'The value of this is: [COLOR=dodgerblue]%s[/COLOR]'%my_buttons[my_choice])
+
+main_text = 'This is example 2 with no fancy colours, just a fullscreen and a working scrollbar.\n\nYou\'ll notice there are also a few more buttons on this one.\n\nline 1\nline 2\nline 3\nline 4\nline 5\nline 6\nline 7\nline 8\nline 9\nline 10\nline 11\nline 12\nline 13\nline 14\nline 15\nline 16\nline 17\nline 18\nline 19\nline 20\n\nYou get the idea we\'ll stop there!'
+my_buttons = ['button 1', 'button 2', 'button 3','button 4', 'button 5', 'button 6','button 7', 'button 8', 'button 9','button 10', 'button 11', 'button 12', 'button 13','button 14', 'button 15', 'button 16','button 17', 'button 18', 'button 19','button 20']
+my_choice = koding.Custom_Dialog(main_content=main_text,pos='center',size='fullscreen',buttons=my_buttons)
+dialog.ok('CUSTOM DIALOG 2','You selected option %s'%my_choice,'The value of this is: [COLOR=dodgerblue]%s[/COLOR]'%my_buttons[my_choice])
+~"""
+ skin_path = os.path.join(koding_path,"resources","skins","Default","720p")
+ ACTION = -1
+ # Convert the transparency percentage to hex
+ transparency = float(transparency) / 100 * 255
+ transparency = hex(int(transparency)).split('x')[1]
+ button_trans_focused = float(button_trans_focused) / 100 * 255
+ button_trans_focused = hex(int(button_trans_focused)).split('x')[1]
+ button_trans_nonfocused = float(button_trans_nonfocused) / 100 * 255
+ button_trans_nonfocused = hex(int(button_trans_nonfocused)).split('x')[1]
+
+ # Work out the dialog dimensions
+ if size == 'fullscreen':
+ dialog_width = '1280'
+ dialog_height = '720'
+ else:
+ dialog_width, dialog_height = size.split('x')
+
+ # Set the background to black image if not set otherwise remove background/transparency
+ if fanart != '' and xbmcvfs.exists(fanart):
+ background = ''
+ transparency = ''
+ else:
+ fanart = 'DialogBack.png'
+
+ button_count = len(buttons)
+ buttons_per_row = (int(dialog_width)-25) / (button_width+25)
+ if buttons_per_row > button_count:
+ buttons_per_row = button_count
+
+ # work out the number of rows, round up if a float
+ button_rows = int(button_count/buttons_per_row) + (button_count % buttons_per_row > 0)
+
+ # Work out the positioning of the dialog
+ if pos == 'center':
+ posx = str( (1280 - int(dialog_width)) / 2)
+ posy = str( (720 - int(dialog_height)) / 2)
+ else:
+ posx, posy = pos.split(',')
+
+# Work out the text area size
+ text_width = str( int(dialog_width)-80 )
+ text_height = str( (int(dialog_height)-(50*(button_rows+1)))-70 )
+ scroll_pos = str( int(text_width)+32 )
+ button_max = int(dialog_height)-30
+ iconx = str(int(text_width)-150)
+
+ # Work out the button positions
+ if dialog == 'Text':
+ button_spacing = ( int(dialog_width)-(buttons_per_row*button_width) ) / (buttons_per_row+1)
+ buttons_dict = {}
+ counter = 1
+ row = 1
+ # Create a dictionary of button positioning
+ for button in buttons:
+ if counter > buttons_per_row:
+ counter = 1
+ row += 1
+ # If starting a new line reset the values
+ if counter > buttons_per_row or counter == 1:
+ current_pos = button_spacing
+ counter += 1
+ else:
+ current_pos = current_pos+button_width+button_spacing
+ counter += 1
+
+ buttons_dict[button] = [str(current_pos),row]
+
+ # Set the dialog template name and new temporary "live" XML
+ dialog_type = dialog.capitalize()+'.xml'
+ dialog_new = 'temp.xml'
+ dialog_path = os.path.join(skin_path,dialog_type)
+ temp_path = os.path.join(skin_path,dialog_new)
+
+ button_num = 100
+ counter = 1
+ buttons_code = ''
+ for button in buttons:
+ if buttons_dict[button][1] == 1:
+ onup = 99
+ else:
+ onup = button_num-buttons_per_row
+
+ # If button is on the last row we set down to scrollbar
+ if buttons_dict[button][1] == button_rows:
+ ondown = 99
+ # Otherwise set down to the item on row below
+ elif buttons_dict[button][1] != button_rows:
+ ondown = button_num+buttons_per_row
+
+ # Set the vertical position (y) of the buttons
+ button_y = str( int(text_height)+(buttons_dict[button][1]*50)+40 )
+ if ( int(text_height) < 200 ) or ( int(button_y) > button_max ):
+ if size != 'fullscreen':
+ xbmcgui.Dialog().ok('WE NEED A BIGGER WINDOW!','The amount of buttons sent through do not fit in this window. Either make the button width smaller or make a bigger window')
+ else:
+ xbmcgui.Dialog().ok('SMALLER BUTTONS NEEDED!','The amount of buttons sent through do not fit in this window. Either send through less buttons or decrease their width using the button_width param.')
+ return
+ button_x = str( buttons_dict[button][0] )
+
+ buttons_code += '\
+ \n\
+ %s\n\
+ %s\n\
+ %s\n\
+ 40\n\
+ \n\
+ DialogBack.png\n\
+ DialogBack.png\n\
+ font12_title\n\
+ %s\n\
+ %s\n\
+ center\n\
+ %s\n\
+ %s\n\
+ %s\n\
+ %s\n\
+ \n' % (button_num, button_x, button_y, button_width, buttons[counter-1],\
+ button_trans_focused, button_color_focused, button_trans_nonfocused,\
+ button_color_nonfocused, text_color, highlight_color, button_num-1,\
+ button_num+1, onup, ondown)
+ button_num += 1
+ counter += 1
+
+ # Grab contents of the template and replace with our new values
+ with open(dialog_path, 'r') as content_file:
+ content = content_file.read()
+ content = content.replace('dialog_width',dialog_width)\
+ .replace('dialog_height',dialog_height)\
+ .replace('text_width',text_width)\
+ .replace('text_height',text_height)\
+ .replace('pos_x',posx)\
+ .replace('pos_y',posy)\
+ .replace('PK_Icon',icon)\
+ .replace('PK_I_X',iconx)\
+ .replace('PK_Fanart',fanart)\
+ .replace('PK_Transparency',transparency)\
+ .replace('PK_Color',background)\
+ .replace('PK_Text_Color',text_color)\
+ .replace('PK_Header_Color',header_color)\
+ .replace('',buttons_code)
+ # Create the new temp "live" XML
+ myfile = open(temp_path,'w')
+ myfile.write(content)
+ myfile.close()
+
+ d=MyDisclaimer(dialog_new,koding_path,header=header,main_content=main_content)
+ d.doModal()
+ ACTION = d.ACTION
+ del d
+ return ACTION
+
+class MyDisclaimer(xbmcgui.WindowXMLDialog):
+ def __init__(self,*args,**kwargs):
+ self.header=kwargs['header']
+ self.main_content=kwargs['main_content']
+ self.WINDOW=xbmcgui.Window( 10000 )
+ self.WINDOW.setProperty( 'PK_Header' , self.header )
+ self.WINDOW.setProperty( 'PK_Main_Text' , self.main_content )
+ self.ACTION=-1
+ def onClick( self, controlID ):
+ if controlID>=100:
+ self.ACTION=(controlID-100)
+ self.close()
+ elif controlID==12:
+ self.close()
+ def onAction( self, action ):
+ if action in [ 5, 6, 7, 9, 10, 92, 117 ] or action.getButtonCode() in [275,257,261]:
+ self.close()
+#----------------------------------------------------------------
+# TUTORIAL #
+def Keyboard(heading='',default='',hidden=False,return_false=False,autoclose=False,kb_type='alphanum'):
+ """
+Show an on-screen keyboard and return the string
+
+CODE: koding.Keyboard([default, heading, hidden, return_false, autoclose, kb_type])
+
+AVAILABLE PARAMS:
+
+ heading - Optionally enter a heading for the text box.
+
+ default - This is optional, if set this will act as the default text shown in the text box
+
+ hidden - Boolean, if set to True the text will appear as hidden (starred out)
+
+ return_false - By default this is set to False and when escaping out of the keyboard
+ the default text is returned (or an empty string if not set). If set to True then
+ you'll receive a return of False.
+
+ autoclose - By default this is set to False but if you want the keyboard to auto-close
+ after a period of time you can send through an integer. The value sent through needs to
+ be milliseconds, so for example if you want it to close after 3 seconds you'd send through
+ 3000. The autoclose function only works with standard alphanumeric keyboard types.
+
+ kb_type - This is the type of keyboard you want to show, by default it's set to alphanum.
+ A list of available values are listed below:
+
+ 'alphanum' - A standard on-screen keyboard containing alphanumeric characters.
+ 'numeric' - An on-screen numerical pad.
+ 'date' - An on-screen numerical pad formatted only for a date.
+ 'time' - An on-screen numerical pad formatted only for a time.
+ 'ipaddress' - An on-screen numerical pad formatted only for an IP Address.
+ 'password' - A standard keyboard but returns value as md5 hash. When typing
+ the text is starred out, once you've entered the password you'll get another
+ keyboard pop up asking you to verify. If the 2 match then your md5 has is returned.
+
+
+EXAMPLE CODE:
+mytext = koding.Keyboard(heading='Type in the text you want returned',default='test text')
+dialog.ok('TEXT RETURNED','You typed in:', '', '[COLOR=dodgerblue]%s[/COLOR]'%mytext)
+dialog.ok('AUTOCLOSE ENABLED','This following example we\'ve set the autoclose to 3000. That\'s milliseconds which converts to 3 seconds.')
+mytext = koding.Keyboard(heading='Type in the text you want returned',default='this will close in 3s',autoclose=3000)
+dialog.ok('TEXT RETURNED','You typed in:', '', '[COLOR=dodgerblue]%s[/COLOR]'%mytext)
+mytext = koding.Keyboard(heading='Enter a number',kb_type='numeric')
+dialog.ok('NUMBER RETURNED','You typed in:', '', '[COLOR=dodgerblue]%s[/COLOR]'%mytext)
+dialog.ok('RETURN FALSE ENABLED','All of the following examples have "return_false" enabled. This means if you escape out of the keyboard the return will be False.')
+mytext = koding.Keyboard(heading='Enter a date',return_false=True,kb_type='date')
+dialog.ok('DATE RETURNED','You typed in:', '', '[COLOR=dodgerblue]%s[/COLOR]'%mytext)
+mytext = koding.Keyboard(heading='Enter a time',return_false=True,kb_type='time')
+dialog.ok('TIME RETURNED','You typed in:', '', '[COLOR=dodgerblue]%s[/COLOR]'%mytext)
+mytext = koding.Keyboard(heading='IP Address',return_false=True,kb_type='ipaddress',autoclose=5)
+dialog.ok('IP RETURNED','You typed in:', '', '[COLOR=dodgerblue]%s[/COLOR]'%mytext)
+mytext = koding.Keyboard(heading='Password',kb_type='password')
+dialog.ok('MD5 RETURN','The md5 for this password is:', '', '[COLOR=dodgerblue]%s[/COLOR]'%mytext)
+~"""
+ from vartools import Decode_String
+ kb_type = eval( 'xbmcgui.INPUT_%s'%kb_type.upper() )
+ if hidden:
+ hidden = eval( 'xbmcgui.%s_HIDE_INPUT'%kb_type.upper() )
+ keyboard = dialog.input(heading,default,kb_type,hidden,autoclose)
+
+ if keyboard != '':
+ return keyboard
+
+ elif not return_false:
+ return Decode_String(default)
+
+ else:
+ return False
+#----------------------------------------------------------------
+# TUTORIAL #
+def Notify(title, message, duration=2000, icon='special://home/addons/script.module.python.koding.aio/resources/update.png'):
+ """
+Show a short notification for x amount of seconds
+
+CODE: koding.Notify(title, message, [duration, icon])
+
+AVAILABLE PARAMS:
+
+ (*) title - A short title to show on top line of notification
+
+ (*) message - A short message to show on the bottom line of notification
+
+ duration - An integer in milliseconds, the default to show the notification for is 2000
+
+ icon - The icon to show in notification bar, default is the update icon from this module.
+
+EXAMPLE CODE:
+koding.Notify(title='TEST NOTIFICATION', message='This is a quick 5 second test', duration=5000)
+~"""
+ xbmc.executebuiltin('XBMC.Notification(%s, %s, %s, %s)' % (title , message , duration, icon))
+#----------------------------------------------------------------
+# TUTORIAL #
+def OK_Dialog(title,message):
+ """
+This will bring up a short text message in a dialog.ok window.
+
+CODE: OK_Dialog(title,message)
+
+AVAILABLE PARAMS:
+
+ (*) title - This is title which appears in the header of the window.
+
+ (*) message - This is the main text you want to appear.
+
+EXAMPLE CODE:
+koding.OK_Dialog(title='TEST DIALOG',message='This is a test dialog ok box. Click OK to quit.')
+~"""
+ dialog.ok(title,message)
+#----------------------------------------------------------------
+# TUTORIAL #
+def Select_Dialog(title,options,key=True):
+ """
+This will bring up a selection of options to choose from. The options are
+sent through as a list and only one can be selected - this is not a multi-select dialog.
+
+CODE: Select_Dialog(title,options,[key])
+
+AVAILABLE PARAMS:
+
+ (*) title - This is title which appears in the header of the window.
+
+ (*) options - This is a list of the options you want the user to be able to choose from.
+
+ key - By default this is set to True so you'll get a return of the item number. For example
+ if the user picks "option 2" and that is the second item in the list you'll receive a return of
+ 1 (0 would be the first item in list and 1 is the second). If set to False you'll recieve a return
+ of the actual string associated with that key, in this example the return would be "option 2".
+
+EXAMPLE CODE:
+my_options = ['Option 1','Option 2','Option 3','Option 4','Option 5']
+mychoice = koding.Select_Dialog(title='TEST DIALOG',options=my_options,key=False)
+koding.OK_Dialog(title='SELECTED ITEM',message='You selected: [COLOR=dodgerblue]%s[/COLOR]\nNow let\'s try again - this time we will return a key...'%mychoice)
+mychoice = koding.Select_Dialog(title='TEST DIALOG',options=my_options,key=True)
+koding.OK_Dialog(title='SELECTED ITEM',message='The item you selected was position number [COLOR=dodgerblue]%s[/COLOR] in the list'%mychoice)
+~"""
+ mychoice = dialog.select(title,options)
+ if key:
+ return mychoice
+ else:
+ return options[mychoice]
+#----------------------------------------------------------------
+# TUTORIAL #
+def Show_Busy(status=True, sleep=0):
+ """
+This will show/hide a "working" symbol.
+
+CODE: Show_Busy([status, sleep])
+
+AVAILABLE PARAMS:
+
+ status - This optional, by default it's True which means the "working"
+ symbol appears. False will disable.
+
+ sleep - If set the busy symbol will appear for amount of
+ milliseconds and then disappear.
+
+EXAMPLE CODE:
+dialog.ok('BUSY SYMBOL','Press OK to show a busy dialog which restricts any user interaction. We have added a sleep of 5 seconds at which point it will disable.')
+koding.Show_Busy(sleep=5000)
+dialog.ok('BUSY SYMBOL','We will now do the same but with slightly different code')
+koding.Show_Busy(status=True)
+xbmc.sleep(5000)
+koding.Show_Busy(status=False)
+~"""
+ if status:
+ xbmc.executebuiltin("ActivateWindow(busydialog)")
+ if sleep:
+ xbmc.sleep(sleep)
+ xbmc.executebuiltin("Dialog.Close(busydialog)")
+ else:
+ xbmc.executebuiltin("Dialog.Close(busydialog)")
+#----------------------------------------------------------------
+# TUTORIAL #
+def Text_Box(header, message):
+ """
+This will allow you to open a blank window and fill it with some text.
+
+CODE: koding.Text_Box(header, message)
+
+AVAILABLE PARAMS:
+
+ (*) header - As the name suggests this is a string to be used for the header/title of the window
+
+ (*) message - Yes you've probably already gussed it, this is the main message text
+
+
+EXAMPLE CODE:
+koding.Text_Box('TEST HEADER','Just some random text... Use kodi tags for new lines, colours etc.')
+~"""
+ xbmc.executebuiltin("ActivateWindow(10147)")
+ controller = xbmcgui.Window(10147)
+ xbmc.sleep(500)
+ controller.getControl(1).setLabel(header)
+ controller.getControl(5).setText(message)
+#----------------------------------------------------------------
+# TUTORIAL #
+def Reset_Percent(property='update_percent_',window_id=10000):
+ """
+If using the Update_Progress function for setting percentages in skinning then this
+will allow you to reset all the percent properties (1-100)
+
+CODE: Reset_Percent([property,window_id])
+
+AVAILABLE PARAMS:
+
+ property - the property name you want reset, this will reset all properties starting
+ with this string from 1-100. For example if you use the default 'update_percent_' this
+ will loop through and reset update_percent_1, update_percent_2 etc. all the way through
+ to update_percent_100.
+
+ window_id - By default this is set to 10000 but you can send any id through you want.
+
+ kwargs - Send through any other params and the respective property will be set.colours etc.')
+~"""
+ counter = 0
+ while counter <= 100:
+ xbmcgui.Window(10000).clearProperty('update_percent_%s'%counter)
+ counter +=1
+#----------------------------------------------------------------
+# TUTORIAL #
+def Update_Progress(total_items,current_item,**kwargs):
+ """
+This function is designed for skinners but can be used for general Python too. It will
+work out the current percentage of items that have been processed and update the
+"update_percent" property accordingly (1-100). You can also send through any properties
+you want updated and it will loop through updating them with the relevant values.
+
+To send through properties just send through the property name as the param and assign to a value.
+Example: Update_Progress( total_items=100,current_item=56, {"myproperty1":"test1","myproperty2":"test2"} )
+
+
+CODE: Update_Progress(total_items,current_item,[kwargs])
+
+AVAILABLE PARAMS:
+
+ (*) total_items - Total amount of items in your list you're processing
+
+ (*) current_item - Current item number that's been processed.
+
+ kwargs - Send through any other params and the respective property will be set.colours etc.
+~"""
+ Reset_Percent()
+ for item in kwargs:
+ if item.endswith('color'):
+ value = '0xFF'+kwargs[item]
+ else:
+ value = kwargs[item]
+ if value == 'false' or value == '' and not item.endswith('color'):
+ xbmcgui.Window(10000).clearProperty(item)
+ elif value:
+ xbmcgui.Window(10000).setProperty(item, value)
+ percent = 100*(current_item/(total_items*1.0))
+ newpercent=int(percent)
+ if (newpercent % 1 == 0) and (newpercent <=100):
+ xbmcgui.Window(10000).setProperty('update_percent',str(newpercent))
+ xbmcgui.Window(10000).setProperty('update_percent_%s'%newpercent,'true')
+ if newpercent == 100:
+ xbmc.executebuiltin('Action(firstpage)')
+#-----------------------------------------------------------------------------
+# TUTORIAL #
+def Update_Screen(disable_quit=False, auto_close=True):
+ """
+This will create a full screen overlay showing progress of updates. You'll need to
+use this in conjunction with the Update_Progress function.
+
+CODE: Update_Screen([disable_quit, auto_close))
+
+AVAILABLE PARAMS:
+
+ disable_quit - By default this is set to False and pressing the parent directory
+ button (generally esc) will allow you to close the window. Setting this to True
+ will mean it's not possible to close the window manually.
+
+ auto_close - By default this is set to true and when the percentage hits 100
+ the window will close. If you intend on then sending through some more commands
+ you might want to consider leaving this window open in which case you'd set this
+ to false. Bare in mind if you go this route the window will stay active until
+ you send through the kill command which is: xbmc.executebuiltin('Action(firstpage)')
+
+EXAMPLE CODE:
+mykwargs = {
+ "update_header" : "Downloading latest updates",\
+ "update_main_text" : "Your device is now downloading all the latest updates.\nThis shouldn\'t take too long, "\
+ "depending on your internet speed this could take anything from 2 to 10 minutes.\n\n"\
+ "Once downloaded the system will start to install the updates.",\
+ "update_bar_color" : "4e91cf",\
+ "update_icon" : "special://home/addons/script.module.python.koding.aio/resources/skins/Default/media/update.png",\
+ "update_spinner" : "true"}
+Update_Screen()
+counter = 1
+while counter <= 60:
+ xbmc.sleep(300)
+ Update_Progress(total_items=60,current_item=counter,**mykwargs)
+ if counter == 30:
+ mykwargs = {
+ "update_header" : "Halfway there!",\
+ "update_main_text" : "We just updated the properties to show how you can change things on the fly "\
+ "simply by sending through some different properties. Both the icon and the "\
+ "background images you see here are being pulled from online.",\
+ "update_header_color" : "4e91cf",\
+ "update_percent_color" : "4e91cf",\
+ "update_bar_color" : "4e91cf",\
+ "update_background" : "http://www.planwallpaper.com/static/images/518164-backgrounds.jpg",\
+ "update_icon" : "http://totalrevolution.tv/img/tr_small_black_bg.jpg",\
+ "update_spinner" : "false"}
+ counter += 1
+~"""
+ import threading
+ update_screen_thread = threading.Thread(target=Show_Update_Screen, args=[disable_quit, auto_close])
+ update_screen_thread.start()
+ xbmc.sleep(2000)
+
+def Show_Update_Screen(disable_quit=False,auto_close=True):
+ xbmcgui.Window(10000).clearProperty('update_icon')
+ xbmcgui.Window(10000).clearProperty('update_percent')
+ xbmcgui.Window(10000).clearProperty('update_spinner')
+ xbmcgui.Window(10000).clearProperty('update_header')
+ xbmcgui.Window(10000).clearProperty('update_main_text')
+ xbmcgui.Window(10000).setProperty('update_background','whitebg.jpg')
+ xbmcgui.Window(10000).setProperty('update_percent_color','0xFF000000')
+ xbmcgui.Window(10000).setProperty('update_bar_color','0xFF000000')
+ xbmcgui.Window(10000).setProperty('update_main_color','0xFF000000')
+ xbmcgui.Window(10000).setProperty('update_header_color','0xFF000000')
+# Set a property so we can determine if update screen is active
+ xbmcgui.Window(10000).setProperty('update_screen','active')
+ d=MyUpdateScreen('Loading.xml',koding_path,disable_quit=disable_quit,auto_close=auto_close)
+ d.doModal()
+ del d
+ xbmcgui.Window(10000).clearProperty('update_screen')
+
+class MyUpdateScreen(xbmcgui.WindowXMLDialog):
+ def __init__(self,*args,**kwargs):
+ self.disable_quit=kwargs['disable_quit']
+ self.auto_close=kwargs['auto_close']
+ self.WINDOW=xbmcgui.Window( 10000 )
+ def onAction( self, action ):
+ if action in [10,7]:
+ if self.disable_quit:
+ xbmc.log("ESC and HOME Disabled",2)
+ else:
+ self.close()
+ if action==159 and self.auto_close:
+ self.close()
+#----------------------------------------------------------------
+# TUTORIAL #
+def YesNo_Dialog(title,message,yes=None,no=None):
+ """
+This will bring up a short text message in a dialog.yesno window. This will
+return True or False
+
+CODE: YesNo_Dialog(title,message,[yeslabel,nolabel])
+
+AVAILABLE PARAMS:
+
+ (*) title - This is title which appears in the header of the window.
+
+ (*) message - This is the main text you want to appear.
+
+ yes - Optionally change the default "YES" to a custom string
+
+ no - Optionally change the default "NO" to a custom string
+
+EXAMPLE CODE:
+mychoice = koding.YesNo_Dialog(title='TEST DIALOG',message='This is a yes/no dialog with custom labels.\nDo you want to see an example of a standard yes/no.',yes='Go on then',no='Nooooo!')
+if mychoice:
+ koding.YesNo_Dialog(title='STANDARD DIALOG',message='This is an example of a standard one without sending custom yes/no params through.')
+~"""
+ choice = dialog.yesno(title,message,yeslabel=yes,nolabel=no)
+ return choice
+#----------------------------------------------------------------
diff --git a/script.module.python.koding.aio/lib/koding/router.py b/script.module.python.koding.aio/lib/koding/router.py
new file mode 100644
index 0000000..442d3e2
--- /dev/null
+++ b/script.module.python.koding.aio/lib/koding/router.py
@@ -0,0 +1,140 @@
+# -*- coding: utf-8 -*-
+
+# script.module.python.koding.aio
+# Python Koding AIO (c) by TOTALREVOLUTION LTD (support@trmc.freshdesk.com)
+
+# Python Koding AIO is licensed under a
+# Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.
+
+# You should have received a copy of the license along with this
+# work. If not, see http://creativecommons.org/licenses/by-nc-nd/4.0.
+
+# Please make sure you've read and understood the license, this code can NOT be used commercially
+# and it can NOT be modified and redistributed. If you're found to be in breach of this license
+# then any affected add-ons will be blacklisted and will not be able to work on the same system
+# as any other add-ons which use this code. Thank you for your cooperation.
+
+import directory
+import tutorials
+import video
+import xbmc
+import xbmcgui
+
+dialog = xbmcgui.Dialog()
+HOME = xbmc.translatePath('special://home')
+master_modes = {
+# Required for certain koding functions to work
+ "play_video": {'function': video.Play_Video, 'args': ["url"]},
+ "show_tutorial": {'function': tutorials.Show_Tutorial, 'args': ["url"]},
+ "tutorials": {'function': tutorials.Grab_Tutorials, 'args': []},
+}
+#----------------------------------------------------------------
+# TUTORIAL #
+def route(mode, args=[]):
+ """
+Use this to set a function in your master_modes dictionary.
+This is to be used for Add_Dir() items, see the example below.
+
+CODE: route(mode, [args])
+
+AVAILABLE PARAMS:
+
+ (*) mode - This must be set, it needs to be a custom string.
+ This is the string you'd use in your Add_Dir command to call
+ the function.
+
+ args - This is optional but if the function you're calling
+ requires extra paramaters you can add them in here. Just add them
+ as a list of strings. Example: args=['name','artwork','description']
+
+BELOW IS AN EXAMPLE OF HOW TO CALL THE CODE IN YOUR MAIN ADDON PY FILE:
+
+@route(mode="test", args=["name","description"])
+def Test_Function(name,description):
+ dialog.ok('This is a test function', name, description')
+
+koding.Add_Dir(name='Test Dialog', url={"name":"My Test Function", "description" : "Its ALIVE!!!"}, mode='test')
+koding.run()
+~"""
+ if mode not in master_modes:
+
+ def _route(function):
+ master_modes[mode] = {
+ 'function': function,
+ 'args': args
+ }
+ return function
+ return _route
+ else:
+ dialog.ok('DUPLICATE MODE',
+ 'The following mode already exists:',
+ '[COLOR=dodgerblue]%s[/COLOR]' % mode)
+#----------------------------------------------------------------
+# TUTORIAL #
+def Run(default="main"):
+ """
+This needs to be called at the bottom of your code in the main default.py
+
+This checks the modes called in Add_Dir and does all the clever stuff
+in the background which assigns those modes to functions and sends
+through the various params.
+
+Just after this command you need to make
+sure you set the endOfDirectory (as shown below).
+
+
+CODE: run([default])
+xbmcplugin.endOfDirectory(int(sys.argv[1]))
+
+AVAILABLE PARAMS:
+
+ default - This is the default mode you want the add-on to open
+ into, it's set as "main" by default. If you have a different mode
+ name you want to open into just edit accordingly.
+~"""
+ import urllib
+ import urlparse
+ import sys
+ from __init__ import DEBUG
+ from guitools import Text_Box
+ from systemtools import Last_Error
+
+ params = dict(urlparse.parse_qsl(sys.argv[2].replace('?', '')))
+ mode = params.get("mode", default)
+ if mode in master_modes:
+ evaled_args = []
+
+ # Grab the url and split up into a dictionary of args
+ try:
+ main_url = params["url"]
+ # Convert back from special to physical path - useful for community shares
+ if urllib.unquote_plus("special://home/") in main_url:
+ main_url = main_url.replace('special://home/',HOME)
+ except:
+ main_url = ''
+ try:
+ my_args = eval(main_url)
+ except:
+ my_args = {"url":main_url}
+
+ for arg in master_modes[mode]["args"]:
+ try:
+ evaled_args.append(my_args[arg])
+ except:
+ if DEBUG == 'true':
+ dialog.ok('ERROR IN CODE','Your Add_Dir function is expecting the [COLOR=gold][B]%s[/B][/COLOR] paramater to be sent through. This does not exist, please check your Add_Dir function.'%arg)
+ xbmc.log(Last_Error(),2)
+ return
+ try:
+ master_modes[mode]["function"](*evaled_args)
+ except:
+ if DEBUG == 'true':
+ Text_Box('ERROR IN CODE', Last_Error())
+ xbmc.log(Last_Error(),2)
+ else:
+ pass
+ else:
+ dialog.ok('MODE DOES NOT EXIST',
+ 'The following mode does not exist in your\
+ master_modes dictionary:',
+ '[COLOR=dodgerblue]%s[/COLOR]' % mode)
diff --git a/script.module.python.koding.aio/lib/koding/systemtools.py b/script.module.python.koding.aio/lib/koding/systemtools.py
new file mode 100644
index 0000000..78abd8f
--- /dev/null
+++ b/script.module.python.koding.aio/lib/koding/systemtools.py
@@ -0,0 +1,908 @@
+# -*- coding: utf-8 -*-
+
+# script.module.python.koding.aio
+# Python Koding AIO (c) by TOTALREVOLUTION LTD (support@trmc.freshdesk.com)
+
+# Python Koding AIO is licensed under a
+# Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.
+
+# You should have received a copy of the license along with this
+# work. If not, see http://creativecommons.org/licenses/by-nc-nd/4.0.
+
+# Please make sure you've read and understood the license, this code can NOT be used commercially
+# and it can NOT be modified and redistributed. If you're found to be in breach of this license
+# then any affected add-ons will be blacklisted and will not be able to work on the same system
+# as any other add-ons which use this code. Thank you for your cooperation.
+
+import datetime
+import os
+import sys
+import shutil
+import xbmc
+import xbmcaddon
+import xbmcgui
+import xbmcvfs
+try:
+ from vartools import Data_Type
+except:
+ pass
+#----------------------------------------------------------------
+# TUTORIAL #
+def Cleanup_Textures(frequency=14,use_count=10):
+ """
+This will check for any cached artwork and wipe if it's not been accessed more than 10 times in the past x amount of days.
+
+CODE: Cleanup_Textures([frequency, use_count])
+
+AVAILABLE PARAMS:
+
+ frequency - This is an optional integer, be default it checks for any
+ images not accessed in 14 days but you can use any amount of days here.
+
+ use_count - This is an optional integer, be default it checks for any
+ images not accessed more than 10 times. If you want to be more ruthless
+ and remove all images not accessed in the past x amount of days then set this very high.
+
+EXAMPLE CODE:
+dialog.ok('Clean Textures','We are going to clear any old cached images not accessed at least 10 times in the past 5 days')
+koding.Cleanup_Textures(frequency=5)
+~"""
+ try: from sqlite3 import dbapi2 as database
+ except: from pysqlite2 import dbapi2 as database
+ from filetools import DB_Path_Check
+
+ db = DB_Path_Check('Textures')
+ xbmc.log('### DB_PATH: %s' % db)
+ conn = database.connect(db, timeout = 10, detect_types=database.PARSE_DECLTYPES, check_same_thread = False)
+ conn.row_factory = database.Row
+ c = conn.cursor()
+
+ # Set paramaters to check in db, cull = the datetime (we've set it to 14 days) and useCount is the amount of times the file has been accessed
+ cull = datetime.datetime.today() - datetime.timedelta(days = frequency)
+
+ # Create an array to store paths for images and ids for database
+ ids = []
+ images = []
+
+ c.execute("SELECT idtexture FROM sizes WHERE usecount < ? AND lastusetime < ?", (use_count, str(cull)))
+
+ for row in c:
+ ids.append(row["idtexture"])
+
+ for id in ids:
+ c.execute("SELECT cachedurl FROM texture WHERE id = ?", (id,))
+ for row in c:
+ images.append(row["cachedurl"])
+
+
+# Clean up database
+ for id in ids:
+ c.execute("DELETE FROM sizes WHERE idtexture = ?", (id,))
+ c.execute("DELETE FROM texture WHERE id = ?", (id,))
+
+ c.execute("VACUUM")
+ conn.commit()
+ c.close()
+
+ xbmc.log("### Automatic Cache Removal: %d Old Textures removed" % len(images))
+
+# Delete files
+ thumbfolder = 'special://home/userdata/Thumbnails'
+ for image in images:
+ path = os.path.join(thumbfolder, image)
+ try:
+ xbmcvfs.delete(path)
+ except:
+ xbmc.log(Last_Error())
+#----------------------------------------------------------------
+# TUTORIAL #
+def Current_Profile():
+ """
+This will return the current running profile, it's only one line of code but this is for my benefit as much as
+anyone else's. I use this function quite a lot and keep forgetting the code so figured it would be easier to
+just write a simple function for it :)
+
+CODE: Current_Profile()
+
+EXAMPLE CODE:
+profile = koding.Current_Profile()
+dialog.ok('CURRENT PROFILE','Your current running profile is:','[COLOR=dodgerblue]%s[/COLOR]' % profile)
+~"""
+
+ return xbmc.getInfoLabel('System.ProfileName')
+#----------------------------------------------------------------
+# TUTORIAL #
+def Force_Close():
+ """
+Force close Kodi, should only be used in extreme circumstances.
+
+CODE: Force_Close()
+
+EXAMPLE CODE:
+if dialog.yesno('FORCE CLOSE','Are you sure you want to forcably close Kodi? This could potentially cause corruption if system tasks are taking place in background.'):
+ koding.Force_Close()
+~"""
+ os._exit(1)
+#----------------------------------------------------------------
+# TUTORIAL #
+def Get_ID(setid=False):
+ """
+A simple function to set user id and group id to the current running App
+for system commands. For example if you're using the subprocess command
+you could send through the preexec_fn paramater as koding.Get_ID(setid=True).
+This function will also return the uid and gid in form of a dictionary.
+
+CODE: Get_ID([setid])
+
+AVAILABLE PARAMS:
+
+ (*) setid - By default this is set to False but if set to True it
+ will set the ids (to be used for subprocess commands)
+
+EXAMPLE CODE:
+ids = Get_ID(setid=False)
+if ids:
+ uid = ids['uid']
+ gid = ids['gid']
+ dialog.ok('USER & GROUP ID','User ID: %s'%uid, 'Group ID: %s'%gid)
+else:
+ dialog.ok('USER & GROUP ID','This function is not applicable to your system. We\'ve been sent back a return of False to indicate this function does not exist on your os.')
+~"""
+ try:
+ uid = os.getuid()
+ gid = os.getgid()
+ if setid:
+ os.setgid(uid)
+ os.setuid(gid)
+ if not setid:
+ return {"uid":uid,"gid":gid}
+ except:
+ return False
+#----------------------------------------------------------------
+def Get_Mac(protocol = 'eth'):
+ cont = False
+ vpn_check = False
+ counter = 0
+ mac = ''
+ while len(mac)!=17 and counter < 5:
+ if sys.platform == 'win32':
+ for line in os.popen("ipconfig /all"):
+ if protocol == 'wifi':
+ if line.startswith('Wireless LAN adapter Wi'):
+ cont = True
+ if line.lstrip().startswith('Physical Address') and cont:
+ mac = line.split(':')[1].strip().replace('-',':').replace(' ','')
+ break
+ else:
+ if line.lstrip().startswith('Description'):
+ if not 'VPN' in line:
+ vpn_check = True
+ if line.lstrip().startswith('Physical Address') and vpn_check:
+ mac = line.split(':')[1].strip().replace('-',':').replace(' ','')
+ vpn_check = False
+ break
+
+ elif sys.platform == 'darwin':
+ if protocol == 'wifi':
+ for line in os.popen("ifconfig en0 | grep ether"):
+ if line.lstrip().startswith('ether'):
+ mac = line.split('ether')[1].strip().replace('-',':').replace(' ','')
+ break
+ else:
+ for line in os.popen("ifconfig en1 | grep ether"):
+ if line.lstrip().startswith('ether'):
+ mac = line.split('ether')[1].strip().replace('-',':').replace(' ','')
+ break
+
+ elif xbmc.getCondVisibility('System.Platform.Android'):
+ try:
+ if protocol == 'wifi':
+ readfile = open('/sys/class/net/wlan0/address', mode='r')
+ if protocol != 'wifi':
+ readfile = open('/sys/class/net/eth0/address', mode='r')
+ mac = readfile.read()
+ readfile.close()
+ mac = mac.strip()
+ mac = mac.replace(' ','')
+ mac = mac[:17]
+ except:
+ mac = ''
+
+ else:
+ mac = ''
+ if protocol == 'wifi':
+ for line in os.popen("/sbin/ifconfig"):
+ if line.find('wlan0') > -1:
+ mac = line.split()[4].strip()
+ break
+ elif line.startswith('en'):
+ if 'Ethernet'in line and 'HWaddr' in line:
+ mac = line.split('HWaddr')[1].strip()
+ break
+ else:
+ for line in os.popen("/sbin/ifconfig"):
+ if line.find('eth0') > -1:
+ mac = line.split()[4].strip()
+ break
+ elif line.startswith('wl'):
+ if 'Ethernet'in line and 'HWaddr' in line:
+ mac = line.split('HWaddr')[1].strip()
+ break
+ counter += 1
+ xbmc.log('attempt no.%s %s mac: %s'%(counter,protocol,mac),2)
+ if len(mac) != 17:
+ counter = 0
+ while counter < 5 and len(mac) != 17:
+ mac = xbmc.getInfoLabel('Network.MacAddress')
+ xbmc.log('MAC (backup): %s'%mac,2)
+ xbmc.sleep(100)
+ counter += 1
+ if len(mac) != 17:
+ return 'Unknown'
+ else:
+ return mac
+#-----------------------------------------------------------------------------
+# TUTORIAL #
+def Grab_Log(log_type = 'std', formatting = 'original', sort_order = 'reverse'):
+ """
+This will grab the log file contents, works on all systems even forked kodi.
+
+CODE: Grab_Log([log_type, formatting, sort_order])
+
+AVAILABLE PARAMS:
+
+ log_type - This is optional, if not set you will get the current log.
+ If you would prefer the old log set this to 'old'
+
+ formatting - By default you'll just get a default log but you can set
+ this to 'warnings', 'notices', 'errors' to filter by only those error types.
+ Notices will return in blue, warnings in gold and errors in red.
+ You can use as many of the formatting values as you want, just separate by an
+ underscore such as 'warnings_errors'. If using anything other than the
+ default in here your log will returned in order of newest log activity first
+ (reversed order). You can also use 'clean' as an option and that will just
+ return the full log but with clean text formatting and in reverse order.
+
+ sort_order - This will only work if you've sent through an argument other
+ than 'original' for the formatting. By default the log will be shown in
+ 'reverse' order but you can set this to 'original' if you prefer ascending
+ timestamp ordering like a normal log.
+
+EXAMPLE CODE:
+my_log = koding.Grab_Log()
+dialog.ok('KODI LOG LOOP','Press OK to see various logging options, every 5 seconds it will show a new log style.')
+koding.Text_Box('CURRENT LOG FILE (ORIGINAL)',my_log)
+xbmc.sleep(5000)
+my_log = koding.Grab_Log(formatting='clean', sort_order='reverse')
+koding.Text_Box('CURRENT LOG FILE (clean in reverse order)',my_log)
+xbmc.sleep(5000)
+my_log = koding.Grab_Log(formatting='errors_warnings', sort_order='reverse')
+koding.Text_Box('CURRENT LOG FILE (erros & warnings only - reversed)',my_log)
+xbmc.sleep(5000)
+old_log = koding.Grab_Log(log_type='old')
+koding.Text_Box('OLD LOG FILE',old_log)
+~"""
+ from filetools import Physical_Path, Text_File
+ log_path = Physical_Path('special://logpath/')
+ logfilepath = os.listdir(log_path)
+ finalfile = 0
+ for item in logfilepath:
+ cont = False
+ if item.endswith('.log') and not item.endswith('.old.log') and log_type == 'std':
+ mylog = os.path.join(log_path,item)
+ cont = True
+ elif item.endswith('.old.log') and log_type == 'old':
+ mylog = os.path.join(log_path,item)
+ cont = True
+ if cont:
+ lastmodified = xbmcvfs.Stat(mylog).st_mtime()
+ if lastmodified>finalfile:
+ finalfile = lastmodified
+ logfile = mylog
+
+ logtext = Text_File(logfile, 'r')
+
+ if formatting != 'original':
+ logtext_final = ''
+
+ with open(logfile) as f:
+ log_array = f.readlines()
+ log_array = [line.strip() for line in log_array]
+
+ if sort_order == 'reverse':
+ log_array = reversed(log_array)
+
+ for line in log_array:
+ if ('warnings' in formatting or 'clean' in formatting) and 'WARNING:' in line:
+ logtext_final += line.replace('WARNING:', '[COLOR=gold]WARNING:[/COLOR]')+'\n'
+ if ('errors' in formatting or 'clean' in formatting) and 'ERROR:' in line:
+ logtext_final += line.replace('ERROR:', '[COLOR=red]ERROR:[/COLOR]')+'\n'
+ if ('notices' in formatting or 'clean' in formatting) and 'NOTICE:' in line:
+ logtext_final += line.replace('NOTICE:', '[COLOR=dodgerblue]NOTICE:[/COLOR]')+'\n'
+
+ logtext = logtext_final
+
+ return logtext
+#----------------------------------------------------------------
+# TUTORIAL #
+def Last_Error():
+ """
+Return details of the last error produced, perfect for try/except statements
+
+CODE: Last_Error()
+
+EXAMPLE CODE:
+try:
+ xbmc.log(this_should_error)
+except:
+ koding.Text_Box('ERROR MESSAGE',Last_Error())
+~"""
+
+ import traceback
+ error = traceback.format_exc()
+ return error
+#----------------------------------------------------------------
+# TUTORIAL #
+def Network_Settings():
+ """
+Attempt to open the WiFi/network settings for the current running operating system.
+
+I have no access to any iOS based systems so if anybody wants to add support for
+that and you know the working code please contact me at info@totalrevolution.tv
+The Linux one is also currently untested and of course there are many different
+distros so if you know of any improved code please do pass on. Thank you.
+
+CODE: Network_Settings()
+
+EXAMPLE CODE:
+koding.Network_Settings()
+~"""
+ content = Grab_Log()
+ if xbmc.getCondVisibility('System.Platform.Android'):
+ xbmc.executebuiltin('StartAndroidActivity(,android.settings.WIFI_SETTINGS)')
+
+ elif xbmc.getCondVisibility('System.Platform.OSX'):
+ os.system('open /System/Library/PreferencePanes/Network.prefPane/')
+
+ elif xbmc.getCondVisibility('System.Platform.Windows'):
+ os.system('ncpa.cpl')
+
+ elif 'Running on OpenELEC' in content or 'Running on LibreELEC' in content:
+
+ if xbmc.getCondVisibility("System.HasAddon(service.openelec.settings)") or xbmc.getCondVisibility("System.HasAddon(service.libreelec.settings)"):
+ if xbmc.getCondVisibility("System.HasAddon(service.openelec.settings)"):
+ xbmcaddon.Addon(id='service.openelec.settings').getAddonInfo('name')
+ xbmc.executebuiltin('RunAddon(service.openelec.settings)')
+ elif xbmc.getCondVisibility("System.HasAddon(service.libreelec.settings)"):
+ xbmcaddon.Addon(id='service.libreelec.settings').getAddonInfo('name')
+ xbmc.executebuiltin('RunAddon(service.libreelec.settings)')
+ xbmc.sleep(1500)
+ xbmc.executebuiltin('Control.SetFocus(1000,2)')
+ xbmc.sleep(500)
+ xbmc.executebuiltin('Control.SetFocus(1200,0)')
+
+ elif xbmc.getCondVisibility('System.Platform.Linux'):
+ os.system('nm-connection-editor')
+#----------------------------------------------------------------
+# TUTORIAL #
+def Python_Version():
+ """
+Return the current version of Python as a string. Very useful if you need
+to find out whether or not to return https links (Python 2.6 is not SSL friendly).
+
+CODE: Python_Version()
+
+EXAMPLE CODE:
+py_version = koding.Python_Version()
+dialog.ok('PYTHON VERSION','You are currently running:','Python v.%s'%py_version)
+~"""
+ py_version = '%s.%s'%(sys.version_info[0],sys.version_info[1])
+ return py_version
+#----------------------------------------------------------------
+# TUTORIAL #
+def Refresh(r_mode=['addons', 'repos'], profile_name='default'):
+ """
+Refresh a number of items in kodi, choose the order they are
+executed in by putting first in your r_mode. For example if you
+want to refresh addons then repo and then the profile you would
+send through a list in the order you want them to be executed.
+
+CODE: Refresh(r_mode, [profile])
+
+AVAILABLE PARAMS:
+
+ r_mode - This is the types of "refresh you want to perform",
+ you can send through just one item or a list of items from the
+ list below. If you want a sleep between each action just put a
+ '~' followed by amount of milliseconds after the r_mode. For example
+ r_mode=['addons~3000', 'repos~2000', 'profile']. This would refresh
+ the addons, wait 2 seconds then refresh the repos, wait 3 seconds then
+ reload the profile. The default is set to do a force refresh on
+ addons and repositories - ['addons', 'repos'].
+
+ 'addons': This will perform the 'UpdateLocalAddons' command.
+
+ 'container': This will refresh the contents of the page.
+
+ 'profile': This will refresh the current profile or if
+ the profile_name param is set it will load that.
+
+ 'repos': This will perform the 'UpdateAddonRepos' command.
+
+ 'skin': This will perform the 'ReloadSkin' command.
+
+ profile_name - If you're sending through the option to refresh
+ a profile it will reload the current running profile by default
+ but you can pass through a profile name here.
+
+EXAMPLE CODE:
+dialog.ok('RELOAD SKIN','We will now attempt to update the addons, pause 3s, update repos and pause 2s then reload the skin. Press OK to continue.')
+koding.Refresh(r_mode=['addons~3000', 'repos~2000', 'skin'])
+xbmc.sleep(2000)
+dialog.ok('COMPLETE','Ok that wasn\'t the best test to perform as you can\'t physically see any visible changes other than the skin refreshing so you\'ll just have to trust us that it worked!')
+~"""
+ from vartools import Data_Type
+ if profile_name == 'default':
+ profile_name = Current_Profile()
+
+ data_type = Data_Type(r_mode)
+ if data_type == 'str':
+ r_mode = [r_mode]
+
+ for item in r_mode:
+ sleeper = 0
+ if '~' in item:
+ item, sleeper = item.split('~')
+ sleeper = int(sleeper)
+ if item =='addons':
+ xbmc.executebuiltin('UpdateLocalAddons')
+ if item =='repos':
+ xbmc.executebuiltin('UpdateAddonRepos')
+ if item =='container':
+ xbmc.executebuiltin('Container.Refresh')
+ if item =='skin':
+ xbmc.executebuiltin('ReloadSkin')
+ if item =='profile':
+ xbmc.executebuiltin('LoadProfile(%s)' % profile_name)
+ if sleeper:
+ xbmc.sleep(sleeper)
+#----------------------------------------------------------------
+# TUTORIAL #
+def Requirements(dependency):
+ """
+Return the min and max versions of built-in kodi dependencies required by
+the running version of Kodi (xbmc.gui, xbmc.python etc.), The return will
+be a dictionary with the keys 'min' and 'max'.
+
+CODE: Requirements(dependency)
+
+AVAILABLE PARAMS:
+
+ (*) dependency - This is the dependency you want to check.
+ You can check any built-in dependency which has backwards-compatibility
+ but the most commonly used are xbmc.gui and xbmc.python.
+
+EXAMPLE CODE:
+xbmc_gui = Requirements('xbmc.gui')
+xbmc_python = Requirements('xbmc.python')
+dialog.ok('DEPENDENCIES','[COLOR=dodgerblue]xbmc.gui[/COLOR] Min: %s Max: %s'%(xbmc_gui['min'],xbmc_gui['max']),'[COLOR=dodgerblue]xbmc.python[/COLOR] Min: %s Max: %s'%(xbmc_python['min'],xbmc_python['max']))
+~"""
+ from filetools import Physical_Path,Text_File
+ from vartools import Find_In_Text
+ kodi_ver = xbmc.getInfoLabel("System.BuildVersion")[:2]
+# Dictionary used for fallback if local file not accessible (AFTV for example)
+ defaults = {'15':{'xbmc.gui':['5.3.0','5.9.0'], 'xbmc.python':['2.1.0','2.20.0']}, '16':{'xbmc.gui':['5.10.0','5.10.0'], 'xbmc.python':['2.1.0','2.24.0']}, '17':{'xbmc.gui':['5.12.0','5.12.0'], 'xbmc.python':['2.1.0','2.25.0']}}
+ root = 'special://xbmc/addons'
+ dep_path = os.path.join(root,dependency,'addon.xml')
+ content = Text_File(dep_path,'r')
+ try:
+ max_ver = Find_In_Text(content=content,start='version="',end='"')[1]
+ min_ver = Find_In_Text(content=content,start='abi="',end='"')[0]
+ except:
+ xbmc.log(repr(defaults[kodi_ver]),2)
+ try:
+ max_ver = defaults[kodi_ver][dependency][1]
+ min_ver = defaults[kodi_ver][dependency][0]
+ except:
+ max_ver = 'unknown'
+ min_ver = 'unknown'
+ xbmc.log('%s min: %s'%(dependency,min_ver),2)
+ xbmc.log('%s max: %s'%(dependency,max_ver),2)
+ return {'min':min_ver,"max":max_ver}
+#----------------------------------------------------------------
+# TUTORIAL #
+def Running_App():
+ """
+Return the Kodi app name you're running, useful for fork compatibility
+
+CODE: Running_App()
+
+EXAMPLE CODE:
+my_kodi = koding.Running_App()
+kodi_ver = xbmc.getInfoLabel("System.BuildVersion")
+dialog.ok('KODI VERSION','You are running:','[COLOR=dodgerblue]%s[/COLOR] - v.%s' % (my_kodi, kodi_ver))
+~"""
+ root_folder = xbmc.translatePath('special://xbmc')
+ xbmc.log(root_folder)
+ if '/cache' in root_folder:
+ root_folder = root_folder.split('/cache')[0]
+ root_folder = root_folder.split('/')
+ if root_folder[len(root_folder)-1] == '':
+ root_folder.pop()
+ finalitem = len(root_folder)-1
+ running = root_folder[finalitem]
+ return running
+#----------------------------------------------------------------
+# TUTORIAL #
+def Set_Setting(setting, setting_type='kodi_setting', value = 'true'):
+ """
+Use this to set built-in kodi settings via JSON or set skin settings.
+
+CODE: Set_Setting(setting, [setting_type, value])
+
+AVAILABLE PARAMS:
+
+ setting_type - The type of setting type you want to change. By default
+ it's set to 'kodi_setting', see below for more info.
+
+ AVAILALE VALUES:
+
+ 'string' : sets a skin string, requires a value.
+
+ 'bool_true' : sets a skin boolean to true, no value required.
+
+ 'bool_false' sets a skin boolean to false, no value required.
+
+ 'kodi_setting' : sets values found in guisettings.xml. Requires
+ a string of 'true' or 'false' for the value paramater.
+
+ 'addon_enable' : enables/disables an addon. Requires a string of
+ 'true' (enable) or 'false' (disable) as the value. You will get a
+ return of True/False on whether successul. Depending on your requirements
+ you may prefer to use the Toggle_Addons function.
+
+ 'json' : WIP - setitng = method, value = params, see documentation on
+ JSON-RPC API here: http://kodi.wiki/view/JSON-RPC_API)
+
+ setting - This is the name of the setting you want to change, it could be a
+ setting from the kodi settings or a skin based setting. If you're wanting
+ to enable/disable an add-on this is set as the add-on id.
+
+ value: This is the value you want to change the setting to. By default this
+ is set to 'true'.
+
+
+EXAMPLE CODE:
+if dialog.yesno('RSS FEEDS','Would you like to enable or disable your RSS feeds?',yeslabel='ENABLE',nolabel='DISABLE'):
+ koding.Set_Setting(setting_type='kodi_setting', setting='lookandfeel.enablerssfeeds', value='true')
+else:
+ koding.Set_Setting(setting_type='kodi_setting', setting='lookandfeel.enablerssfeeds', value='false')
+~"""
+ try: import simplejson as json
+ except: import json
+
+ try:
+
+# If the setting_type is kodi_setting we run the command to set the relevant values in guisettings.xml
+ if setting_type == 'kodi_setting':
+ setting = '"%s"' % setting
+ value = '"%s"' % value
+
+ query = '{"jsonrpc":"2.0", "method":"Settings.SetSettingValue","params":{"setting":%s,"value":%s}, "id":1}' % (setting, value)
+ response = xbmc.executeJSONRPC(query)
+
+ if 'error' in str(response):
+ query = '{"jsonrpc":"2.0", "method":"Settings.SetSettingValue","params":{"setting":%s,"value":%s}, "id":1}' % (setting, value.replace('"',''))
+ response = xbmc.executeJSONRPC(query)
+ if 'error' in str(response):
+ xbmc.log('### Error With Setting: %s' % response, 2)
+ return False
+ else:
+ return True
+ else:
+ return True
+
+# Set a skin string to
+ elif setting_type == 'string':
+ xbmc.executebuiltin('Skin.SetString(%s,%s)' % (setting, value))
+
+# Set a skin setting to true
+ elif setting_type == 'bool_true':
+ xbmc.executebuiltin('Skin.SetBool(%s)' % setting)
+
+# Set a skin setting to false
+ elif setting_type == 'bool_false':
+ xbmc.executebuiltin('Skin.Reset(%s)' % setting)
+
+# If we're enabling/disabling an addon
+ elif setting_type == 'addon_enable':
+ if setting != '':
+ query = '{"jsonrpc":"2.0", "method":"Addons.SetAddonEnabled","params":{"addonid":"%s", "enabled":%s}, "id":1}' % (setting, value)
+ response = xbmc.executeJSONRPC(query)
+ if 'error' in str(response):
+ xbmc.log('### Error in json: %s'%query,2)
+ xbmc.log('^ %s' % response, 2)
+ return False
+ else:
+ return True
+
+# If it's none of the above then it must be a json command so we use the setting_type as the method in json
+ elif setting_type == 'json':
+ query = '{"jsonrpc":"2.0", "method":"%s","params":{%s}, "id":1}' % (setting, value)
+ response = xbmc.executeJSONRPC(query)
+ if 'error' in str(response):
+ xbmc.log('### Error With Setting: %s' % response,2)
+ return False
+ else:
+ return True
+
+ except:
+ xbmc.log(Last_Error())
+#----------------------------------------------------------------
+# TUTORIAL #
+def Sleep_If_Function_Active(function, args=[], kill_time=30, show_busy=True):
+ """
+This will allow you to pause code while a specific function is
+running in the background.
+
+CODE: Sleep_If_Function_Active(function, args, kill_time, show_busy)
+
+AVAILABLE PARAMS:
+
+ function - This is the function you want to run. This does
+ not require brackets, you only need the function name.
+
+ args - These are the arguments you want to send through to
+ the function, these need to be sent through as a list.
+
+ kill_time - By default this is set to 30. This is the maximum
+ time in seconds you want to wait for a response. If the max.
+ time is reached before the function completes you will get
+ a response of False.
+
+ show_busy - By default this is set to True so you'll get a busy
+ working dialog appear while the function is running. Set to
+ false if you'd rather not have this.
+
+EXAMPLE CODE:
+def Open_Test_URL(url):
+ koding.Open_URL(url)
+
+dialog.ok('SLEEP IF FUNCTION ACTIVE','We will now attempt to read a 20MB zip and then give up after 10 seconds.','Press OK to continue.')
+koding.Sleep_If_Function_Active(function=Open_Test_URL, args=['http://download.thinkbroadband.com/20MB.zip'], kill_time=10, show_busy=True)
+dialog.ok('FUNCTION COMPLETE','Of course we cannot read that file in just 10 seconds so we\'ve given up!')
+~"""
+ from guitools import Show_Busy
+ import threading
+ if show_busy:
+ Show_Busy(True)
+ my_thread = threading.Thread(target=function, args=args)
+ my_thread.start()
+ thread_alive = True
+ counter = 0
+ while thread_alive and counter <= kill_time:
+ xbmc.sleep(1000)
+ thread_alive = my_thread.isAlive()
+ counter += 1
+ if show_busy:
+ Show_Busy(False)
+ return thread_alive
+#----------------------------------------------------------------
+# TUTORIAL #
+def Sleep_If_Window_Active(window_type=10147):
+ """
+This will allow you to pause code while a specific window is open.
+
+CODE: Sleep_If_Window_Active(window_type)
+
+AVAILABLE PARAMS:
+
+ window_type - This is the window xml name you want to check for, if it's
+ active then the code will sleep until it becomes inactive. By default this
+ is set to the custom text box (10147). You can find a list of window ID's
+ here: http://kodi.wiki/view/Window_IDs
+
+EXAMPLE CODE:
+koding.Text_Box('EXAMPLE TEXT','This is just an example, normally a text box would not pause code and the next command would automatically run immediately over the top of this.')
+koding.Sleep_If_Window_Active(10147) # This is the window id for the text box
+dialog.ok('WINDOW CLOSED','The window has now been closed so this dialog code has now been initiated')
+~"""
+ from __init__ import dolog
+ windowactive = False
+ counter = 0
+
+ if window_type == 'yesnodialog' or window_type == 10100:
+ count = 30
+ else:
+ count = 10
+
+ okwindow = False
+
+# Do not get stuck in an infinite loop. Check x amount of times and if condition isn't met after x amount it quits
+ while not okwindow and counter < count:
+ xbmc.sleep(100)
+ okwindow = xbmc.getCondVisibility('Window.IsActive(%s)' % window_type)
+ counter += 1
+
+# Window is active
+ while okwindow:
+ okwindow = xbmc.getCondVisibility('Window.IsActive(%s)' % window_type)
+ xbmc.sleep(250)
+
+ return okwindow
+#----------------------------------------------------------------
+# TUTORIAL #
+def System(command, function=''):
+ """
+This is just a simplified method of grabbing certain Kodi infolabels, paths
+and booleans as well as performing some basic built in kodi functions.
+We have a number of regularly used functions added to a dictionary which can
+quickly be called via this function or you can use this function to easily
+run a command not currently in the dictionary. Just use one of the
+many infolabels, builtin commands or conditional visibilities available:
+
+info: http://kodi.wiki/view/InfoLabels
+bool: http://kodi.wiki/view/List_of_boolean_conditions
+
+CODE: System(command, [function])
+
+AVAILABLE PARAMS:
+
+ (*) command - This is the command you want to perform, below is a list
+ of all the default commands you can choose from, however you can of course
+ send through your own custom command if using the function option (details
+ at bottom of page)
+
+ AVAILABLE VALUES:
+
+ 'addonid' : Returns the FOLDER id of the current add-on. Please note could differ from real add-on id.
+ 'addonname' : Returns the current name of the add-on
+ 'builddate' : Return the build date for the current running version of Kodi
+ 'cpu' : Returns the CPU usage as a percentage
+ 'cputemp' : Returns the CPU temperature in farenheit or celcius depending on system settings
+ 'currentlabel' : Returns the current label of the item in focus
+ 'currenticon' : Returns the name of the current icon
+ 'currentpos' : Returns the current list position of focused item
+ 'currentpath' : Returns the url called by Kodi for the focused item
+ 'currentrepo' : Returns the repo of the current focused item
+ 'currentskin' : Returns the FOLDER id of the skin. Please note could differ from actual add-on id
+ 'date' : Returns the date (Tuesday, April 11, 2017)
+ 'debug' : Toggles debug mode on/off
+ 'freeram' : Returns the amount of free memory available (in MB)
+ 'freespace' : Returns amount of free space on storage in this format: 10848 MB Free
+ 'hibernate' : Hibernate system, please note not all systems are capable of waking from hibernation
+ 'internetstate' : Returns True or False on whether device is connected to internet
+ 'ip' : Return the current LOCAL IP address (not your public IP)
+ 'kernel' : Return details of the system kernel
+ 'language' : Return the language currently in use
+ 'mac' : Return the mac address, will only return the mac currently in use (Wi-Fi OR ethernet, not both)
+ 'numitems' : Return the total amount of list items curently in focus
+ 'profile' : Return the currently running profile name
+ 'quit' : Quit Kodi
+ 'reboot' : Reboot the system
+ 'restart' : Restart Kodi (Windows/Linux only)
+ 'shutdown' : Shutdown the system
+ 'sortmethod' : Return the current list sort method
+ 'sortorder' : Return the current list sort order
+ 'systemname' : Return a clean friendly name for the system
+ 'time' : Return the current time in this format: 2:05 PM
+ 'usedspace' : Return the amount of used space on the storage in this format: 74982 MB Used
+ 'version' : Return the current version of Kodi, this may need cleaning up as it contains full file details
+ 'viewmode' : Return the current list viewmode
+ 'weatheraddon' : Return the current plugin being used for weather
+
+
+ function - This is optional and default is set to a blank string which will
+ allow you to use the commands listed above but if set you can use your own
+ custom commands by setting this to one of the values below.
+
+ AVAILABLE VALUES:
+
+ 'bool' : This will allow you to send through a xbmc.getCondVisibility() command
+ 'info' : This will allow you to send through a xbmc.getInfoLabel() command
+ 'exec' : This will allow you to send through a xbmc.executebuiltin() command
+
+EXAMPLE CODE:
+current_time = koding.System(command='time')
+current_label = koding.System(command='currentlabel')
+is_folder = koding.System(command='ListItem.IsFolder', function='bool')
+dialog.ok('PULLED DETAILS','The current time is %s' % current_time, 'Folder status of list item [COLOR=dodgerblue]%s[/COLOR]: %s' % (current_label, is_folder),'^ A zero means False, as in it\'s not a folder.')
+~"""
+ params = {
+ 'addonid' :'xbmc.getInfoLabel("Container.PluginName")',
+ 'addonname' :'xbmc.getInfoLabel("Container.FolderName")',
+ 'builddate' :'xbmc.getInfoLabel("System.BuildDate")',
+ 'cpu' :'xbmc.getInfoLabel("System.CpuUsage")',
+ 'cputemp' :'xbmc.getInfoLabel("System.CPUTemperature")',
+ 'currentlabel' :'xbmc.getInfoLabel("System.CurrentControl")',
+ 'currenticon' :'xbmc.getInfoLabel("ListItem.Icon")',
+ 'currentpos' :'xbmc.getInfoLabel("Container.CurrentItem")',
+ 'currentpath' :'xbmc.getInfoLabel("Container.FolderPath")',
+ 'currentrepo' :'xbmc.getInfoLabel("Container.Property(reponame)")',
+ 'currentskin' :'xbmc.getSkinDir()',
+ 'date' :'xbmc.getInfoLabel("System.Date")',
+ 'debug' :'xbmc.executebuiltin("ToggleDebug")',
+ 'freeram' :'xbmc.getFreeMem()',
+ 'freespace' :'xbmc.getInfoLabel("System.FreeSpace")',
+ 'hibernate' :'xbmc.executebuiltin("Hibernate")',
+ 'internetstate' :'xbmc.getInfoLabel("System.InternetState")',
+ 'ip' :'xbmc.getIPAddress()',
+ 'kernel' :'xbmc.getInfoLabel("System.KernelVersion")',
+ 'language' :'xbmc.getInfoLabel("System.Language")',
+ 'mac' :'xbmc.getInfoLabel("Network.MacAddress")',
+ 'numitems' :'xbmc.getInfoLabel("Container.NumItems")',
+ 'profile' :'xbmc.getInfoLabel("System.ProfileName")',
+ 'quit' :'xbmc.executebuiltin("Quit")',
+ 'reboot' :'xbmc.executebuiltin("Reboot")',
+ 'restart' :'xbmc.restart()', # Windows/Linux only
+ 'shutdown' :'xbmc.shutdown()',
+ 'sortmethod' :'xbmc.getInfoLabel("Container.SortMethod")',
+ 'sortorder' :'xbmc.getInfoLabel("Container.SortOrder")',
+ 'systemname' :'xbmc.getInfoLabel("System.FriendlyName")',
+ 'time' :'xbmc.getInfoLabel("System.Time")',
+ 'usedspace' :'xbmc.getInfoLabel("System.UsedSpace")',
+ 'version' :'xbmc.getInfoLabel("System.BuildVersion")',
+ 'viewmode' :'xbmc.getInfoLabel("Container.Viewmode")',
+ 'weatheraddon' :'xbmc.getInfoLabel("Weather.plugin")',
+ }
+
+ if function == '': newcommand = params[command]
+ elif function == 'info': newcommand = 'xbmc.getInfoLabel("%s")' % command
+ elif function == 'bool': newcommand = 'xbmc.getCondVisibility("%s")' % command
+ elif function == 'exec': newcommand = 'xbmc.getCondVisibility("%s")' % command
+ else:
+ dialog.ok('INCORRECT PARAMS','The following command has been called:','koding.System(%s,[COLOR=dodgerblue]%s[/COLOR])'%(command, function),'^ The wrong function has been sent through, please double check the section highlighted in blue.')
+
+ try:
+ return eval(newcommand)
+ except:
+ return 'error'
+#----------------------------------------------------------------
+# TUTORIAL #
+def Timestamp(mode = 'integer'):
+ """
+This will return the timestamp in various formats. By default it returns as "integer" mode but other options are listed below:
+
+CODE: Timestamp(mode)
+mode is optional, by default it's set as integer
+
+AVAILABLE VALUES:
+
+ 'integer' - An integer which is nice and easy to work with in Python (especially for
+ finding out human readable diffs). The format returned is [year][month][day][hour][minutes][seconds].
+
+ 'epoch' - Unix Epoch format (calculated in seconds passed since 12:00 1st Jan 1970).
+
+ 'clean' - A clean user friendly time format: Tue Jan 13 10:17:09 2009
+
+ 'date_time' - A clean interger style date with time at end: 2017-04-07 10:17:09
+
+EXAMPLE CODE:
+integer_time = koding.Timestamp('integer')
+epoch_time = koding.Timestamp('epoch')
+clean_time = koding.Timestamp('clean')
+date_time = koding.Timestamp('date_time')
+import datetime
+installedtime = str(datetime.datetime.now())[:-7]
+dialog.ok('CURRENT TIME','Integer: %s' % integer_time, 'Epoch: %s' % epoch_time, 'Clean: %s' % clean_time)
+~"""
+ import time
+ import datetime
+
+ now = time.time()
+ try:
+ localtime = time.localtime(now)
+ except:
+ localtime = str(datetime.datetime.now())[:-7]
+ localtime = localtime.replace('-','').replace(':','')
+ if mode == 'date_time':
+ return time.strftime('%Y-%m-%d %H:%M:%S', localtime)
+ if mode == 'integer':
+ return time.strftime('%Y%m%d%H%M%S', localtime)
+
+ if mode == 'clean':
+ return time.asctime(localtime)
+
+ if mode == 'epoch':
+ return now
+#----------------------------------------------------------------
\ No newline at end of file
diff --git a/script.module.python.koding.aio/lib/koding/tutorials.py b/script.module.python.koding.aio/lib/koding/tutorials.py
new file mode 100644
index 0000000..95c2370
--- /dev/null
+++ b/script.module.python.koding.aio/lib/koding/tutorials.py
@@ -0,0 +1,169 @@
+# -*- coding: utf-8 -*-
+
+# script.module.python.koding.aio
+# Python Koding AIO (c) by TOTALREVOLUTION LTD (support@trmc.freshdesk.com)
+
+# Python Koding AIO is licensed under a
+# Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.
+
+# You should have received a copy of the license along with this
+# work. If not, see http://creativecommons.org/licenses/by-nc-nd/4.0.
+
+# Please make sure you've read and understood the license, this code can NOT be used commercially
+# and it can NOT be modified and redistributed. If you're found to be in breach of this license
+# then any affected add-ons will be blacklisted and will not be able to work on the same system
+# as any other add-ons which use this code. Thank you for your cooperation.
+
+import os
+import re
+import sys
+import urllib
+import xbmc
+import xbmcaddon
+import xbmcgui
+import xbmcplugin
+import xbmcvfs
+
+from directory import Add_Dir
+from filetools import Text_File
+from vartools import Find_In_Text
+from guitools import Text_Box, Show_Busy, Keyboard
+from systemtools import Sleep_If_Window_Active
+from video import Play_Video
+from web import Open_URL
+
+dialog = xbmcgui.Dialog()
+py_path = 'special://home/addons/script.module.python.koding.aio/lib/koding'
+video_base = 'http://totalrevolution.tv/videos/python_koding/'
+#----------------------------------------------------------------
+def Grab_Tutorials():
+ """ internal command ~"""
+ import re
+ full_array = []
+ dirs,files = xbmcvfs.listdir(py_path)
+# Check all the modules for functions with tutorial info
+ for file in files:
+ file_path = os.path.join(py_path,file)
+ if file.endswith('.py') and file != 'tutorials.py':
+ content = Text_File(file_path,'r').replace('\r','')
+ # content_array = re.compile('# TUTORIAL #\ndef (.+?)\(').findall(content)
+ content_array = Find_In_Text(content=content, start='# TUTORIAL #\ndef ', end='\(', show_errors=False)
+ if content_array:
+ for item in content_array:
+ item = item.strip()
+ full_array.append('%s~%s'%(item,file_path))
+ content_array = Find_In_Text(content=content, start='# TUTORIAL #\nclass ', end='\(', show_errors=False)
+ if content_array:
+ for item in content_array:
+ item = item.strip()
+ full_array.append('%s~%s'%(item,file_path))
+
+# Return a list of tutorials
+ Add_Dir('[COLOR=gold]CREATE YOUR FIRST ADD-ON[/COLOR]',video_base+'Create_Addon.mov','play_video', folder=False, icon='', fanart='', description='How to create your own add-on using the Python Koding framework.')
+
+ for item in sorted(full_array,key=str.lower):
+ name, filepath = item.split('~')
+ filepath = urllib.quote(filepath)
+ Add_Dir(name=name.upper().replace('_',' '), url='%s~%s'%(name,filepath), mode='show_tutorial', folder=False, icon='', fanart='', description='Instructions for how to use the %s function.'%name)
+#----------------------------------------------------------------
+def Show_Tutorial(url):
+ """ internal command ~"""
+ name, filepath = url.split('~')
+ filepath = urllib.unquote(filepath)
+ readfile = Text_File(filepath,'r').replace('\r','')
+ try:
+ raw_find = Find_In_Text(content=readfile, start='# TUTORIAL #\ndef %s' % name,end='~"""')[0]
+ except:
+ raw_find = Find_In_Text(content=readfile, start='# TUTORIAL #\nclass %s' % name,end='~"""')[0]
+# Check if an example code segment exists in the comments
+ if 'EXAMPLE CODE:' in raw_find:
+ code = re.findall(r'(?<=EXAMPLE CODE:)(?s)(.*$)', raw_find)[0]
+ code = code.replace('script.module.python.koding.aio','temp_replace_string')
+ code = code.replace('koding.','').strip()
+ code = code.replace('temp_replace_string','script.module.python.koding.aio')
+
+ else:
+ code = None
+
+# Check if a video exists in the comments
+ internetstate = xbmc.getInfoLabel('System.InternetState')
+ if internetstate:
+ video_page = Open_URL(video_base)
+ extension = Find_In_Text(video_page, name, '"', False)
+ if extension != '' and extension != None:
+ video = video_base+name+extension[0]
+ else:
+ video = None
+ else:
+ video = None
+
+ counter = 0
+ removal_string = ''
+ final_header = ''
+ newline = ''
+ temp_raw = raw_find.splitlines()
+ for line in temp_raw:
+ if counter == 0:
+ removal_string += line
+ if '[' in line:
+ replace_file = Find_In_Text(content=line,start='\[',end='\]')
+ for item in replace_file:
+ line = line.replace(item,'')
+ if ',' in line:
+ header_extension = line.split(',')
+ for item in header_extension:
+ if '=' in item:
+ item = item.split('=')[0]
+ final_header += item+','
+ final_header = 'koding.'+name+final_header[:-2]+')'
+ else:
+ final_header = 'koding.'+name+line[:-1]
+ else:
+ removal_string += '\n'+line
+ counter += 1
+ if counter == 2:
+ break
+ if final_header.endswith('))'):
+ final_header = final_header[:-1]
+ if final_header.startswith('koding.User_Info'):
+ final_header = 'koding.User_Info()'
+ full_text = raw_find.replace(removal_string,'').strip()
+
+# Initialise the dialog select
+ dialog_array = ['Documentation']
+ if code:
+ dialog_array.append('Run Example Code')
+ if video:
+ dialog_array.append('Watch Video')
+
+# If there's more than one item we show a dialog select otherwise we just load up the text window
+ if len(dialog_array) > 1:
+ choice = dialog.select(name, dialog_array)
+ if choice >= 0:
+ choice = dialog_array[choice]
+ if choice == 'Documentation':
+ Text_Box(final_header,full_text
+ .replace('AVAILABLE PARAMS:','[COLOR=dodgerblue]AVAILABLE PARAMS:[/COLOR]')
+ .replace('EXAMPLE CODE:','[COLOR=dodgerblue]EXAMPLE CODE:[/COLOR]')
+ .replace('IMPORTANT:','[COLOR=gold]IMPORTANT:[/COLOR]')
+ .replace('CODE:','[COLOR=dodgerblue]CODE:[/COLOR]')
+ .replace('AVAILABLE VALUES:','[COLOR=dodgerblue]AVAILABLE VALUES:[/COLOR]')
+ .replace('WARNING:','[COLOR=red]WARNING:[/COLOR]'))
+ elif choice == 'Run Example Code':
+ codefile = filepath.split(os.sep)
+ codefile = codefile[len(codefile)-1].replace('.py','')
+ exec('from %s import *' % codefile)
+ # exec('from %s import %s' % (codefile, params["name"]))
+ exec(code)
+ elif choice == 'Watch Video':
+ Play_Video(video)
+ if choice < 0:
+ return
+ else:
+ Text_Box(final_header,full_text
+ .replace('AVAILABLE PARAMS:','[COLOR=dodgerblue]AVAILABLE PARAMS:[/COLOR]')
+ .replace('EXAMPLE CODE:','[COLOR=dodgerblue]EXAMPLE CODE:[/COLOR]')
+ .replace('IMPORTANT:','[COLOR=gold]IMPORTANT:[/COLOR]')
+ .replace('CODE:','[COLOR=dodgerblue]CODE:[/COLOR]')
+ .replace('AVAILABLE VALUES:','[COLOR=dodgerblue]AVAILABLE VALUES:[/COLOR]')
+ .replace('WARNING:','[COLOR=red]WARNING:[/COLOR]'))
diff --git a/script.module.python.koding.aio/lib/koding/vartools.py b/script.module.python.koding.aio/lib/koding/vartools.py
new file mode 100644
index 0000000..bdb534c
--- /dev/null
+++ b/script.module.python.koding.aio/lib/koding/vartools.py
@@ -0,0 +1,930 @@
+# -*- coding: utf-8 -*-
+
+# script.module.python.koding.aio
+# Python Koding AIO (c) by TOTALREVOLUTION LTD (support@trmc.freshdesk.com)
+
+# Python Koding AIO is licensed under a
+# Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.
+
+# You should have received a copy of the license along with this
+# work. If not, see http://creativecommons.org/licenses/by-nc-nd/4.0.
+
+# Please make sure you've read and understood the license, this code can NOT be used commercially
+# and it can NOT be modified and redistributed. If you're found to be in breach of this license
+# then any affected add-ons will be blacklisted and will not be able to work on the same system
+# as any other add-ons which use this code. Thank you for your cooperation.
+
+import datetime
+import os
+import sys
+import shutil
+import xbmc
+import xbmcaddon
+import xbmcgui
+
+from filetools import Physical_Path
+
+HOME = Physical_Path('special://home')
+#----------------------------------------------------------------
+# TUTORIAL #
+def ASCII_Check(sourcefile=HOME, dp=False):
+ """
+Return a list of files found containing non ASCII characters in the filename.
+
+CODE: ASCII_Check([sourcefile, dp])
+
+AVAILABLE PARAMS:
+
+ sourcefile - The folder you want to scan, by default it's set to the
+ Kodi home folder.
+
+ dp - Optional DialogProgress, by default this is False. If you want
+ to show a dp make sure you initiate an instance of xbmcgui.DialogProgress()
+ and send through as the param.
+
+EXAMPLE CODE:
+home = koding.Physical_Path('special://home')
+progress = xbmcgui.DialogProgress()
+progress.create('ASCII CHECK')
+my_return = ASCII_Check(sourcefile=home, dp=progress)
+if len(my_return) > 0:
+ dialog.select('NON ASCII FILES', my_return)
+else:
+ dialog.ok('ASCII CHECK CLEAN','Congratulations!','There weren\'t any non-ASCII files found on this system.')
+~"""
+ rootlen = len(sourcefile)
+ for_progress = []
+ final_array = []
+ ITEM = []
+
+ for base, dirs, files in os.walk(sourcefile):
+ for file in files:
+ ITEM.append(file)
+ N_ITEM =len(ITEM)
+
+ for base, dirs, files in os.walk(sourcefile):
+ dirs[:] = [d for d in dirs]
+ files[:] = [f for f in files]
+
+ for file in files:
+ for_progress.append(file)
+ progress = len(for_progress) / float(N_ITEM) * 100
+ if dp:
+ dp.update(0,"Checking for non ASCII files",'[COLOR yellow]%s[/COLOR]'%d, 'Please Wait')
+
+ try:
+ file.encode('ascii')
+
+ except UnicodeDecodeError:
+ badfile = (str(base)+'/'+str(file)).replace('\\','/').replace(':/',':\\')
+ final_array.append(badfile)
+ return final_array
+#----------------------------------------------------------------
+# TUTORIAL #
+def Cleanup_String(my_string):
+ """
+Clean a string, removes whitespaces and common buggy formatting when pulling from websites
+
+CODE: Cleanup_String(my_string)
+
+AVAILABLE PARAMS:
+
+ (*) my_string - This is the main text you want cleaned up.
+
+EXAMPLE CODE:
+current_text = '" This is a string of text which should be cleaned up /'
+dialog.ok('ORIGINAL STRING', '[COLOR dodgerblue]%s[/COLOR]\n\nPress OK to view the cleaned up version.'%current_text)
+clean_text = koding.Cleanup_String(current_text)
+dialog.ok('CLEAN STRING', '[COLOR dodgerblue]%s[/COLOR]'%clean_text)
+~"""
+ import urllib
+ bad_chars = ['/','\\',':',';','"',"'"]
+
+ try:
+ my_string = my_string.encode('utf8')
+ except:
+ pass
+
+ my_string = urllib.unquote_plus(my_string)
+ my_string = my_string.replace(' ','').replace(' ','').replace(' ','')
+ my_string = my_string.replace('
','').replace('','').replace('','')
+ my_string = my_string.replace('&','&')
+ if len(my_string) > 4:
+ if my_string[-4] == '.':
+ my_string = my_string[:-4]
+
+ my_string = my_string.strip()
+
+ while my_string[0] in bad_chars or my_string[-1] in bad_chars:
+ if my_string[-1] in bad_chars:
+ my_string = my_string[:-1]
+ if my_string[0] in bad_chars:
+ my_string = my_string[1:]
+ my_string = my_string.strip()
+
+ return my_string
+#----------------------------------------------------------------
+# TUTORIAL #
+def Colour_Text(text, colour1='dodgerblue',colour2='white'):
+ """
+Capitalize a string and make the first colour of each string blue and the rest of text white
+That's the default colours but you can change to whatever colours you want.
+
+CODE: Colour_Text(text, [color1, color2])
+
+AVAILABLE PARAMS:
+
+ (*) text - This is the main text you want to change
+
+ colour1 - This is optional and is set as dodgerblue by default.
+ This is the first letter of each word in the string
+
+ colour2 - This is optional and is set as white by default.
+ This is the colour of the text
+
+IMPORTANT: I use the Queens English so please note the word "colour" has a 'u' in it!
+
+EXAMPLE CODE:
+current_text = 'This is a string of text which should be changed to dodgerblue and white with every first letter capitalised'
+mytext = koding.Colour_Text(text=current_text, colour1='dodgerblue', colour2='white')
+xbmc.log(current_text)
+xbmc.log(mytext)
+dialog.ok('CURRENT TEXT', current_text)
+dialog.ok('NEW TEXT', mytext)
+~"""
+ if text.startswith('[COLOR') and text.endswith('/COLOR]'):
+ return text
+
+ colour_clean = 0
+
+ if ' ' in text:
+ newname = ''
+ text = text.split(' ')
+ for item in text:
+ if len(item)==1 and item == '&':
+ newname += ' &'
+ if '[/COLOR]' in item:
+ newname += ' '+item
+ elif not item.startswith('[COLOR=') and not colour_clean:
+ if item.startswith('(') or item.startswith('['):
+ newname += '[COLOR=yellow] '+item
+ colour_clean = 1
+ else:
+ if item.isupper():
+ newname += '[COLOR=%s] %s[/COLOR]' % (colour1, item)
+ else:
+ try:
+ newname += '[COLOR=%s] %s[/COLOR][COLOR=%s]%s[/COLOR]' % (colour1, item[0].upper(), colour2, item[1:])
+ except:
+ try:
+ newname += '[COLOR=%s] %s[/COLOR][COLOR=%s][/COLOR]' % (colour1, item[0], colour2, item[1:])
+ except:
+ pass
+
+
+ elif item.endswith(')') or item.endswith(']'):
+ newname += ' '+item+'[/COLOR]'
+ colour_clean = 0
+
+ else:
+ newname += ' '+item
+
+ else:
+ if text[0] == '(':
+ newname = '[COLOR=%s]%s[/COLOR][COLOR=%s]%s[/COLOR][COLOR=%s]%s[/COLOR]' % (colour2, text[0], colour1, text[1].upper(), colour2, text[2:])
+ else:
+ newname = '[COLOR=%s]%s[/COLOR][COLOR=%s]%s[/COLOR]' % (colour1, text[0], colour2, text[1:])
+
+ success = 0
+ while success != 1:
+ if newname.startswith(' '):
+ newname = newname[1:]
+ success = 1
+ if newname.startswith('[COLOR=%s] ' % colour1):
+ newname = '[COLOR=%s]%s' % (colour1, newname[19:])
+
+ return newname
+#----------------------------------------------------------------
+# TUTORIAL #
+def Convert_Special(filepath=HOME, string=False, quoted=True):
+ """
+Convert physcial paths stored in text files to their special:// equivalent or
+replace instances of physical paths to special in a string sent through.
+
+CODE: Convert_Special([filepath, string])
+
+AVAILABLE PARAMS:
+
+ filepath - This is the path you want to scan, by default it's set to the Kodi HOME directory.
+
+ string - By default this is set to False which means it will convert all instances found of
+ the physical paths to their special equivalent. The scan will convert all instances in all filenames
+ ending in ini, xml, hash, properties. If you set this value to True you will get a return of your
+ 'filepath' string and no files will be altered.
+
+ quoted - By default this is set to true, this means the return you get will be converted
+ with urllib.quote_plus(). This is ideal if you need to get a string you can send
+ through as a path for routing.
+
+EXAMPLE CODE:
+path = koding.Physical_Path('special://profile')
+dialog.ok('ORIGINAL PATH','Let\'s convert this path to it\'s special equivalent:\n[COLOR dodgerblue]%s[/COLOR]'%path)
+path = Convert_Special(filepath=path,string=True,quoted=False)
+dialog.ok('CONVERTED PATH','This is the converted path:\n[COLOR dodgerblue]%s[/COLOR]'%path)
+if dialog.yesno('CONVERT PHYSICAL PATHS','We will now run through your Kodi folder converting all physical paths to their special:// equivalent in xml/hash/properties/ini files.\nDo you want to continue?'):
+ koding.Convert_Special()
+ dialog.ok('SUCCESS','Congratulations, all references to your physical paths have been converted to special:// paths.')
+~"""
+ import urllib
+ from filetools import Text_File
+ if not string:
+ for root, dirs, files in os.walk(filepath):
+ for file in files:
+ if file.endswith(".xml") or file.endswith(".hash") or file.endswith("properies") or file.endswith(".ini"):
+ contents = Text_File(os.path.join(root,file), 'r')
+ encodedpath = urllib.quote(HOME)
+ encodedpath2 = encodedpath.replace('%3A','%3a').replace('%5C','%5c')
+ newfile = contents.replace(HOME, 'special://home/').replace(encodedpath, 'special://home/').replace(encodedpath2, 'special://home/')
+ Text_File(os.path.join(root, file), 'w', newfile)
+ else:
+ encodedpath = urllib.quote(HOME)
+ encodedpath2 = encodedpath.replace('%3A','%3a').replace('%5C','%5c')
+ newstring = filepath.replace(HOME, 'special://home/').replace(encodedpath, 'special://home/').replace(encodedpath2, 'special://home/')
+ if quoted:
+ newstring = urllib.quote_plus(newstring)
+ return newstring
+#----------------------------------------------------------------
+# TUTORIAL #
+def Data_Type(data):
+ """
+This will return whether the item received is a dictionary, list, string, integer etc.
+
+CODE: Data_Type(data)
+
+AVAILABLE PARAMS:
+
+ (*) data - This is the variable you want to check.
+
+RETURN VALUES:
+ list, dict, str, int, float, bool
+
+EXAMPLE CODE:
+test1 = ['this','is','a','list']
+test2 = {"a" : "1", "b" : "2", "c" : 3}
+test3 = 'this is a test string'
+test4 = 12
+test5 = 4.3
+test6 = True
+
+my_return = '[COLOR dodgerblue]%s[/COLOR] : %s\n' % (test1, koding.Data_Type(test1))
+my_return += '[COLOR dodgerblue]%s[/COLOR] : %s\n' % (test2, koding.Data_Type(test2))
+my_return += '[COLOR dodgerblue]%s[/COLOR] : %s\n' % (test3, koding.Data_Type(test3))
+my_return += '[COLOR dodgerblue]%s[/COLOR] : %s\n' % (test4, koding.Data_Type(test4))
+my_return += '[COLOR dodgerblue]%s[/COLOR] : %s\n' % (test5, koding.Data_Type(test5))
+my_return += '[COLOR dodgerblue]%s[/COLOR] : %s\n' % (test6, koding.Data_Type(test6))
+
+koding.Text_Box('TEST RESULTS', my_return)
+~"""
+ data_type = type(data).__name__
+ return data_type
+#----------------------------------------------------------------
+# TUTORIAL #
+def Decode_String(string):
+ """
+This will allow you to send a string which contains a variety of special characters (including
+non ascii, unicode etc.) and it will convert into a nice clean string which plays nicely
+with Python and Kodi.
+
+CODE: Decode_String(string)
+
+AVAILABLE PARAMS:
+
+ (*) string - This is the string you want to convert
+
+EXAMPLE CODE:
+my_string = 'symbols like [COLOR dodgerblue]¥¨˚∆ƒπø“¬∂≈óõřĖė[/COLOR] can cause errors \nnormal chars like [COLOR dodgerblue]asfasdf[/COLOR] are fine'
+dialog.ok('ORIGINAL TEXT',my_string)
+my_string = koding.Decode_String(my_string)
+dialog.ok('DECODED/STRIPPED',my_string)
+~"""
+ try:
+ string = string.encode('ascii', 'ignore')
+ except:
+ string = string.decode('utf-8').encode('ascii', 'ignore')
+ return string
+#----------------------------------------------------------------
+# TUTORIAL #
+def Find_In_Text(content, start, end, show_errors = False):
+ """
+Regex through some text and return a list of matches.
+Please note this will return a LIST so even if only one item is found
+you will still need to access it as a list, see example below.
+
+CODE: Find_In_Text(content, start, end, [show_errors])
+
+AVAILABLE PARAMS:
+
+ (*) content - This is the string to search
+
+ (*) start - The start search string
+
+ (*) end - The end search string
+
+ show_errors - Default is False, if set to True the code will show help
+ dialogs for bad code.
+
+EXAMPLE CODE:
+textsearch = 'This is some text so lets have a look and see if we can find the words "lets have a look"'
+dialog.ok('ORIGINAL TEXT','Below is the text we\'re going to use for our search:','[COLOR dodgerblue]%s[/COLOR]'%textsearch)
+search_result = koding.Find_In_Text(textsearch, 'text so ', ' and see')
+dialog.ok('SEARCH RESULT','You searched for the start string of "text so " and the end string of " and see".','','Your result is: [COLOR dodgerblue]%s[/COLOR]' % search_result[0])
+
+# Please note: we know for a fact there is only one result which is why we're only accessing list item zero.
+# If we were expecting more than one return we would probably do something more useful and loop through in a for loop.
+~"""
+ import re
+ if content == None or content == False:
+ if show_errors:
+ dialog.ok('ERROR WITH REGEX','No content sent through - there\'s nothing to scrape. Please check the website address is still active (details at bottom of log).')
+ xbmc.log(content)
+ return
+ if end != '':
+ links = re.findall('%s([\s\S]*?)%s' % (start, end), content)
+ if len(links)>0:
+ return links
+ else:
+ if show_errors:
+ xbmc.log(content)
+ dialog.ok('ERROR WITH REGEX','Please check your regex, there was content sent through to search but there are no matches for the regex supplied. The raw content has now been printed to the log')
+ return None
+#----------------------------------------------------------------
+# TUTORIAL #
+def Fuzzy_Search(search_string, search_list, replace_strings=[]):
+ """
+Send through a list of items and try to match against the search string.
+This will match where the search_string exists in the list or an item in
+the list exists in the search_string.
+
+CODE: Fuzzy_Search(search_string, search_list, [strip])
+
+AVAILABLE PARAMS:
+
+ (*) search_string - This is the string to search for
+
+ (*) search_list - The list of items to search through
+
+ replace_strings - Optionally send through a list of strings you want to
+ replace. For example you may want to search for "West Ham United" but in
+ the list you've sent through they've abbreviated it to "West Ham Utd FC". In
+ this case we might want to send through a replace_strings list of:
+
+ (["united","utd"], ["fc",""])
+
+ This will remove any instances of "FC" from the search and it will replace
+ instances of "united" to "utd". The code will convert everythig to lowercase
+ so it doesn't matter what case you use in these searches.
+
+EXAMPLE CODE:
+my_search = 'west ham utd'
+my_list = ['west ham united', 'west ham utd', 'rangers fc', 'Man City', 'West Ham United FC', 'Fulham FC', 'West Ham f.c']
+my_replace = (["united","utd"], ["fc",""], ["f.c",""])
+dialog.ok('FUZZY SEARCH','Let\'s search for matches similar to "west ham utd" in the list:\n\n%s'%my_list)
+search_result = koding.Fuzzy_Search(my_search, my_list, my_replace)
+good = ', '.join(search_result)
+bad = ''
+for item in my_list:
+ if item not in search_result:
+ bad += item+', '
+dialog.ok('RESULTS FOUND','[COLOR=dodgerblue]SEARCH:[/COLOR] %s\n[COLOR=lime]GOOD:[/COLOR] %s\n[COLOR=cyan]BAD:[/COLOR] %s'%(my_search,good,bad))
+~"""
+ final_array = []
+ newsearch = search_string.lower().strip().replace(' ','')
+ for item in replace_strings:
+ newsearch = newsearch.replace(item[0],item[1])
+ xbmc.log('newsearch: %s'%newsearch,2)
+ for item in search_list:
+ newitem = item.lower().strip().replace(' ','')
+ for rep in replace_strings:
+ newitem = newitem.replace(rep[0],rep[1])
+ xbmc.log('list_item: %s'%newitem,2)
+ if (newsearch in newitem) or (newitem in newsearch):
+ final_array.append(item)
+ if len(final_array)>0:
+ return final_array
+ else:
+ return False
+#----------------------------------------------------------------
+# TUTORIAL #
+def Highest_Version(content=[],start_point='',end_point=''):
+ """
+Send through a list of strings which all have a common naming structure,
+the one with the highest version number will be returned.
+
+CODE: Highest_Version(content,[start_point,end_point])
+
+AVAILABLE PARAMS:
+
+ (*) content - This is the list of filenames you want to check.
+
+ start_point - If your filenames have a common character/string immediately
+ before the version number enter that here. For example if you're looking at
+ online repository/add-on files you would use '-' as the start_point. The version
+ numbers always appear after the final '-' with add-ons residing on repo's.
+
+ end_point - If your version number is followed by a common string (e.g. '.zip')
+ then enter it in here.
+
+EXAMPLE CODE:
+mylist = ['plugin.test-1.0.zip','plugin.test-0.7.zip','plugin.test-1.1.zip','plugin.test-0.9.zip']
+dialog.ok('OUR LIST OF FILES', '[COLOR=dodgerblue]%s[/COLOR]\n[COLOR=powderblue]%s[/COLOR]\n[COLOR=dodgerblue]%s[/COLOR]\n[COLOR=powderblue]%s[/COLOR]'%(mylist[0],mylist[1],mylist[2],mylist[3]))
+
+highest = Highest_Version(content=mylist,start_point='-',end_point='.zip')
+dialog.ok('HIGHEST VERSION', 'The highest version number of your files is:','[COLOR=dodgerblue]%s[/COLOR]'%highest)
+~"""
+ highest = 0
+ highest_ver = ''
+ for item in content:
+ version = item.replace(end_point,'')
+ version = version.split(start_point)
+ version = version[len(version)-1]
+ if version > highest:
+ highest = version
+ highest_ver = item
+ return highest_ver
+#----------------------------------------------------------------
+# TUTORIAL #
+def ID_Generator(size=15):
+ """
+This will generate a random string made up of uppercase & lowercase ASCII
+characters and digits - it does not contain special characters.
+
+CODE: ID_Generator([size])
+size is an optional paramater.
+
+AVAILABLE PARAMS:
+
+ size - just send through an integer, this is the length of the string you'll get returned.
+ So if you want a password generated that's 20 characters long just use ID_Generator(20). The default is 15.
+
+EXAMPLE CODE:
+my_password = koding.ID_Generator(20)
+dialog.ok('ID GENERATOR','Password generated:', '', '[COLOR=dodgerblue]%s[/COLOR]' % my_password)
+~"""
+ import string
+ import random
+
+ chars=string.ascii_uppercase + string.digits + string.ascii_lowercase
+ return ''.join(random.choice(chars) for _ in range(size))
+#---------------------------------------------------------------------------------------------------
+# TUTORIAL #
+def List_From_Dict(mydict={},use_key=True):
+ """
+Send through a dictionary and return a list of either the keys or values.
+Please note: The returned list will be sorted in alphabetical order.
+
+CODE: List_From_Dict(mydict,[use_key])
+
+AVAILABLE PARAMS:
+
+ (*) mydict - This is the dictionary (original data) you want to traverse through.
+
+ use_key - By default this is set to True and a list of all your dictionary keys
+ will be returned. Set to False if you'd prefer to have a list of the values returned.
+
+EXAMPLE CODE:
+raw_data = {'test1':'one','test2':'two','test3':'three','test4':'four','test5':'five'}
+mylist1 = koding.List_From_Dict(mydict=raw_data)
+mylist2 = koding.List_From_Dict(mydict=raw_data,use_key=False)
+koding.Text_Box('LIST_FROM_DICT','Original dictionary: [COLOR dodgerblue]%s[/COLOR][CR][CR]Returned List (use_key=True): [COLOR dodgerblue]%s[/COLOR][CR]Returned List (use_key=False): [COLOR dodgerblue]%s[/COLOR]'%(raw_data,mylist1,mylist2))
+~"""
+ pos = 1
+ if use_key:
+ pos = 0
+ final_list = []
+ for item in mydict.items():
+ if item[0]!='' and item[1]!='':
+ final_list.append(item[pos])
+ return sorted(final_list)
+#---------------------------------------------------------------------------------------------------
+# TUTORIAL #
+def md5_check(src,string=False):
+ """
+Return the md5 value of string/file/directory, this will return just one unique value.
+
+CODE: md5_check(src,[string])
+
+AVAILABLE PARAMS:
+
+ (*) src - This is the source you want the md5 value of.
+ This can be a string, path of a file or path to a folder.
+
+ string - By default this is set to False but if you want to send
+ through a string rather than a path set this to True.
+
+EXAMPLE CODE:
+home = koding.Physical_Path('special://home')
+home_md5 = koding.md5_check(home)
+dialog.ok('md5 Check', 'The md5 of your home folder is:', '[COLOR=dodgerblue]%s[/COLOR]'%home_md5)
+
+guisettings = xbmc.translatePath('special://profile/guisettings.xml')
+guisettings_md5 = koding.md5_check(guisettings)
+dialog.ok('md5 Check', 'The md5 of your guisettings.xml:', '[COLOR=dodgerblue]%s[/COLOR]'%guisettings_md5)
+
+mystring = 'This is just a random text string we\'ll get the md5 value of'
+myvalue = koding.md5_check(src=mystring,string=True)
+dialog.ok('md5 String Check', 'String to get md5 value of:', '[COLOR=dodgerblue]%s[/COLOR]'%mystring)
+dialog.ok('md5 String Check', 'The md5 value of your string:', '[COLOR=dodgerblue]%s[/COLOR]'%myvalue)
+~"""
+ import hashlib
+ import os
+
+ SHAhash = hashlib.md5()
+ if not os.path.exists(src) and not string:
+ return -1
+
+# If source is a file
+ if string:
+ return hashlib.md5(src).hexdigest()
+# If source is a file
+ elif not os.path.isdir(src):
+ return hashlib.md5(open(src,'rb').read()).hexdigest()
+
+# If source is a directory
+ else:
+ try:
+ for root, dirs, files in os.walk(src):
+ for names in files:
+ filepath = os.path.join(root,names)
+ try:
+ f1 = open(filepath, 'rb')
+ except:
+ f1.close()
+ continue
+
+ while 1:
+# Read file in as little chunks
+ buf = f1.read(4096)
+ if not buf : break
+ SHAhash.update(hashlib.md5(buf).hexdigest())
+ f1.close()
+ except:
+ return -2
+
+ return SHAhash.hexdigest()
+#----------------------------------------------------------------
+# TUTORIAL #
+def Merge_Dicts(*dict_args):
+ """
+Send through any number of dictionaries and get a return of one merged dictionary.
+Please note: If you have duplicate keys the value will be overwritten by the final
+dictionary to be checked. So if you send through dicts a-f and the same key exists
+in dicts a,e,f the final value for that key would be whatever is set in 'f'.
+
+CODE: Merge_Dicts(*dict_args)
+
+AVAILABLE PARAMS:
+
+ (*) *dict_args - Enter as many dictionaries as you want, these will be merged
+ into one final dictionary. Please send each dictionary through as a new paramater.
+
+EXAMPLE CODE:
+dict1 = {'1':'one','2':'two'}
+dict2 = {'3':'three','4':'four','5':'five'}
+dict3 = {'6':'six','7':'seven'}
+dict4 = {'1':'three','8':'eight'}
+
+mytext = 'Original Dicts:\ndict1 = %s\ndict2 = %s\ndict3 = %s\ndict4 = %s\n\n'%(repr(dict1),repr(dict2),repr(dict3),repr(dict4))
+mytext += 'Merged dictionaries (1-3): %s\n\n'%repr(koding.Merge_Dicts(dict1,dict2,dict3))
+mytext += 'Merged dictionaries (1-4): %s\n\n'%repr(koding.Merge_Dicts(dict1,dict2,dict3,dict4))
+mytext += "[COLOR = gold]IMPORTANT:[/COLOR]\nNotice how on the last run the key '1'now has a value of three.\nThis is because dict4 also contains that same key."
+Text_Box('Merge_Dicts',mytext)
+~"""
+ result = {}
+ for dictionary in dict_args:
+ if Data_Type(dictionary)=='dict':
+ result.update(dictionary)
+ return result
+#----------------------------------------------------------------
+# TUTORIAL #
+def Parse_XML(source, block, tags):
+ """
+Send through the contents of an XML file and pull out a list of matching
+items in the form of dictionaries. When checking your results you should
+allow for lists to be returned, by default each tag found in the xml will
+be returned as a string but if multiple entries of the same tag exists your
+dictionary item will be a list. Although this can be used for many uses this
+was predominantly added for support of XML's which contain multiple links to video
+files using things like . When checking to see if a string or list has been
+returned you can use the Data_Type function from Koding which will return 'str' or 'list'.
+
+CODE: Parse_XML(source, block, tags)
+
+AVAILABLE PARAMS:
+
+ source - This is the original source file, this must already be read into
+ memory as a string so made sure you've either used Open_URL or Text_File to
+ read the contents before sending through.
+
+ block - This is the master tag you want to use for creating a dictionary of items.
+ For example if you have an xml which contains multiple tags called and you wanted
+ to create a dictionary of sub items found in each of these you would just use 'item'.
+
+ tags - This is a list of tags you want to return in your dictionary, so lets say each
+ section contains , and tags you can return a dictionary of all those
+ items by sending through ['link','title','thumb']
+
+EXAMPLE CODE:
+dialog.ok('DICTIONARY OF ITEMS','We will now attempt to return a list of the source details pulled from the official Kodi repository addon.xml')
+xml_file = koding.Physical_Path('special://xbmc/addons/repository.xbmc.org/addon.xml')
+xml_file = koding.Text_File(xml_file,'r')
+xbmc.log(xml_file,2)
+repo_details = koding.Parse_XML(source=xml_file, block='extension', tags=['info','checksum','datadir'])
+counter = 0
+for item in repo_details:
+ dialog.ok( 'REPO %s'%(counter+1),'info path: [COLOR dodgerblue]%s[/COLOR]\nchecksum path: [COLOR dodgerblue]%s[/COLOR]\ndatadir: [COLOR dodgerblue]%s[/COLOR]' % (repo_details[counter]['info'],repo_details[counter]['checksum'],repo_details[counter]['datadir']) )
+ counter += 1
+~"""
+ from BeautifulSoup import BeautifulSoup
+ soup = BeautifulSoup(source)
+ my_return = []
+
+# Grab all the blocks of xml to search
+ for myblock in soup.findAll(block):
+ if myblock:
+ my_dict = {}
+ for tag in tags:
+ newsoup = BeautifulSoup(str(myblock))
+ newtag = newsoup.findAll(tag)
+ if newtag:
+ xbmc.log(repr(newtag),2)
+
+ # If only one instance is found we add to dict as a plain string
+ if len(newtag)==1:
+ newtag = str(newtag).split(r'>')[1]
+ newtag = newtag.split(r'<')[0]
+
+ # Otherwise we add to dict as a list
+ else:
+ tag_array = []
+ for item in newtag:
+ mynewtag = str(item).split(r'>')[1]
+ mynewtag = mynewtag.split(r'<')[0]
+ tag_array.append(mynewtag)
+ newtag = tag_array
+ my_dict[tag] = newtag
+ my_return.append(my_dict)
+ return my_return
+#----------------------------------------------------------------
+# TUTORIAL #
+def Table_Convert(url, contents={}, table=0):
+ """
+Open a web page which a table and pull out the contents of that table
+into a list of dictionaries with your own custom keys.
+
+CODE: Table_Convert(url, contents, table)
+
+AVAILABLE PARAMS:
+
+ url - The url you want to open and pull data from
+
+ contents - Send through a dictionary of the keys you want to assign to
+ each of the cells. The format would be: {my_key : position}
+ You can pull out as many cells as you want, so if your table has 10 columns
+ but you only wanted to pull data for cells 2,4,5,6,8 then you could do so
+ by setting contents to the following params:
+ contents = {"name 1":2, "name 2":4, "name 3":5, "name 4":6, "name 5":8}
+
+ table - By default this is set to zero, this is to be used if there's
+ multiple tables on the page you're accessing. Remeber to start at zero,
+ so if you want to access the 2nd table on the page it will be table=1.
+
+EXAMPLE CODE:
+dialog.ok('TABLE DATA','Let\'s pull some details from the proxy list table found at:\nhttps://free-proxy-list.net.')
+proxies = koding.Table_Convert(url='https://free-proxy-list.net', contents={"ip":0,"port":1}, table=0)
+mytext = '[COLOR dodgerblue]Here are some proxies:[/COLOR]\n'
+for item in proxies:
+ mytext += '\nIP: %s\nPort: %s\n[COLOR steelblue]----------------[/COLOR]'%(item['ip'],item['port'])
+koding.Text_Box('MASTER PROXY LIST',mytext)
+~"""
+ from web import Open_URL
+ from BeautifulSoup import BeautifulSoup
+ table_list=[]
+ content = Open_URL(url)
+ if content:
+ rawdata = Parse_XML(content,'table',['td'])
+
+ # Work out the amount of columns in the table
+ soup = BeautifulSoup(content)
+ my_return = []
+ mytable = soup.findAll('table')[table]
+ if mytable:
+ newsoup = BeautifulSoup(str(mytable))
+ newtag = str( newsoup.find('tr') )
+ if '
= 10:
+ try:
+ xbmc.log('attempting to send click to close dp')
+ xbmc.executebuiltin('SendClick()')
+ if dp.iscanceled():
+ dp.close()
+ try:
+ dp.close()
+ except:
+ pass
+ except:
+ xbmc.log('### FAILED TO CLOSE DP')
+ try:
+ dp.close()
+ except:
+ pass
+
+ isplaying = xbmc.Player().isPlaying()
+ counter = 1
+ if xbmc.Player().isPlayingAudio():
+ return True
+# If xbmc player is not yet active give it some time to initialise
+ while not isplaying and counter < timeout:
+ xbmc.sleep(1000)
+ isplaying = xbmc.Player().isPlaying()
+ xbmc.log('### XBMC Player not yet active, sleeping for %s seconds' % counter)
+ counter += 1
+
+ success = 0
+ counter = 0
+
+# If it's playing give it time to physically start streaming then attempt to pull some info
+ if isplaying:
+ xbmc.sleep(1000)
+ while not success and counter < 5:
+ try:
+ if xbmc.Player().isPlayingVideo():
+ infotag = xbmc.Player().getVideoInfoTag()
+ vidtime = xbmc.Player().getTime()
+ if vidtime > 0:
+ success = 1
+
+# If playback doesn't start automatically (buffering) we force it to play
+ else:
+ xbmc.log('### Playback active but time at zero, trying to unpause')
+ xbmc.executebuiltin('PlayerControl(Play)')
+ xbmc.sleep(2000)
+ vidtime = xbmc.Player().getTime()
+ if vidtime > 0:
+ success = 1
+
+# If no infotag or time could be pulled then we assume playback failed, try and stop the xbmc.player
+ except:
+ counter += 1
+ xbmc.sleep(1000)
+
+# Check if the busy dialog is still active from previous locked up playback attempt
+ isbusy = xbmc.getCondVisibility('Window.IsActive(busydialog)')
+ counter = 1
+ while isbusy:
+ xbmc.log('### Busy dialog active, sleeping for %ss' % counter)
+ xbmc.sleep(1000)
+ isbusy = xbmc.getCondVisibility('Window.IsActive(busydialog)')
+ counter += 1
+ if counter >= 5:
+ xbmc.executebuiltin('Dialog.Close(busydialog)')
+
+ if not success:
+ xbmc.executebuiltin('PlayerControl(Stop)')
+ xbmc.log('### Failed playback, stopped stream')
+ return False
+ else:
+ return True
+#----------------------------------------------------------------
+# TUTORIAL #
+def Last_Played():
+ """
+Return the link of the last played (or currently playing) video.
+This differs to the built in getPlayingFile command as that only shows details
+of the current playing file, these details can differ to the url which was
+originally sent through to initiate the stream. This Last_Played function
+directly accesses the database to get the REAL link which was initiated and
+will even return the plugin path if it's been played through an external add-on.
+
+CODE: Last_Played()
+
+EXAMPLE CODE:
+if koding.Play_Video('http://totalrevolution.tv/videos/python_koding/Browse_To_Folder.mov'):
+ xbmc.sleep(3000)
+ xbmc.Player().stop()
+ last_vid = Last_Played()
+ dialog.ok('VIDEO LINK','The link we just played is:\n\n%s'%last_vid)
+else:
+ dialog.ok('PLAYBACK FAILED','Sorry this video is no longer available, please try using a different video link.')
+~"""
+ from database import DB_Query
+ from filetools import DB_Path_Check
+ from vartools import Decode_String
+ db_path = DB_Path_Check('MyVideos')
+ sql = "SELECT files.strFilename as mystring, path.strPath as mybase FROM files JOIN path ON files.idPath=path.idPath ORDER BY files.lastPlayed DESC LIMIT 1"
+ results = DB_Query(db_path, sql)
+ try:
+ if Decode_String(results[0]['mybase']).startswith('plugin://'):
+ return Decode_String(results[0]['mystring'])
+ else:
+ return Decode_String(results[0]['mybase']+results[0]['mystring'])
+ except:
+ return False
+#----------------------------------------------------------------
+# TUTORIAL #
+def Link_Tester(video='', local_check=True, proxy_list=None, proxy_url='https://free-proxy-list.net/', ip_col=0, port_col=1, table=0):
+ """
+Send through a link and test whether or not it's playable on other devices.
+Many links include items in the query string which lock the content down to your
+IP only so what may open fine for you may not open for anyone else!
+
+This function will attempt to load the page using a proxy. If when trying to access
+the link via a proxy the header size and content-type match then we assume the
+link will play on any device. This is not fool proof and could potentially return
+false positives depending on the security used on the website being accessed.
+
+The return you'll get is a dictionary of the following items:
+
+ 'plugin_path' - This will have the path for a plugin, it means the stream was
+ originally passed through an add-on to get the final link. If this is not set
+ to None then it "should" work on any device so long as that add-on is installed
+ (e.g. YouTube).
+
+ 'url' - This is the final resolved url which Kodi was playing, you need to check
+ the status though to find out whether or not that link is locked to your IP only.
+
+ 'status' - This will return one of the following status codes:
+ good - The link should work on all IPs.
+
+ bad_link - The link was not valid, won't even play on your current Kodi setup.
+
+ proxy_fail - None of the proxies sent through worked.
+
+ locked - The url only works on this device, if this is the case consider using
+ the plugin_path which should generally work on all devices (although this does
+ depend on how the developer of that add-on coded up their add-on).
+
+CODE: Link_Tester([proxy_list, url, ip_col, port_col, table])
+
+AVAILABLE PARAMS:
+
+ video - This is the url of the video you want to check
+
+ local_check - By default this is set to True and this function will first of
+ all attempt to play the video locally with no proxy just to make sure the
+ link is valid in the first place. If you want to skip this step then set
+ this to False.
+
+ proxy_list - If you already have a list of proxies you want to test with
+ send them through in the form of a list of dictionaries. Use the following
+ format: [{"ip":"0.0.0.0","port":"80"},{"ip":"127.0.0.1","port":"8080"}]
+
+ proxy_url - If you want to scrape for online proxies and loop through until a
+ working one has been found you can set the url here. If using this then
+ proxy_list can be left as the default (None). If you open this Link_Tester
+ function with no params the defaults are setup to grab from:
+ free-proxy-list.net but there is no guarantee this will always
+ work, the website may well change it's layout/security over time.
+
+ ip_col - If you've sent through a proxy_url then you'll need to set a column number
+ for where in the table the IP address is stored. The default is 0
+
+ port_col - If you've sent through a proxy_url then you'll need to set a column number
+ for where in the table the port details are stored. The default is 1
+
+ table - If you've sent through a proxy_url then you'll need to set a table number.
+ The default is 0 - this presumes we need to use the first html table found on the
+ page, if you require a different table then alter accordingly - remember zero is the
+ first instance so if you want the 3rd table on the page you would set to 2.
+
+EXAMPLE CODE:
+vid_test = Link_Tester(video='http://totalrevolution.tv/videos/python_koding/Browse_To_Folder.mov')
+if vid_test['status'] == 'bad_link':
+ dialog.ok('BAD LINK','The link you sent through cannot even be played on this device let alone another one!')
+elif vid_test['status'] == 'proxy_fail':
+ dialog.ok('PROXIES EXHAUSTED','It was not possible to get any working proxies as a result it\'s not possible to fully test whether this link will work on other devices.')
+elif vid_test['status'] == 'locked':
+ dialog.ok('NOT PLAYABLE','Although you can play this link locally the tester was unable to play it when using a proxy so this is no good.')
+ if vid_test['plugin_path']:
+ dialog.ok('THERE IS SOME GOOD NEWS!','Although the direct link for this video won\'t work on other IPs it "should" be possible to open this using the following path:\n[COLOR dodgerblue]%s[/COLOR]'%vid_test['plugin_path'])
+else:
+ dialog.ok('WORKING!!!','Congratulations this link can be resolved and added to your playlist.')
+~"""
+ import random
+ import urllib
+ from guitools import Notify
+ from vartools import Table_Convert
+ from systemtools import System
+ # xbmc.executebuiltin('RunScript(special://home/addons/script.module.python.koding.aio/lib/koding/localproxy.py)')
+ Notify('PLEASE WAIT','Checking Link - Step 1','5000','Video.png')
+ isplaying = xbmc.Player().isPlaying()
+
+# If video not yet playing try playing it
+ if not isplaying:
+ xbmc.Player().play(video)
+
+ if Check_Playback(True):
+ xbmclink = xbmc.Player().getPlayingFile()
+ active_plugin = System(command='addonid')
+ plugin_path = System(command='currentpath')
+ vid_title = ''
+ title_count = 0
+
+ while vid_title == '' and title_count < 10:
+ vid_title = xbmc.getInfoLabel('Player.Title')
+ xbmc.sleep(100)
+ title_count += 1
+
+ xbmc.Player().stop()
+ video_orig = Last_Played()
+ xbmc.log('VIDEO: %s'%video_orig,2)
+ if video_orig.startswith('plugin://'):
+ video = xbmclink
+ xbmc.log('NEW VIDEO: %s'%video,2)
+ else:
+ video = video_orig
+ r = requests.head(url=video, timeout=5)
+ orig_header = r.headers
+ try:
+ orig_size = orig_header['Content-Length']
+ except:
+ orig_size = 0
+ try:
+ orig_type = orig_header['Content-Type']
+ except:
+ orig_type = ''
+ proxies = Table_Convert(url=proxy_url, contents={"ip":ip_col,"port":port_col}, table=table)
+ myproxies = []
+ used_proxies = []
+ for item in proxies:
+ myproxies.append({'http':'http://%s:%s'%(item['ip'],item['port']),'https':'https://%s:%s'%(item['ip'],item['port'])})
+ success = False
+ if video_orig.startswith('plugin://'):
+ dp.create('[COLOR gold]CHECKING PROXIES[/COLOR]','This video is being parsed through another add-on so using the plugin path should work. Now checking the final resolved link...','')
+ else:
+ dp.create('[COLOR gold]CHECKING PROXIES[/COLOR]','Please wait...','')
+
+ counter = 1
+ while (not success) and (len(myproxies) > 0):
+ dp.update(counter/len(myproxies),'Checking proxy %s'%counter)
+ counter += 1
+ proxychoice = random.choice( range(0,len(myproxies)) )
+ currentproxy = myproxies[proxychoice]
+
+ # Find a working proxy and play the video through it
+ try:
+ xbmc.log(repr(currentproxy),2)
+ r = requests.head(url=video, proxies=currentproxy, timeout=5)
+ headers = r.headers
+ try:
+ new_size = headers['Content-Length']
+ except:
+ new_size = 0
+ try:
+ new_type = headers['Content-Type']
+ except:
+ new_type = ''
+ xbmc.log('orig size: %s'%orig_size,2)
+ xbmc.log('new size: %s'%new_size,2)
+ xbmc.log('orig type: %s'%orig_type,2)
+ xbmc.log('new type: %s'%new_type,2)
+ xbmc.log('VIDEO: %s'%video,2)
+ if orig_size != 0 and (orig_size==new_size) and (orig_type==new_type):
+ dp.close()
+ success = True
+ except:
+ xbmc.log('failed with proxy: %s'%currentproxy,2)
+
+ myproxies.pop(proxychoice)
+ if dp.iscanceled():
+ dp.close()
+ break
+ plugin_path = None
+ if video_orig.startswith('plugin://'):
+ plugin_path = video_orig
+ if len(myproxies)==0 and not success:
+ return {"plugin_path":plugin_path, "url":video, "status":"proxy_fail"}
+ elif not success:
+ return {"plugin_path":plugin_path, "url":video, "status":"locked"}
+ else:
+ return {"plugin_path":plugin_path, "url":video, "status":"good"}
+ else:
+ return {"plugin_path":None, "url":video, "status":"bad_link"}
+#----------------------------------------------------------------
+# TUTORIAL #
+def M3U_Selector(url,post_type='get',header='Stream Selection'):
+ """
+Send through an m3u/m3u8 playlist and have the contents displayed via a dialog select.
+The return will be a dictionary of 'name' and 'url'. You can send through either
+a locally stored filepath or an online URL.
+
+This function will try it's best to pull out the relevant playlist details even if the
+web page isn't a correctly formatted m3u playlist (e.g. an m3u playlist embedded into
+a blog page).
+
+CODE: M3U_Selector(url, [post_type, header])
+
+AVAILABLE PARAMS:
+ (*) url - The location of your m3u file, this can be local or online
+
+ post_type - If you need to use POST rather than a standard query string
+ in your url set this to 'post', by default it's set to 'get'.
+
+ header - This is the header you want to appear at the top of your dialog
+ selection window, by default it's set to "Stream Selection"
+
+EXAMPLE CODE:
+dialog.ok('M3U SELECTOR','We will now call this function using the following url:','','[COLOR dodgerblue]http://totalrevolution.tv/videos/playlists/youtube.m3u[/COLOR]')
+
+# This example uses YouTube plugin paths but any playable paths will work
+vid = koding.M3U_Selector(url='http://totalrevolution.tv/videos/playlists/youtube.m3u')
+
+
+# Make sure there is a valid link returned
+if vid:
+ playback = koding.Play_Video(video=vid['url'], showbusy=False)
+ if playback:
+ dialog.ok('SUCCESS!','Congratulations the playback was successful!')
+ xbmc.Player().stop()
+ else:
+ dialog.ok('OOPS!','Looks like something went wrong there, the playback failed. Check the links are still valid.')
+~"""
+ from web import Open_URL
+ from vartools import Cleanup_String, Find_In_Text
+ from filetools import Text_File
+ success = False
+ if url.startswith('http'):
+ content = Open_URL(url=url, post_type=post_type, timeout=10)
+ else:
+ try:
+ url = xbmc.translatePath(url)
+ except:
+ pass
+ content = Text_File(url,'r')
+ if content:
+ newcontent = content.splitlines()
+ name_array = []
+ url_array = []
+ name = ''
+ for line in newcontent:
+ line = line.strip()
+ # Grab the name of the stream
+ if line.startswith('#EXT'):
+ name = line.split(',')
+ name.pop(0)
+ name = ''.join(name)
+ # Grab the url(s) of the stream
+ if name != '' and line != '' and not line.startswith('#EXT'):
+ name_array.append(Cleanup_String(name))
+ line = line.replace(' ','').replace(' ','').replace(' ','')
+ line = line.replace('','').replace('','').replace('','')
+ xbmc.log('line: %s'%line)
+ if 'm3u' in line or 'm3u8' in line:
+ line = 'LIST~'+line
+ if 'src="' in line:
+ line = Find_In_Text(content=line, start='src="', end='"')[0]
+ url_array.append(line)
+ name = ''
+ line = ''
+ # If there is only one entry with no names/comments just return as unknown with the link
+ if not '#EXT' in content:
+ return {'name' : 'Unknown', 'url' : line}
+
+ # If there's a list we show a dialog select of the available links
+ if len(name_array) > 0:
+ choice = xbmcgui.Dialog().select(header, name_array)
+ if choice >= 0:
+
+ # If the selection is a final url and not a list of multiple links
+ if not url_array[choice].startswith('LIST~'):
+ success = True
+ return {'name' : name_array[choice], 'url' : url_array[choice]}
+
+ # List of multiple links detected, give option of which link to play
+ else:
+ clean_link = url_array[choice].replace('LIST~','')
+ content = Open_URL(url=clean_link, timeout=10)
+ if content:
+ newcontent = content.splitlines()
+ name_array = []
+ url_array = []
+ name = ''
+ counter = 1
+ for line in newcontent:
+ # Show name as link 1,2,3,4 etc.
+ if line.startswith('#EXT'):
+ name = 'LINK '+str(counter)
+ # Grab the link(s) to the video
+ if name != '' and line != '' and not line.startswith('#EXT'):
+ name_array.append(name)
+ line = line.replace(' ','').replace(' ','').replace(' ','')
+ line = line.replace('','').replace('','').replace('','')
+ url_array.append(line)
+ name = ''
+ line = ''
+ counter += 1
+ # If there is only one entry with no names/comments just return as unknown with the link
+ if not '#EXT' in content:
+ return {'name' : 'Unknown', 'url' : line}
+
+ # Give option of which link to play in case of multiple links available
+ if len(name_array) > 0:
+ choice = xbmcgui.Dialog().select(header, name_array)
+ if choice >= 0:
+ success = True
+ return {'name' : name_array[choice], 'url' : url_array[choice]}
+ if not success:
+ xbmcgui.Dialog().ok('NO LINKS FOUND','Sorry no valid links could be found for this stream.')
+ return False
+#----------------------------------------------------------------
+# TUTORIAL #
+def Play_Video(video,showbusy=True,content='video',ignore_dp=False,timeout=10, item=None, player=xbmc.Player(), resolver=None):
+ """
+This will attempt to play a video and return True or False on
+whether or not playback was successful. This function is similar
+to Check_Playback but this actually tries a number of methods to
+play the video whereas Check_Playback does not actually try to
+play a video - it will just return True/False on whether or not
+a video is currently playing.
+
+If you have m3u or m3u8 playlist links please use the M3U_Selector
+function to get the final resolved url.
+
+CODE: Play_Video(video, [showbusy, content, ignore_dp, timeout, item])
+
+AVAILABLE PARAMS:
+
+ (*) video - This is the path to the video, this can be a local
+ path, online path or a channel number from the PVR.
+
+ showbusy - By default this is set to True which means while the
+ function is attempting to playback the video the user will see the
+ busy dialog. Set to False if you prefer this not to appear but do
+ bare in mind a user may navigate to another section and try playing
+ something else if they think this isn't doing anything.
+
+ content - By default this is set to 'video', however if you're
+ passing through audio you may want to set this to 'music' so the
+ system can correctly set the tags for artist, song etc.
+
+ ignore_dp - By default this is set to True but if set to False
+ this will ignore the DialogProgress window. If you use a DP while
+ waiting for the stream to start then you'll want to set this True.
+ Please bare in mind the reason this check is in place and enabled
+ by default is because some streams do bring up a DialogProgress
+ when initiated (such as f4m proxy links) and disabling this check
+ in those circumstances can cause false positives.
+
+ timeout - This is the amount of time you want to allow for playback
+ to start before sending back a response of False. Please note if
+ ignore_dp is set to True then it will also add a potential 10s extra
+ to this amount if a DialogProgress window is open. The default setting
+ for this is 10s.
+
+ item - By default this is set to None and in this case the metadata
+ will be auto-populated from the previous Add_Dir so you'll just get the
+ basics like title, thumb and description. If you want to send through your
+ own metadata in the form of a dictionary you can do so and it will override
+ the auto-generation. If anything else sent through no metadata will be set,
+ you would use this option if you've already set metadata in a previous function.
+
+ player - By default this is set to xbmc.Player() but you can send through
+ a different class/function if required.
+
+ resolver - By default this is set to urlresolver but if you prefer to use
+ your own custom resolver then just send through that class when calling this
+ function and the link sent through will be resolved by your custom resolver.
+
+EXAMPLE CODE:
+isplaying = koding.Play_Video('http://totalrevolution.tv/videos/python_koding/Browse_To_Folder.mov')
+if isplaying:
+ dialog.ok('PLAYBACK SUCCESSFUL','Congratulations, playback was successful')
+ xbmc.Player().stop()
+else:
+ dialog.ok('PLAYBACK FAILED','Sorry, playback failed :(')
+~"""
+
+ xbmc.log('### ORIGINAL VIDEO: %s'%video)
+ if not resolver:
+ import urlresolver
+ resolver = urlresolver
+ try: import simplejson as json
+ except: import json
+
+ if not item:
+ meta = {}
+ for i in ['title', 'originaltitle', 'tvshowtitle', 'year', 'season', 'episode', 'genre', 'rating', 'votes',
+ 'director', 'writer', 'plot', 'tagline']:
+ try:
+ meta[i] = xbmc.getInfoLabel('listitem.%s' % i)
+ except:
+ pass
+ meta = dict((k, v) for k, v in meta.iteritems() if not v == '')
+ if 'title' not in meta:
+ meta['title'] = xbmc.getInfoLabel('listitem.label')
+ icon = xbmc.getInfoLabel('listitem.icon')
+ item = xbmcgui.ListItem(path=video, iconImage =icon, thumbnailImage=icon)
+ if content == "music":
+ try:
+ meta['artist'] = xbmc.getInfoLabel('listitem.artist')
+ item.setInfo(type='Music', infoLabels={'title': meta['title'], 'artist': meta['artist']})
+ except:
+ item.setInfo(type='Video', infoLabels=meta)
+ else:
+ item.setInfo(type='Video', infoLabels=meta)
+
+ elif type(item).__name__ == 'dict':
+ item.setInfo(type='Video', infoLabels=meta)
+
+ else:
+ pass
+
+ playback = False
+ if showbusy:
+ Show_Busy()
+
+
+# if a plugin path is sent we try activate window
+ if video.startswith('plugin://'):
+ try:
+ xbmc.log('Attempting to play via xbmc.Player().play() method')
+ player.play(video)
+ playback = Check_Playback(ignore_dp,timeout)
+ except:
+ xbmc.log(Last_Error())
+
+# If an XBMC action has been sent through we do an executebuiltin command
+ elif video.startswith('ActivateWindow') or video.startswith('RunAddon') or video.startswith('RunScript') or video.startswith('PlayMedia'):
+ try:
+ xbmc.log('Attempting to play via xbmc.executebuiltin method')
+ xbmc.executebuiltin('%s'%video)
+ playback = Check_Playback(ignore_dp,timeout)
+ except:
+ xbmc.log(Last_Error())
+
+ elif ',' in video:
+# Standard xbmc.player method (a comma in url seems to throw urlresolver off)
+ try:
+ xbmc.log('Attempting to play via xbmc.Player.play() method')
+ player.play('%s'%video, item)
+ playback = Check_Playback(ignore_dp,timeout)
+
+# Attempt to resolve via urlresolver
+ except:
+ try:
+ xbmc.log('Attempting to resolve via urlresolver module')
+ xbmc.log('video = %s'%video)
+ hmf = resolver.HostedMediaFile(url=video, include_disabled=False, include_universal=True)
+ if hmf.valid_url() == True:
+ video = hmf.resolve()
+ xbmc.log('### VALID URL, RESOLVED: %s'%video)
+ player.play('%s' % video, item)
+ playback = Check_Playback(ignore_dp,timeout)
+ except:
+ xbmc.log(Last_Error())
+
+# Play from a db entry - untested
+ elif video.isdigit():
+ xbmc.log('### Video is digit, presuming it\'s a db item')
+ command = ('{"jsonrpc": "2.0", "id":"1", "method": "Player.Open","params":{"item":{"channelid":%s}}}' % url)
+ xbmc.executeJSONRPC(command)
+ playback = Check_Playback(ignore_dp,timeout)
+
+ else:
+# Attempt to resolve via urlresolver
+ try:
+ xbmc.log('Attempting to resolve via urlresolver module')
+ xbmc.log('video = %s'%video)
+ hmf = resolver.HostedMediaFile(url=video, include_disabled=False, include_universal=True)
+ if hmf.valid_url() == True:
+ video = hmf.resolve()
+ xbmc.log('### VALID URL, RESOLVED: %s'%video)
+ player.play('%s' % video, item)
+ playback = Check_Playback(ignore_dp,timeout)
+
+# Standard xbmc.player method
+ except:
+ try:
+ xbmc.log('Attempting to play via xbmc.Player.play() method')
+ player.play('%s' % video, item)
+ playback = Check_Playback(ignore_dp,timeout)
+ except:
+ xbmc.log(Last_Error())
+
+ xbmc.log('Playback status: %s' % playback)
+ Show_Busy(False)
+ counter = 1
+ dialogprogress = xbmc.getCondVisibility('Window.IsActive(progressdialog)')
+ if not ignore_dp:
+ while dialogprogress:
+ dp.create('Playback Good','Closing dialog...')
+ xbmc.log('Attempting to close dp #%s'%counter)
+ dp.close()
+ xbmc.sleep(1000)
+ counter += 1
+ dialogprogress = xbmc.getCondVisibility('Window.IsActive(progressdialog)')
+
+ return playback
+#----------------------------------------------------------------
+# TUTORIAL #
+def Sleep_If_Playback_Active():
+ """
+This will allow you to pause code while kodi is playing audio or video
+
+CODE: Sleep_If_Playback_Active()
+
+EXAMPLE CODE:
+dialog.ok('PLAY A VIDEO','We will now attempt to play a video, once you stop this video you should see a dialog.ok message.')
+xbmc.Player().play('http://download.blender.org/peach/bigbuckbunny_movies/big_buck_bunny_720p_stereo.avi')
+xbmc.sleep(3000) # Give kodi enough time to load up the video
+koding.Sleep_If_Playback_Active()
+dialog.ok('PLAYBACK FINISHED','The playback has now been finished so this dialog code has now been initiated')
+~"""
+ isplaying = xbmc.Player().isPlaying()
+ while isplaying:
+ xbmc.sleep(500)
+ isplaying = xbmc.Player().isPlaying()
diff --git a/script.module.python.koding.aio/lib/koding/web.py b/script.module.python.koding.aio/lib/koding/web.py
new file mode 100644
index 0000000..4a0accb
--- /dev/null
+++ b/script.module.python.koding.aio/lib/koding/web.py
@@ -0,0 +1,332 @@
+# -*- coding: utf-8 -*-
+
+# script.module.python.koding.aio
+# Python Koding AIO (c) by TOTALREVOLUTION LTD (support@trmc.freshdesk.com)
+
+# Python Koding AIO is licensed under a
+# Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.
+
+# You should have received a copy of the license along with this
+# work. If not, see http://creativecommons.org/licenses/by-nc-nd/4.0.
+
+# Please make sure you've read and understood the license, this code can NOT be used commercially
+# and it can NOT be modified and redistributed. If you're found to be in breach of this license
+# then any affected add-ons will be blacklisted and will not be able to work on the same system
+# as any other add-ons which use this code. Thank you for your cooperation.
+
+import os
+import time
+import urllib
+import xbmc
+import xbmcgui
+import xbmcvfs
+from systemtools import Python_Version
+#----------------------------------------------------------------
+# TUTORIAL #
+def Cleanup_URL(url):
+ """
+Clean a url, removes whitespaces and common buggy formatting when pulling from websites
+
+CODE: Cleanup_URL(url)
+
+AVAILABLE PARAMS:
+
+ (*) url - This is the main url you want cleaned up.
+
+EXAMPLE CODE:
+raw_url = '" http://test.com/video/"/'
+clean_url = koding.Cleanup_URL(raw_url)
+dialog.ok('CLEANUP URL', 'Orig: %s'%raw_url,'Clean: %s'%clean_url)
+~"""
+ from HTMLParser import HTMLParser
+
+ bad_chars = ['/','\\',':',';','"',"'"]
+ url = url.strip()
+
+ while url[0] in bad_chars or url[-1] in bad_chars:
+ if url[-1] in bad_chars:
+ url = url[:-1]
+ if url[0] in bad_chars:
+ url = url[1:]
+ url = url.strip()
+ return HTMLParser().unescape(url)
+#----------------------------------------------------------------
+# TUTORIAL #
+def Delete_Cookies(filename='cookiejar'):
+ """
+This will delete your cookies file.
+
+CODE: Delete_Cookies([filename])
+
+AVAILABLE PARAMS:
+
+ filename - By default this is set to the filename 'cookiejar'.
+ This is the default cookie filename which is created by the Open_URL
+ function but you can use any name you want and this function will
+ return True or False on whether or not it's successfully been removed.
+
+EXAMPLE CODE:
+Open_URL(url='http://google.com',cookiejar='google')
+dialog.ok('GOOGLE COOKIES CREATED','We have just opened a page to google.com, if you check your addon_data folder for your add-on you should see a cookies folder and in there should be a cookie called "google". When you press OK this will be removed.')
+koding.Delete_Cookies(filename='google')
+~"""
+ from addons import Addon_Info
+ Addon_Version = Addon_Info(id='version')
+ Addon_Profile = Addon_Info(id='profile')
+ Cookie_File = os.path.join(Addon_Profile,'cookies',filename)
+ if xbmcvfs.delete(Cookie_File):
+ return True
+ else:
+ return False
+#----------------------------------------------------------------
+# TUTORIAL #
+def Download(url, dest, dp=None, timeout=5):
+ """
+This will download a file, currently this has to be a standard download link which doesn't require cookies/login.
+
+CODE: Download(src,dst,[dp])
+dp is optional, by default it is set to false
+
+AVAILABLE PARAMS:
+
+ (*) src - This is the source file, the URL to your download. If you attempted to download an item but it's not behaving the way you think it should (e.g. a zip file not unzipping) then change the extension of the downloaded file to .txt and open up in a text editor. You'll most likely find it's just a piece of text that was returned from the URL you gave and it should have details explaining why it failed. Could be that's the wrong URL, it requires some kind of login, it only accepts certain user-agents etc.
+
+ (*) dst - This is the destination file, make sure it's a physical path and not "special://...". Also remember you need to add the actual filename to the end of the path, so if we were downloading something to the "downloads" folder and we wanted the file to be called "test.txt" we would use this path: dst = "downloads/test.txt". Of course the downloads folder would actually need to exist otherwise it would fail and based on this poor example the downloads folder would be at root level of your device as we've not specified a path prior to that so it just uses the first level that's accessible.
+
+ dp - This is optional, if you pass through the dp function as a DialogProgress() then you'll get to see the progress of the download. If you choose not to add this paramater then you'll just get a busy spinning circle icon until it's completed. See the example below for a dp example.
+
+ timeout - By default this is set to 5. This is the max. amount of time you want to allow for checking whether or
+ not the url is a valid link and can be accessed via the system.
+
+EXAMPLE CODE:
+src = 'http://noobsandnerds.com/portal/Bits%20and%20bobs/Documents/user%20guide%20of%20the%20gyro%20remote.pdf'
+dst = 'special://home/remote.pdf'
+dp = xbmcgui.DialogProgress()
+dp.create('Downloading File','Please Wait')
+koding.Download(src,dst,dp)
+dialog.ok('DOWNLOAD COMPLETE','Your download is complete, please check your home Kodi folder. There should be a new file called remote.pdf.')
+dialog.ok('DELETE FILE','Click OK to delete the downloaded file.')
+xbmcvfs.delete(dst)
+~"""
+ from filetools import Physical_Path
+ dest = Physical_Path(dest)
+ status = Validate_Link(url,timeout)
+ if status >= 200 and status < 400:
+ if Python_Version() < 2.7 and url.startswith('https'):
+ url = url.replace('https','http')
+ start_time=time.time()
+ urllib.urlretrieve(url, dest, lambda nb, bs, fs: Download_Progress(nb, bs, fs, dp, start_time))
+ return True
+ else:
+ return False
+#----------------------------------------------------------------
+def Download_Progress(numblocks, blocksize, filesize, dp, start_time):
+ """ internal command ~"""
+
+ try:
+ percent = min(numblocks * blocksize * 100 / filesize, 100)
+ currently_downloaded = float(numblocks) * blocksize / (1024 * 1024)
+ kbps_speed = numblocks * blocksize / (time.time() - start_time)
+ if kbps_speed > 0:
+ eta = (filesize - numblocks * blocksize) / kbps_speed
+ else:
+ eta = 0
+ kbps_speed = kbps_speed / 1024
+ total = float(filesize) / (1024 * 1024)
+ mbs = '%.02f MB of %.02f MB' % (currently_downloaded, total)
+ e = 'Speed: %.02f Kb/s ' % kbps_speed
+ e += 'ETA: %02d:%02d' % divmod(eta, 60)
+ if dp:
+ dp.update(percent, mbs, e)
+ if dp.iscanceled():
+ dp.close()
+ except:
+ percent = 100
+ if dp:
+ dp.update(percent)
+ if dp:
+ if dp.iscanceled():
+ dp.close()
+ dp.close()
+#----------------------------------------------------------------
+# TUTORIAL #
+def Get_Extension(url):
+ """
+Return the extension of a url
+
+CODE: Get_Extension(url)
+
+AVAILABLE PARAMS:
+
+ (*) url - This is the url you want to grab the extension from
+
+EXAMPLE CODE:
+dialog.ok('ONLINE FILE','We will now try and get the extension of the file found at this URL:','','[COLOR=dodgerblue]http://www.sample-videos.com/video/mp4/720/big_buck_bunny_720p_1mb.mp4[/COLOR]')
+url_extension = koding.Get_Extension('http://www.sample-videos.com/video/mp4/720/big_buck_bunny_720p_1mb.mp4')
+dialog.ok('FILE EXTENSION','The file extension of this Big Buck Bunny sample is:','','[COLOR=dodgerblue]%s[/COLOR]'%url_extension)
+~"""
+
+ import os
+ import urlparse
+
+ parsed = urlparse.urlparse(url)
+ root, ext = os.path.splitext(parsed.path)
+ return ext
+#----------------------------------------------------------------
+# TUTORIAL #
+def Open_URL(url='',post_type='get',payload={},headers={'User-Agent':'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.3'},cookies=True,auth=None,timeout=None,cookiejar=None,proxies={}):
+ """
+If you need to pull the contents of a webpage it's very simple to do so by using this function.
+This uses the Python Requests module, for more detailed info on how the params work
+please look at the following link: http://docs.python-requests.org/en/master/user/advanced/
+
+IMPORTANT: This function will attempt to convert a url with a query string into the
+correct params for a post or get command but I highly recommend sending through your
+query string as a dictionary using the payload params. It's much cleaner and is a
+safer way of doing things, if you send through your url with a query string attached
+then I take no responsibility if it doesn't work!
+
+CODE: Open_URL(url,[post_type,payload,headers,cookies,auth,timeout,cookiejar])
+
+AVAILABLE PARAMS:
+
+ url - This is the main url you want to send through. Send it through
+ as a query string format even if it's a post.
+
+ post_type - By default this is set to 'get' but this can be set to 'post',
+ if set to post the query string will be split up into a post format automatically.
+
+ payload - By default this is not used but if you just want a standard
+ basic Open_URL function you can add a dictionary of params here. If you
+ don't enter anything in here the function will just split up your url
+ accordingly. Make sure you read the important information at the top
+ of this tutorial text.
+
+ headers - Optionally send through headers in form of a dictionary.
+
+ cookies - If set to true your request will send through and store cookies.
+
+ auth - User/pass details
+
+ timeout - Optionally set a timeout for the request.
+
+ cookiejar - An name for the location to store cookies. By default it's
+ set to addon_data//cookies/cookiejar but if you have multiple
+ websites you access then you may want to use a separate filename for each site.
+
+ proxies - Use a proxy for accessing the link, see requests documentation for full
+ information but essentially you would send through a dictionary like this:
+ proxies = {"http":"http://10.10.1.10:3128","htts":"https://10.10.1.10:3128"}
+
+EXAMPLE CODE:
+dialog.ok('OPEN FORUM PAGE','We will attempt to open the noobsandnerds forum page and return the contents. You will now be asked for your forum credentials.')
+myurl = 'http://noobsandnerds.com/support/index.php'
+username = koding.Keyboard('ENTER USERNAME')
+password = koding.Keyboard('ENTER PASSWORD')
+params = {"username":username,"password":password}
+xbmc.log(repr(params),2)
+url_contents = koding.Open_URL(url=myurl, payload=params, post_type='get')
+koding.Text_Box('CONTENTS OF WEB PAGE',url_contents)
+~"""
+ import os
+ import pickle
+ import requests
+ import sys
+ import xbmc
+ import xbmcaddon
+
+ from __init__ import converthex, dolog, ADDON_ID, KODI_VER
+ from addons import Addon_Info
+ from filetools import Text_File
+
+ Addon_Version = Addon_Info(id='version')
+ Addon_Profile = Addon_Info(id='profile')
+ Cookie_Folder = os.path.join(Addon_Profile,'cookies')
+ xbmc.log(Cookie_Folder,2)
+ if not xbmcvfs.exists(Cookie_Folder):
+ xbmcvfs.mkdirs(Cookie_Folder)
+
+ if cookiejar == None:
+ Cookie_Jar = os.path.join(Cookie_Folder,'cookiejar')
+ else:
+ Cookie_Jar = os.path.join(Cookie_Folder,cookiejar)
+
+ my_cookies = None
+ if cookies:
+ if xbmcvfs.exists(Cookie_Jar):
+ try:
+ openfile = xbmcvfs.File(Cookie_Jar)
+ f = openfile.read()
+ openfile.close()
+ my_cookies = pickle.load(f)
+ except:
+ my_cookies = None
+
+# If the payload is empty we split the params
+ if len(payload) == 0:
+
+ if '?' in url:
+ url, args = url.split('?')
+ args = args.split('&')
+ for item in args:
+ var, data = item.split('=')
+ payload[var] = data
+
+ dolog('PAYLOAD: %s'%payload)
+
+ try:
+ if Python_Version() < 2.7 and url.startswith('https'):
+ url = url.replace('https','http')
+
+ if post_type == 'post':
+ r = requests.post(url, payload, headers=headers, cookies=my_cookies, auth=auth, timeout=timeout, proxies=proxies)
+ else:
+ r = requests.get(url, payload, headers=headers, cookies=my_cookies, auth=auth, timeout=timeout, proxies=proxies)
+ except:
+ dolog('Failed to pull content for %s'%url)
+ return False
+ dolog('### CODE: %s | REASON: %s' % (r.status_code, r.reason))
+ if r.status_code >= 200 and r.status_code < 400:
+ content = r.text.encode('utf-8')
+ dolog('content: %s'%content)
+ if cookies:
+ openfile = xbmcvfs.File(Cookie_Jar,'wb')
+ pickle.dump(r.cookies, openfile)
+ openfile.close()
+ return content
+ else:
+ dolog('Failed to pull content for %s'%url)
+ return False
+#----------------------------------------------------------------
+# TUTORIAL #
+def Validate_Link(url='',timeout=30):
+ """
+Returns the code for a particular link, so for example 200 is a good link and 404 is a URL not found
+
+CODE: Validate_Link(url,[timeout])
+
+AVAILABLE PARAMS:
+
+ (*) url - This is url you want to check the header code for
+
+ timeout - An optional timeout integer for checking url (default is 30 seconds)
+
+EXAMPLE CODE:
+url_code = koding.Validate_Link('http://totalrevolution.tv')
+if url_code == 200:
+ dialog.ok('WEBSITE STATUS','The website [COLOR=dodgerblue]totalrevolution.tv[/COLOR] is [COLOR=lime]ONLINE[/COLOR]')
+else:
+ dialog.ok('WEBSITE STATUS','The website [COLOR=dodgerblue]totalrevolution.tv[/COLOR] is [COLOR=red]OFFLINE[/COLOR]')
+~"""
+ import requests
+ import xbmc
+
+ if Python_Version() < 2.7 and url.startswith('https'):
+ url = url.replace('https','http')
+
+ try:
+ r = requests.get(url,timeout=timeout)
+ return r.status_code
+ except:
+ return 400
+#----------------------------------------------------------------
\ No newline at end of file
diff --git a/script.module.python.koding.aio/resources/.DS_Store b/script.module.python.koding.aio/resources/.DS_Store
new file mode 100644
index 0000000..f94e34b
Binary files /dev/null and b/script.module.python.koding.aio/resources/.DS_Store differ
diff --git a/script.module.python.koding.aio/resources/language/.DS_Store b/script.module.python.koding.aio/resources/language/.DS_Store
new file mode 100644
index 0000000..907ce39
Binary files /dev/null and b/script.module.python.koding.aio/resources/language/.DS_Store differ
diff --git a/script.module.python.koding.aio/resources/language/English/strings.po b/script.module.python.koding.aio/resources/language/English/strings.po
new file mode 100644
index 0000000..617115f
--- /dev/null
+++ b/script.module.python.koding.aio/resources/language/English/strings.po
@@ -0,0 +1,62 @@
+# Addon Name: Python Koding AIO
+# Addon id: script.module.python.koding.aio
+# Addon Provider: TOTALREVOLUTION LTD (support@trmc.freshdesk.com)
+msgid ""
+msgstr ""
+"Project-Id-Version: script.module.python.koding.aio\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Language-Team: English\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"Language: en\n"
+
+msgctxt "#30807"
+msgid "Please wait..."
+msgstr ""
+
+msgctxt "#30809"
+msgid "WARNING"
+msgstr ""
+
+msgctxt "#30810"
+msgid "You may not have enough space:"
+msgstr ""
+
+msgctxt "#30811"
+msgid "Amount of data in source: [COLOR=dodgerblue]%sMB[/COLOR]"
+msgstr ""
+
+msgctxt "#30812"
+msgid "Amount of free space: [COLOR=dodgerblue]%sMB[/COLOR]"
+msgstr ""
+
+msgctxt "#30813"
+msgid "Continue"
+msgstr ""
+
+msgctxt "#30814"
+msgid "Quit"
+msgstr ""
+
+msgctxt "#30815"
+msgid "The path given does not exist:[CR][CR][COLOR=dodgerblue]%s[/COLOR]"
+msgstr ""
+
+msgctxt "#30825"
+msgid "Wrong Password"
+msgstr ""
+
+msgctxt "#30833"
+msgid "Servers Busy"
+msgstr ""
+
+msgctxt "#30837"
+msgid "No information available"
+msgstr ""
+
+msgctxt "#30965"
+msgid "Error"
+msgstr ""
\ No newline at end of file
diff --git a/script.module.python.koding.aio/resources/skins/.DS_Store b/script.module.python.koding.aio/resources/skins/.DS_Store
new file mode 100644
index 0000000..a9f417d
Binary files /dev/null and b/script.module.python.koding.aio/resources/skins/.DS_Store differ
diff --git a/script.module.python.koding.aio/resources/skins/Default/.DS_Store b/script.module.python.koding.aio/resources/skins/Default/.DS_Store
new file mode 100644
index 0000000..ed1b1c3
Binary files /dev/null and b/script.module.python.koding.aio/resources/skins/Default/.DS_Store differ
diff --git a/script.module.python.koding.aio/resources/skins/Default/720p/.DS_Store b/script.module.python.koding.aio/resources/skins/Default/720p/.DS_Store
new file mode 100644
index 0000000..5008ddf
Binary files /dev/null and b/script.module.python.koding.aio/resources/skins/Default/720p/.DS_Store differ
diff --git a/script.module.python.koding.aio/resources/skins/Default/720p/Font.xml b/script.module.python.koding.aio/resources/skins/Default/720p/Font.xml
new file mode 100644
index 0000000..7bca1eb
--- /dev/null
+++ b/script.module.python.koding.aio/resources/skins/Default/720p/Font.xml
@@ -0,0 +1,521 @@
+
+
+
+
+ font10
+ Roboto-Regular.ttf
+ 14
+
+
+ font12
+ Roboto-Regular.ttf
+ 17
+
+
+ font13
+ Roboto-Regular.ttf
+ 20
+
+
+ font14
+ Roboto-Regular.ttf
+ 22
+
+
+ font16
+ Roboto-Regular.ttf
+ 25
+
+
+ font30
+ Roboto-Regular.ttf
+ 30
+
+
+ fontContextMenu
+ Roboto-Regular.ttf
+ 18
+
+
+
+
+
+ font10_title
+ Roboto-Bold.ttf
+ 12
+
+
+ font12_title
+ Roboto-Bold.ttf
+ 17
+
+
+ font13_title
+ Roboto-Bold.ttf
+ 20
+
+
+ font24_title
+ Roboto-Bold.ttf
+ 24
+
+
+ font28_title
+ Roboto-Bold.ttf
+ 28
+
+
+ font30_title
+ Roboto-Bold.ttf
+ 30
+
+
+ font35_title
+ Roboto-Bold.ttf
+ 35
+
+
+ font45caps_title
+ Roboto-Bold.ttf
+ 45
+
+
+
+ font_MainMenu
+ DejaVuSans-Bold-Caps.ttf
+ 35
+
+
+ WeatherTemp
+ Roboto-Bold.ttf
+ 80
+
+
+ osd1
+ LightCaps.ttf
+ 36
+
+
+ osd2
+ LightCaps.ttf
+ 30
+
+
+ osdTitle
+ Ubuntu-L.ttf
+ 36
+
+
+ rss
+ Ubuntu-R.ttf
+ 22
+
+
+ tvg_font10
+ Ubuntu-R.ttf
+ 16
+
+
+ tvg_font13
+ Ubuntu-R.ttf
+ 22
+
+
+ tvg_font14
+ Ubuntu-R.ttf
+ 20
+
+
+
+
+
+ font10
+ Roboto-Regular.ttf
+ 17
+
+
+ font12
+ Roboto-Regular.ttf
+ 20
+
+
+ font13
+ Roboto-Regular.ttf
+ 22
+
+
+ font14
+ Roboto-Regular.ttf
+ 25
+
+
+ font16
+ Roboto-Regular.ttf
+ 30
+
+
+ font30
+ Roboto-Regular.ttf
+ 32
+
+
+ fontContextMenu
+ Roboto-Regular.ttf
+ 20
+
+
+
+
+
+ font10_title
+ Roboto-Bold.ttf
+ 17
+
+
+ font12_title
+ Roboto-Bold.ttf
+ 20
+
+
+ font13_title
+ Roboto-Bold.ttf
+ 24
+
+
+ font24_title
+ Roboto-Bold.ttf
+ 28
+
+
+ font28_title
+ Roboto-Bold.ttf
+ 30
+
+
+ font30_title
+ Roboto-Bold.ttf
+ 32
+
+
+ font35_title
+ Roboto-Bold.ttf
+ 36
+
+
+ font45caps_title
+ Roboto-Bold.ttf
+ 48
+
+
+ font_MainMenu
+ Roboto-Bold.ttf
+ 42
+
+
+ WeatherTemp
+ Roboto-Bold.ttf
+ 80
+
+
+ osd1
+ LightCaps.ttf
+ 36
+
+
+ osd2
+ LightCaps.ttf
+ 30
+
+
+ osdTitle
+ Ubuntu-L.ttf
+ 36
+
+
+ rss
+ Ubuntu-R.ttf
+ 22
+
+
+ tvg_font10
+ Ubuntu-R.ttf
+ 16
+
+
+ tvg_font13
+ Ubuntu-R.ttf
+ 22
+
+
+ tvg_font14
+ Ubuntu-R.ttf
+ 20
+
+
+
+
+
+
+ font10
+ Roboto-Regular.ttf
+ 14
+
+
+ font12
+ Roboto-Regular.ttf
+ 17
+
+
+ font13
+ Roboto-Regular.ttf
+ 20
+
+
+ font14
+ Roboto-Regular.ttf
+ 22
+
+
+ font16
+ Roboto-Regular.ttf
+ 25
+
+
+ font30
+ Roboto-Regular.ttf
+ 30
+
+
+ fontContextMenu
+ Roboto-Regular.ttf
+ 18
+
+
+
+
+
+ font10_title
+ Roboto-Bold.ttf
+ 12
+
+
+ font12_title
+ Roboto-Bold.ttf
+ 17
+
+
+ font13_title
+ Roboto-Bold.ttf
+ 20
+
+
+ font24_title
+ Roboto-Bold.ttf
+ 24
+
+
+ font28_title
+ Roboto-Bold.ttf
+ 28
+
+
+ font30_title
+ Roboto-Bold.ttf
+ 30
+
+
+ font35_title
+ Roboto-Bold.ttf
+ 35
+
+
+ font45caps_title
+ Roboto-Bold.ttf
+ 45
+
+
+
+ font_MainMenu
+ Roboto-Bold.ttf
+ 40
+
+
+ WeatherTemp
+ Roboto-Bold.ttf
+ 80
+
+
+ osd1
+ LightCaps.ttf
+ 36
+
+
+ osd2
+ LightCaps.ttf
+ 30
+
+
+ osdTitle
+ Ubuntu-L.ttf
+ 36
+
+
+ rss
+ Ubuntu-R.ttf
+ 22
+
+
+ tvg_font10
+ Ubuntu-R.ttf
+ 16
+
+
+ tvg_font13
+ Ubuntu-R.ttf
+ 22
+
+
+ tvg_font14
+ Ubuntu-R.ttf
+ 20
+
+
+
+
+
+
+ font10
+ arial.ttf
+ 12
+
+
+ font12
+ arial.ttf
+ 15
+
+
+ font13
+ arial.ttf
+ 18
+
+
+ font14
+ arial.ttf
+ 20
+
+
+ font16
+ arial.ttf
+ 23
+
+
+ font30
+ arial.ttf
+ 28
+
+
+ fontContextMenu
+ arial.ttf
+ 16
+
+
+
+
+
+ font10_title
+ arial.ttf
+ 10
+
+
+
+ font12_title
+ arial.ttf
+ 15
+
+
+
+ font13_title
+ arial.ttf
+ 18
+
+
+
+ font24_title
+ arial.ttf
+ 22
+
+
+
+ font28_title
+ arial.ttf
+ 26
+
+
+
+ font30_title
+ arial.ttf
+ 28
+
+
+
+ font35_title
+ arial.ttf
+ 33
+
+
+
+ font45caps_title
+ arial.ttf
+ 43
+
+
+
+
+ font_MainMenu
+ arial.ttf
+ 35
+
+
+
+ WeatherTemp
+ arial.ttf
+ 80
+
+
+
+ osd1
+ Arial.ttf
+ 36
+
+
+ osd2
+ Arial.ttf
+ 30
+
+
+ osdTitle
+ Arial.ttf
+ 36
+
+
+ rss
+ Arial.ttf
+ 22
+
+
+ tvg_font10
+ Arial.ttf
+ 16
+
+
+ tvg_font13
+ Arial.ttf
+ 22
+
+
+ tvg_font14
+ Arial.ttf
+ 20
+
+
+
\ No newline at end of file
diff --git a/script.module.python.koding.aio/resources/skins/Default/720p/Loading.xml b/script.module.python.koding.aio/resources/skins/Default/720p/Loading.xml
new file mode 100644
index 0000000..e46653b
--- /dev/null
+++ b/script.module.python.koding.aio/resources/skins/Default/720p/Loading.xml
@@ -0,0 +1,874 @@
+
+
+ 100
+
+ 0
+ 0
+
+ WindowOpen
+ WindowClose
+
+
+ 0
+ 0
+ 1280
+ 720
+ $INFO[Window(Home).Property(update_background)]
+
+
+ Update Circle
+ 200
+ 130
+ 250
+ 250
+ $INFO[Window(Home).Property(update_icon)]
+ Conditional
+
+
+ Percent Text
+ 240
+ 480
+ 800
+ 50
+ center
+ center
+ font13
+
+ $INFO[Window(Home).Property(update_percent_color)]
+ !IsEmpty(Window(Home).Property(update_percent))
+
+
+ 240
+ 530
+ 800
+ 75
+ transparent_box.png
+ !IsEmpty(Window(Home).Property(update_percent))
+
+
+ 243
+ 533
+ 8
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_1))
+
+
+ 243
+ 533
+ 16
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_2))
+
+
+ 243
+ 533
+ 24
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_3))
+
+
+ 243
+ 533
+ 32
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_4))
+
+
+ 243
+ 533
+ 40
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_5))
+
+
+ 243
+ 533
+ 48
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_6))
+
+
+ 243
+ 533
+ 56
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_7))
+
+
+ 243
+ 533
+ 64
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_8))
+
+
+ 243
+ 533
+ 72
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_9))
+
+
+ 243
+ 533
+ 80
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_10))
+
+
+ 243
+ 533
+ 88
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_11))
+
+
+ 243
+ 533
+ 96
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_12))
+
+
+ 243
+ 533
+ 104
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_13))
+
+
+ 243
+ 533
+ 112
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_14))
+
+
+ 243
+ 533
+ 120
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_15))
+
+
+ 243
+ 533
+ 128
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_16))
+
+
+ 243
+ 533
+ 136
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_17))
+
+
+ 243
+ 533
+ 144
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_18))
+
+
+ 243
+ 533
+ 152
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_19))
+
+
+ 243
+ 533
+ 160
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_20))
+
+
+ 243
+ 533
+ 168
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_21))
+
+
+ 243
+ 533
+ 176
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_22))
+
+
+ 243
+ 533
+ 184
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_23))
+
+
+ 243
+ 533
+ 192
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_24))
+
+
+ 243
+ 533
+ 200
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_25))
+
+
+ 243
+ 533
+ 208
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_26))
+
+
+ 243
+ 533
+ 216
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_27))
+
+
+ 243
+ 533
+ 224
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_28))
+
+
+ 243
+ 533
+ 232
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_29))
+
+
+ 243
+ 533
+ 240
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_30))
+
+
+ 243
+ 533
+ 248
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_31))
+
+
+ 243
+ 533
+ 256
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_32))
+
+
+ 243
+ 533
+ 264
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_33))
+
+
+ 243
+ 533
+ 272
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_34))
+
+
+ 243
+ 533
+ 280
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_35))
+
+
+ 243
+ 533
+ 288
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_36))
+
+
+ 243
+ 533
+ 296
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_37))
+
+
+ 243
+ 533
+ 304
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_38))
+
+
+ 243
+ 533
+ 312
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_39))
+
+
+ 243
+ 533
+ 320
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_40))
+
+
+ 243
+ 533
+ 328
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_41))
+
+
+ 243
+ 533
+ 336
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_42))
+
+
+ 243
+ 533
+ 344
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_43))
+
+
+ 243
+ 533
+ 352
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_44))
+
+
+ 243
+ 533
+ 360
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_45))
+
+
+ 243
+ 533
+ 368
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_46))
+
+
+ 243
+ 533
+ 376
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_47))
+
+
+ 243
+ 533
+ 384
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_48))
+
+
+ 243
+ 533
+ 392
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_49))
+
+
+ 243
+ 533
+ 400
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_50))
+
+
+ 243
+ 533
+ 408
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_51))
+
+
+ 243
+ 533
+ 416
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_52))
+
+
+ 243
+ 533
+ 424
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_53))
+
+
+ 243
+ 533
+ 432
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_54))
+
+
+ 243
+ 533
+ 440
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_55))
+
+
+ 243
+ 533
+ 448
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_56))
+
+
+ 243
+ 533
+ 456
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_57))
+
+
+ 243
+ 533
+ 464
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_58))
+
+
+ 243
+ 533
+ 472
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_59))
+
+
+ 243
+ 533
+ 480
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_60))
+
+
+ 243
+ 533
+ 488
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_61))
+
+
+ 243
+ 533
+ 496
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_62))
+
+
+ 243
+ 533
+ 504
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_63))
+
+
+ 243
+ 533
+ 512
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_64))
+
+
+ 243
+ 533
+ 520
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_65))
+
+
+ 243
+ 533
+ 528
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_66))
+
+
+ 243
+ 533
+ 536
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_67))
+
+
+ 243
+ 533
+ 544
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_68))
+
+
+ 243
+ 533
+ 552
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_69))
+
+
+ 243
+ 533
+ 560
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_70))
+
+
+ 243
+ 533
+ 568
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_71))
+
+
+ 243
+ 533
+ 576
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_72))
+
+
+ 243
+ 533
+ 584
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_73))
+
+
+ 243
+ 533
+ 592
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_74))
+
+
+ 243
+ 533
+ 600
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_75))
+
+
+ 243
+ 533
+ 608
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_76))
+
+
+ 243
+ 533
+ 616
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_77))
+
+
+ 243
+ 533
+ 624
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_78))
+
+
+ 243
+ 533
+ 632
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_79))
+
+
+ 243
+ 533
+ 640
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_80))
+
+
+ 243
+ 533
+ 648
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_81))
+
+
+ 243
+ 533
+ 656
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_82))
+
+
+ 243
+ 533
+ 664
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_83))
+
+
+ 243
+ 533
+ 672
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_84))
+
+
+ 243
+ 533
+ 680
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_85))
+
+
+ 243
+ 533
+ 688
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_86))
+
+
+ 243
+ 533
+ 696
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_87))
+
+
+ 243
+ 533
+ 704
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_88))
+
+
+ 243
+ 533
+ 712
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_89))
+
+
+ 243
+ 533
+ 720
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_90))
+
+
+ 243
+ 533
+ 728
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_91))
+
+
+ 243
+ 533
+ 736
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_92))
+
+
+ 243
+ 533
+ 744
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_93))
+
+
+ 243
+ 533
+ 752
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_94))
+
+
+ 243
+ 533
+ 760
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_95))
+
+
+ 243
+ 533
+ 768
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_96))
+
+
+ 243
+ 533
+ 776
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_97))
+
+
+ 243
+ 533
+ 784
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_98))
+
+
+ 243
+ 533
+ 792
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_99))
+
+
+ 243
+ 533
+ 794
+ 69
+ progress.png
+ !IsEmpty(Window(Home).Property(update_percent_100))
+
+
+ update header
+ 500
+ 150
+ 550
+ 300
+ font13
+
+ left
+ top
+ $INFO[Window(Home).Property(update_header_color)]
+
+
+ update text
+ 500
+ 200
+ 550
+ 300
+ font13
+
+ left
+ top
+ true
+ $INFO[Window(Home).Property(update_main_color)]
+
+
+
\ No newline at end of file
diff --git a/script.module.python.koding.aio/resources/skins/Default/720p/Text.xml b/script.module.python.koding.aio/resources/skins/Default/720p/Text.xml
new file mode 100644
index 0000000..8533ae4
--- /dev/null
+++ b/script.module.python.koding.aio/resources/skins/Default/720p/Text.xml
@@ -0,0 +1,98 @@
+
+
+ 100
+
+ pos_x
+ pos_y
+
+ WindowOpen
+ WindowClose
+
+
+ 0
+ 0
+ dialog_width
+ dialog_height
+ PK_Fanart
+
+
+ Dialog Header image
+ 40
+ 16
+ text_width
+ 40
+ dialogheader.png
+
+
+ header label
+ 40
+ 20
+ text_width
+ 30
+ font13_title
+
+ center
+ center
+ PK_Header_Color
+ FF000000
+
+
+ Close Window button
+ 0
+ 15
+ 64
+ 32
+
+ -
+ PreviousMenu
+ DialogCloseButton-focus.png
+ DialogCloseButton.png
+ 100
+ 100
+ 100
+ 100
+ system.getbool(input.enablemouse)
+
+
+
+ Icon image
+ PK_I_X
+ 65
+ 150
+ 150
+ PK_Icon
+
+
+ terms details
+ 30
+ 75
+ text_width
+ text_height
+
+ font13
+ justify
+ PK_Text_Color
+ black
+ 99
+
+
+ scroll_pos
+ 73
+ 25
+ text_height
+ ScrollBarV.png
+ ScrollBarV_bar.png
+ ScrollBarV_bar_focus.png
+ ScrollBarNib.png
+ ScrollBarNib.png
+ 101
+ 100
+ 100
+ 98
+ true
+ vertical
+
+
+
+
+
\ No newline at end of file
diff --git a/script.module.python.koding.aio/resources/skins/Default/media/DialogBack.png b/script.module.python.koding.aio/resources/skins/Default/media/DialogBack.png
new file mode 100644
index 0000000..bc8a793
Binary files /dev/null and b/script.module.python.koding.aio/resources/skins/Default/media/DialogBack.png differ
diff --git a/script.module.python.koding.aio/resources/skins/Default/media/DialogCloseButton-focus.png b/script.module.python.koding.aio/resources/skins/Default/media/DialogCloseButton-focus.png
new file mode 100644
index 0000000..c5ec717
Binary files /dev/null and b/script.module.python.koding.aio/resources/skins/Default/media/DialogCloseButton-focus.png differ
diff --git a/script.module.python.koding.aio/resources/skins/Default/media/DialogCloseButton.png b/script.module.python.koding.aio/resources/skins/Default/media/DialogCloseButton.png
new file mode 100644
index 0000000..89a0ef4
Binary files /dev/null and b/script.module.python.koding.aio/resources/skins/Default/media/DialogCloseButton.png differ
diff --git a/script.module.python.koding.aio/resources/skins/Default/media/dialogheader.png b/script.module.python.koding.aio/resources/skins/Default/media/dialogheader.png
new file mode 100644
index 0000000..8deb22a
Binary files /dev/null and b/script.module.python.koding.aio/resources/skins/Default/media/dialogheader.png differ
diff --git a/script.module.python.koding.aio/resources/skins/Default/media/progress.png b/script.module.python.koding.aio/resources/skins/Default/media/progress.png
new file mode 100644
index 0000000..68e5cd8
Binary files /dev/null and b/script.module.python.koding.aio/resources/skins/Default/media/progress.png differ
diff --git a/script.module.python.koding.aio/resources/skins/Default/media/scrollbarnib.png b/script.module.python.koding.aio/resources/skins/Default/media/scrollbarnib.png
new file mode 100644
index 0000000..0858c19
Binary files /dev/null and b/script.module.python.koding.aio/resources/skins/Default/media/scrollbarnib.png differ
diff --git a/script.module.python.koding.aio/resources/skins/Default/media/scrollbarv.png b/script.module.python.koding.aio/resources/skins/Default/media/scrollbarv.png
new file mode 100644
index 0000000..129cfcd
Binary files /dev/null and b/script.module.python.koding.aio/resources/skins/Default/media/scrollbarv.png differ
diff --git a/script.module.python.koding.aio/resources/skins/Default/media/scrollbarv_bar.png b/script.module.python.koding.aio/resources/skins/Default/media/scrollbarv_bar.png
new file mode 100644
index 0000000..7e96b52
Binary files /dev/null and b/script.module.python.koding.aio/resources/skins/Default/media/scrollbarv_bar.png differ
diff --git a/script.module.python.koding.aio/resources/skins/Default/media/scrollbarv_bar_focus.png b/script.module.python.koding.aio/resources/skins/Default/media/scrollbarv_bar_focus.png
new file mode 100644
index 0000000..6007f4e
Binary files /dev/null and b/script.module.python.koding.aio/resources/skins/Default/media/scrollbarv_bar_focus.png differ
diff --git a/script.module.python.koding.aio/resources/skins/Default/media/separator.png b/script.module.python.koding.aio/resources/skins/Default/media/separator.png
new file mode 100644
index 0000000..73405c4
Binary files /dev/null and b/script.module.python.koding.aio/resources/skins/Default/media/separator.png differ
diff --git a/script.module.python.koding.aio/resources/skins/Default/media/transparent_box.png b/script.module.python.koding.aio/resources/skins/Default/media/transparent_box.png
new file mode 100644
index 0000000..db80392
Binary files /dev/null and b/script.module.python.koding.aio/resources/skins/Default/media/transparent_box.png differ
diff --git a/script.module.python.koding.aio/resources/skins/Default/media/update.png b/script.module.python.koding.aio/resources/skins/Default/media/update.png
new file mode 100644
index 0000000..415a46a
Binary files /dev/null and b/script.module.python.koding.aio/resources/skins/Default/media/update.png differ
diff --git a/script.module.python.koding.aio/resources/skins/Default/media/whitebg.jpg b/script.module.python.koding.aio/resources/skins/Default/media/whitebg.jpg
new file mode 100644
index 0000000..50f2126
Binary files /dev/null and b/script.module.python.koding.aio/resources/skins/Default/media/whitebg.jpg differ
diff --git a/script.module.python.koding.aio/resources/update.png b/script.module.python.koding.aio/resources/update.png
new file mode 100644
index 0000000..56f80f4
Binary files /dev/null and b/script.module.python.koding.aio/resources/update.png differ
diff --git a/zips/addons.xml b/zips/addons.xml
index 0cc6190..426689e 100644
--- a/zips/addons.xml
+++ b/zips/addons.xml
@@ -1,263 +1,289 @@
-
-
-
-
-
-
-
- audio
-
-
- Kiss FM UK - Live Stream
- KissFMUK.com
-
- English
- all
-
-
- http://www.kissfmuk.com
- oli@whitenoisehq.co.uk
-
-
-
-
-
-
-
-
-
- audio
-
-
- Rinse.FM - Live Stream
- RinseFM live from Brick Lane, London
-
- English
- all
-
-
- http://rinse.fm
- oli@whitenoisehq.co.uk
-
-
-
-
-
-
-
-
-
-
- video
-
-
- Talks and Presentations from the Black Hat USA, Europe and Asia
-
- all
-
- https://olipassey.me.uk
-
- tech,uk
-
-
-
-
-
-
-
-
-
-
-
- video
-
-
- Grab videos from my blog
-
- all
-
- https://olipassey.me.uk
-
- tech,uk
-
-
-
-
-
-
-
-
-
-
-
- video
-
-
- Talks and Presentations from the DEF CON Hacking Conference in Las Vegas
-
- all
-
- https://olipassey.me.uk
-
- tech,uk
-
-
-
-
-
-
-
-
-
-
-
- video
-
-
- Videos from Friends of the Earth
-
- all
-
- https://friendsoftheearth.uk
-
- tech,uk
-
-
- BETA
-
-
-
-
-
-
-
-
- video
-
-
- London Live - Live Stream
- LondonLive.co.uk
-
- English
- all
-
- http://forum.xbmc.org/showthread.php?tid=190935
- http://www.londonlive.co.uk
- oli@whitenoisehq.co.uk
-
-
-
-
-
-
-
-
-
-
- video
-
-
- Looking at code for scraping stuff from my blog
-
- all
-
- olipassey.me.uk
-
- tech,uk
-
-
-
-
-
-
-
-
-
-
- video
-
-
- White Noise HQ live on your TV!
- en
- White Noise HQ is no longer operating, the back-end domain has gone away and will not be back.
- all
- deprecated
-
-
-
-
-
-
- https://raw.githubusercontent.com/noobsandnerds/modules4all/master/zips/addons.xml
- https://raw.githubusercontent.com/noobsandnerds/modules4all/master/zips/addons.xml.md5
- https://raw.githubusercontent.com/noobsandnerds/modules4all/master/zips/
-
- https://raw.githubusercontent.com/OliPassey/repository.olipassey/master/zips/addons.xml
- https://raw.githubusercontent.com/OliPassey/repository.olipassey/master/zips/addons.xml.md5
- https://raw.githubusercontent.com/OliPassey/repository.olipassey/master/zips/
-
-
- Kodi Kontroller Required addons
- More info at olipassey.me.uk
- tech,uk
-
-
-
-
-
-
-
-
- executable
-
-
-
- Send CEC commands from JSON
- Call this addon from the xbmc json-rpc to run CEC commands through an appropriate adapter.
-accepted commands are 'activate', 'toggle' and 'standby'. Example:
-{"jsonrpc":"2.0","method":"Addons.ExecuteAddon",
-"params":{"addonid":"script.json-cec",
-"params":{"command":"activate"}},"id":1}
- all
-
- https://github.com/joshjowen
-
-
-
-
-
-
-
-
-
-
-
-
-
- executable
-
-
- all
- https://olipassey.me.uk
- Display LowerThird Notifications via JSON
- GNU GENERAL PUBLIC LICENSE. Version 3
-
- oli@olipassey.me.uk
- Displays lower third notifications
- This addon was forked from Lanik's Banners addon
-
- icon.png
- fanart.jpg
- resources/screenshot-01.jpg
- resources/screenshot-02.jpg
- resources/screenshot-03.jpg
- resources/banner.jpg
- resources/logo.png
-
-
-
-
+
+
+
+
+
+
+
+ audio
+
+
+ Kiss FM UK - Live Stream
+ KissFMUK.com
+
+ English
+ all
+
+
+ http://www.kissfmuk.com
+ oli@whitenoisehq.co.uk
+
+
+
+
+
+
+
+
+
+ audio
+
+
+ Rinse.FM - Live Stream
+ RinseFM live from Brick Lane, London
+
+ English
+ all
+
+
+ http://rinse.fm
+ oli@whitenoisehq.co.uk
+
+
+
+
+
+
+
+
+
+
+ video
+
+
+ Talks and Presentations from the Black Hat USA, Europe and Asia
+
+ all
+
+ https://olipassey.me.uk
+
+ tech,uk
+
+
+
+
+
+
+
+
+
+
+
+ video
+
+
+ Grab videos from my blog
+
+ all
+
+ https://olipassey.me.uk
+
+ tech,uk
+
+
+
+
+
+
+
+
+
+
+
+ video
+
+
+ Talks and Presentations from the DEF CON Hacking Conference in Las Vegas
+
+ all
+
+ https://olipassey.me.uk
+
+ tech,uk
+
+
+
+
+
+
+
+
+
+
+
+ video
+
+
+ Videos from Friends of the Earth
+
+ all
+
+ https://friendsoftheearth.uk
+
+ tech,uk
+
+
+ BETA
+
+
+
+
+
+
+
+
+ video
+
+
+ London Live - Live Stream
+ LondonLive.co.uk
+
+ English
+ all
+
+ http://forum.xbmc.org/showthread.php?tid=190935
+ http://www.londonlive.co.uk
+ oli@whitenoisehq.co.uk
+
+
+
+
+
+
+
+
+
+
+ video
+
+
+ Looking at code for scraping stuff from my blog
+
+ all
+
+ olipassey.me.uk
+
+ tech,uk
+
+
+
+
+
+
+
+
+
+
+ video
+
+
+ White Noise HQ live on your TV!
+ en
+ White Noise HQ is no longer operating, the back-end domain has gone away and will not be back.
+ all
+ deprecated
+
+
+
+
+
+
+ https://raw.githubusercontent.com/noobsandnerds/modules4all/master/zips/addons.xml
+ https://raw.githubusercontent.com/noobsandnerds/modules4all/master/zips/addons.xml.md5
+ https://raw.githubusercontent.com/noobsandnerds/modules4all/master/zips/
+
+ https://raw.githubusercontent.com/OliPassey/repository.olipassey/master/zips/addons.xml
+ https://raw.githubusercontent.com/OliPassey/repository.olipassey/master/zips/addons.xml.md5
+ https://raw.githubusercontent.com/OliPassey/repository.olipassey/master/zips/
+
+
+ Kodi Kontroller Required addons
+ More info at olipassey.me.uk
+ tech,uk
+
+
+
+
+
+
+
+
+ executable
+
+
+
+ Send CEC commands from JSON
+ Call this addon from the xbmc json-rpc to run CEC commands through an appropriate adapter.
+accepted commands are 'activate', 'toggle' and 'standby'. Example:
+{"jsonrpc":"2.0","method":"Addons.ExecuteAddon",
+"params":{"addonid":"script.json-cec",
+"params":{"command":"activate"}},"id":1}
+ all
+
+ https://github.com/joshjowen
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ all
+ Python Koding All In One
+ Python Koding AIO contains a bunch of time saving modules that allows for quick and simple development.
+
+
+ Creative Commons 4.0-NC-ND
+ http://totalrevolution.tv/forum
+ http://totalrevolution.tv
+ 1.0 Stable release
+ https://github.com/totalrevolution/python-koding/tree/master/script.module.python.koding.aio
+
+
+
+
+
+
+
+
+
+
+
+ executable
+
+
+ all
+ https://olipassey.me.uk
+ Display LowerThird Notifications via JSON
+ GNU GENERAL PUBLIC LICENSE. Version 3
+
+ oli@olipassey.me.uk
+ Displays lower third notifications
+ This addon was forked from Lanik's Banners addon
+
+ icon.png
+ fanart.jpg
+ resources/screenshot-01.jpg
+ resources/screenshot-02.jpg
+ resources/screenshot-03.jpg
+ resources/banner.jpg
+ resources/logo.png
+
+
+
+
diff --git a/zips/addons.xml.md5 b/zips/addons.xml.md5
index 48240c3..533dea6 100644
--- a/zips/addons.xml.md5
+++ b/zips/addons.xml.md5
@@ -1 +1 @@
-70f1e8dcbf99e4c8e7d1dea9f71a8753
\ No newline at end of file
+6d600c3cd03dbc0469221fa13bc41f78
\ No newline at end of file
diff --git a/zips/plugin.video.defconconf/addon.xml b/zips/plugin.video.defconconf/addon.xml
index 7786a05..2820607 100644
--- a/zips/plugin.video.defconconf/addon.xml
+++ b/zips/plugin.video.defconconf/addon.xml
@@ -1,5 +1,5 @@
-
+
diff --git a/zips/plugin.video.defconconf/plugin.video.defconconf-0.1.9.zip b/zips/plugin.video.defconconf/plugin.video.defconconf-0.1.9.zip
new file mode 100644
index 0000000..e5de002
Binary files /dev/null and b/zips/plugin.video.defconconf/plugin.video.defconconf-0.1.9.zip differ
diff --git a/zips/script.module.python.koding.aio/addon.xml b/zips/script.module.python.koding.aio/addon.xml
new file mode 100644
index 0000000..d3ec895
--- /dev/null
+++ b/zips/script.module.python.koding.aio/addon.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+ all
+ Python Koding All In One
+ Python Koding AIO contains a bunch of time saving modules that allows for quick and simple development.
+
+
+ Creative Commons 4.0-NC-ND
+ http://totalrevolution.tv/forum
+ http://totalrevolution.tv
+ 1.0 Stable release
+ https://github.com/totalrevolution/python-koding/tree/master/script.module.python.koding.aio
+
+
diff --git a/zips/script.module.python.koding.aio/fanart.jpg b/zips/script.module.python.koding.aio/fanart.jpg
new file mode 100644
index 0000000..57671dc
Binary files /dev/null and b/zips/script.module.python.koding.aio/fanart.jpg differ
diff --git a/zips/script.module.python.koding.aio/icon.png b/zips/script.module.python.koding.aio/icon.png
new file mode 100644
index 0000000..cbfc634
Binary files /dev/null and b/zips/script.module.python.koding.aio/icon.png differ
diff --git a/zips/script.module.python.koding.aio/script.module.python.koding.aio-1.0.zip b/zips/script.module.python.koding.aio/script.module.python.koding.aio-1.0.zip
new file mode 100644
index 0000000..2196b87
Binary files /dev/null and b/zips/script.module.python.koding.aio/script.module.python.koding.aio-1.0.zip differ