September 2018 Update

Added - Koding AIO to repo as addons depend on it
Updated - DefCon now has 2018 talks in
This commit is contained in:
Oli Passey
2018-09-17 19:54:29 +01:00
parent 9982a3f13f
commit f0bbdc56fb
53 changed files with 13094 additions and 282 deletions

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="plugin.video.defconconf" name="DEF CON Talks" version="0.1.8" provider-name="Oli Passey">
<addon id="plugin.video.defconconf" name="DEF CON Talks" version="0.1.9" provider-name="Oli Passey">
<requires>
<import addon="xbmc.python" version="2.19.0" />
<import addon="script.module.python.koding.aio" version="0.0.1"/>

View File

@@ -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]))
xbmcplugin.endOfDirectory(int(sys.argv[1]))

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="script.module.python.koding.aio"
name="Python Koding AIO"
version="1.0"
provider-name="Total Revolution">
<requires>
<import addon="xbmc.python" version="2.1.0"/>
<import addon="script.module.urlresolver" version="1.2.0"/>
<import addon="script.module.requests" version="2.3.0"/>
<import addon="script.module.beautifulsoup" version="2.1.31"/>
<import addon="script.module.unidecode" version="0.4.16"/>
</requires>
<extension point="xbmc.python.module" library="lib" />
<extension point="xbmc.addon.metadata">
<platform>all</platform>
<summary lang="en">Python Koding All In One</summary>
<description lang="en">Python Koding AIO contains a bunch of time saving modules that allows for quick and simple development.</description>
<disclaimer lang="en"></disclaimer>
<language></language>
<license>Creative Commons 4.0-NC-ND</license>
<forum>http://totalrevolution.tv/forum</forum>
<website>http://totalrevolution.tv</website>
<news>1.0 Stable release</news>
<source>https://github.com/totalrevolution/python-koding/tree/master/script.module.python.koding.aio</source>
</extension>
</addon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

View File

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

View File

@@ -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 = '<extension point="xbmc.service"'
for item in addons:
addon_path = os.path.join(ADDONS,item,'addon.xml')
content = Text_File(addon_path,'r')
if service_line in content:
if item not in service_addons:
service_addons.append(item)
if mode != 'list':
if not item in skip_service:
for line in content.splitlines():
if service_line in line:
if not (line.strip().startswith('<!--')) and (mode == 'disable'):
replace_line = '<!--%s-->'%line
Text_File(addon_path,'w',content.replace(line,replace_line))
break
elif line.strip().startswith('<!--') and mode == 'enable':
replace_line = line.replace(r'<!--','').replace(r'-->','')
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('<string>','')
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'<checksum>(.+?)</checksum>', 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)
#----------------------------------------------------------------

View File

@@ -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)
#----------------------------------------------------------------

View File

@@ -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/<your_addon_id>/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/<your_addon_id>/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/<your_addon_id>/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/<your_addon_id>/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/<your_addon_id>/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/<your_addon_id>/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

View File

@@ -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')
#----------------------------------------------------------------

File diff suppressed because it is too large Load Diff

View File

@@ -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 += '\
<control type="button" id="%s">\n\
<posx>%s</posx>\n\
<posy>%s</posy>\n\
<width>%s</width>\n\
<height>40</height>\n\
<label>%s</label>\n\
<texturefocus colordiffuse="%s%s">DialogBack.png</texturefocus>\n\
<texturenofocus colordiffuse="%s%s">DialogBack.png</texturenofocus>\n\
<font>font12_title</font>\n\
<textcolor>%s</textcolor>\n\
<focusedcolor>%s</focusedcolor>\n\
<align>center</align>\n\
<onleft>%s</onleft>\n\
<onright>%s</onright>\n\
<onup>%s</onup>\n\
<ondown>%s</ondown>\n\
</control>\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 -->',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 <sleep> 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
#----------------------------------------------------------------

View File

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

View File

@@ -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 <value>
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
#----------------------------------------------------------------

View File

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

View File

@@ -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('<br>','').replace('<br />','').replace('<br/>','')
my_string = my_string.replace('</p>','').replace('</div>','').replace('</class>','')
my_string = my_string.replace('&amp;','&')
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 <sublink>. 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 <item> 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 <item>
section contains <link>, <title> and <thumb> 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 '<th' in newtag:
count_tag = '<th'
else:
count_tag = '<td'
cells = newtag.count(count_tag)
rows = [rawdata[table]['td'][x:x+cells] for x in range(0, len(rawdata[0]['td']), cells)]
for row in rows:
my_dict = {}
for cell_name in contents:
my_dict[cell_name] = row[contents[cell_name]]
table_list.append(my_dict)
return table_list
else:
return {}
#----------------------------------------------------------------
# TUTORIAL #
def Split_Lines(raw_string, size):
"""
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))
~"""
final_list=[""]
for i in raw_string:
length = len(final_list)-1
if len(final_list[length]) < size:
final_list[length]+=i
else:
final_list += [i]
return final_list
#----------------------------------------------------------------
# TUTORIAL #
def Split_List(source, split_point, include='all'):
"""
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)
~"""
if include == 'all':
return [source[x:x+split_point] for x in range(0, len(source), split_point)]
elif Data_Type(include) == 'list':
mylists = [source[x:x+split_point] for x in range(0, len(source), split_point)]
return_list = []
for item in mylists:
mylist = []
for keep in include:
mylist.append(item[keep])
return_list.append(mylist)
return return_list
elif Data_Type(include) == 'int':
return [source[x:x+include] for x in range(0, len(source), split_point)]
#----------------------------------------------------------------
# TUTORIAL #
def String(code='', source=''):
"""
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)
~"""
import xbmcaddon
from addons import Caller
if source == '':
source = Caller()
if source != 'system':
addon_id = xbmcaddon.Addon(id=source)
mystring = addon_id.getLocalizedString(code)
else:
mystring = xbmc.getLocalizedString(code)
return mystring
#----------------------------------------------------------------
# TUTORIAL #
def Remove_Formatting(string, color=True, bold=True, italic=True, spaces=True, dots=True, dashes=True):
"""
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)))
~"""
import re
if color:
if '[COLOR' in string:
string = string.replace('[/COLOR]','')
colorlist = re.compile(r'\[COLOR(.+?)\]').findall(string)
for colors in colorlist:
string = string.replace('[COLOR%s]'%colors,'')
if spaces:
string = string.strip()
if bold:
string = string.replace('[B]','').replace('[/B]','')
if spaces:
string = string.strip()
if italic:
string = string.replace('[I]','').replace('[/I]','')
if spaces:
string = string.strip()
if dots:
while string.startswith('.'):
string = string[1:]
if spaces:
string = string.strip()
if dashes:
while string.startswith('-'):
string = string[1:]
if spaces:
string = string.strip()
if spaces:
string = string.strip()
return string

View File

@@ -0,0 +1,711 @@
# -*- 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 requests
import shutil
import xbmc
import xbmcgui
from guitools import Show_Busy
from systemtools import Last_Error
dp = xbmcgui.DialogProgress()
check_started = xbmc.translatePath('special://profile/addon_data/script.module.python.koding.aio/temp/playback_in_progress')
#----------------------------------------------------------------
# TUTORIAL #
def Check_Playback(ignore_dp=False,timeout=10):
"""
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 :(')
~"""
if not ignore_dp:
isdialog = True
counter = 1
# Check if the progress window is active and wait for playback
while isdialog and counter < 60:
if xbmc.getCondVisibility('Window.IsActive(progressdialog)'):
try:
if dp.iscanceled():
dp.close()
break
except:
pass
xbmc.log('### Current Window: %s' % xbmc.getInfoLabel('System.CurrentWindow'))
xbmc.log('### Current XML: %s' % xbmc.getInfoLabel('Window.Property(xmlfile)'))
xbmc.log('### Progress Dialog active, sleeping for %s seconds' % counter)
xbmc.sleep(1000)
if xbmc.getCondVisibility('Window.IsActive(progressdialog)') or (xbmc.getInfoLabel('Window.Property(xmlfile)') == 'DialogProgress.xml'):
isdialog = True
else:
isdialog = False
counter += 1
xbmc.log('counter: %s' % counter)
# Given the DialogProgress 10 seconds to finish and it's still up - time to close it
if counter >= 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('<br>','').replace('<br />','').replace('<br/>','')
line = line.replace('</p>','').replace('</div>','').replace('</class>','')
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('<br>','').replace('<br />','').replace('<br/>','')
line = line.replace('</p>','').replace('</div>','').replace('</class>','')
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()

View File

@@ -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/<addon_id>/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
#----------------------------------------------------------------

Binary file not shown.

Binary file not shown.

View File

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

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,521 @@
<fonts>
<fontset id="Default" idloc="31390" unicode="true">
<font>
<name>font10</name>
<filename>Roboto-Regular.ttf</filename>
<size>14</size>
</font>
<font>
<name>font12</name>
<filename>Roboto-Regular.ttf</filename>
<size>17</size>
</font>
<font>
<name>font13</name>
<filename>Roboto-Regular.ttf</filename>
<size>20</size>
</font>
<font>
<name>font14</name>
<filename>Roboto-Regular.ttf</filename>
<size>22</size>
</font>
<font>
<name>font16</name>
<filename>Roboto-Regular.ttf</filename>
<size>25</size>
</font>
<font>
<name>font30</name>
<filename>Roboto-Regular.ttf</filename>
<size>30</size>
</font>
<font>
<name>fontContextMenu</name>
<filename>Roboto-Regular.ttf</filename>
<size>18</size>
</font>
<font>
<name>font10_title</name>
<filename>Roboto-Bold.ttf</filename>
<size>12</size>
</font>
<font>
<name>font12_title</name>
<filename>Roboto-Bold.ttf</filename>
<size>17</size>
</font>
<font>
<name>font13_title</name>
<filename>Roboto-Bold.ttf</filename>
<size>20</size>
</font>
<font>
<name>font24_title</name>
<filename>Roboto-Bold.ttf</filename>
<size>24</size>
</font>
<font>
<name>font28_title</name>
<filename>Roboto-Bold.ttf</filename>
<size>28</size>
</font>
<font>
<name>font30_title</name>
<filename>Roboto-Bold.ttf</filename>
<size>30</size>
</font>
<font>
<name>font35_title</name>
<filename>Roboto-Bold.ttf</filename>
<size>35</size>
</font>
<font>
<name>font45caps_title</name>
<filename>Roboto-Bold.ttf</filename>
<size>45</size>
</font>
<font>
<name>font_MainMenu</name>
<filename>DejaVuSans-Bold-Caps.ttf</filename>
<size>35</size>
</font>
<font>
<name>WeatherTemp</name>
<filename>Roboto-Bold.ttf</filename>
<size>80</size>
</font>
<font>
<name>osd1</name>
<filename>LightCaps.ttf</filename>
<size>36</size>
</font>
<font>
<name>osd2</name>
<filename>LightCaps.ttf</filename>
<size>30</size>
</font>
<font>
<name>osdTitle</name>
<filename>Ubuntu-L.ttf</filename>
<size>36</size>
</font>
<font>
<name>rss</name>
<filename>Ubuntu-R.ttf</filename>
<size>22</size>
</font>
<font>
<name>tvg_font10</name>
<filename>Ubuntu-R.ttf</filename>
<size>16</size>
</font>
<font>
<name>tvg_font13</name>
<filename>Ubuntu-R.ttf</filename>
<size>22</size>
</font>
<font>
<name>tvg_font14</name>
<filename>Ubuntu-R.ttf</filename>
<size>20</size>
</font>
</fontset>
<fontset id="Large-Font" unicode="true">
<font>
<name>font10</name>
<filename>Roboto-Regular.ttf</filename>
<size>17</size>
</font>
<font>
<name>font12</name>
<filename>Roboto-Regular.ttf</filename>
<size>20</size>
</font>
<font>
<name>font13</name>
<filename>Roboto-Regular.ttf</filename>
<size>22</size>
</font>
<font>
<name>font14</name>
<filename>Roboto-Regular.ttf</filename>
<size>25</size>
</font>
<font>
<name>font16</name>
<filename>Roboto-Regular.ttf</filename>
<size>30</size>
</font>
<font>
<name>font30</name>
<filename>Roboto-Regular.ttf</filename>
<size>32</size>
</font>
<font>
<name>fontContextMenu</name>
<filename>Roboto-Regular.ttf</filename>
<size>20</size>
</font>
<font>
<name>font10_title</name>
<filename>Roboto-Bold.ttf</filename>
<size>17</size>
</font>
<font>
<name>font12_title</name>
<filename>Roboto-Bold.ttf</filename>
<size>20</size>
</font>
<font>
<name>font13_title</name>
<filename>Roboto-Bold.ttf</filename>
<size>24</size>
</font>
<font>
<name>font24_title</name>
<filename>Roboto-Bold.ttf</filename>
<size>28</size>
</font>
<font>
<name>font28_title</name>
<filename>Roboto-Bold.ttf</filename>
<size>30</size>
</font>
<font>
<name>font30_title</name>
<filename>Roboto-Bold.ttf</filename>
<size>32</size>
</font>
<font>
<name>font35_title</name>
<filename>Roboto-Bold.ttf</filename>
<size>36</size>
</font>
<font>
<name>font45caps_title</name>
<filename>Roboto-Bold.ttf</filename>
<size>48</size>
</font>
<font>
<name>font_MainMenu</name>
<filename>Roboto-Bold.ttf</filename>
<size>42</size>
</font>
<font>
<name>WeatherTemp</name>
<filename>Roboto-Bold.ttf</filename>
<size>80</size>
</font>
<font>
<name>osd1</name>
<filename>LightCaps.ttf</filename>
<size>36</size>
</font>
<font>
<name>osd2</name>
<filename>LightCaps.ttf</filename>
<size>30</size>
</font>
<font>
<name>osdTitle</name>
<filename>Ubuntu-L.ttf</filename>
<size>36</size>
</font>
<font>
<name>rss</name>
<filename>Ubuntu-R.ttf</filename>
<size>22</size>
</font>
<font>
<name>tvg_font10</name>
<filename>Ubuntu-R.ttf</filename>
<size>16</size>
</font>
<font>
<name>tvg_font13</name>
<filename>Ubuntu-R.ttf</filename>
<size>22</size>
</font>
<font>
<name>tvg_font14</name>
<filename>Ubuntu-R.ttf</filename>
<size>20</size>
</font>
</fontset>
<fontset id="DefaultNoCaps" idloc="31391" unicode="true">
<font>
<name>font10</name>
<filename>Roboto-Regular.ttf</filename>
<size>14</size>
</font>
<font>
<name>font12</name>
<filename>Roboto-Regular.ttf</filename>
<size>17</size>
</font>
<font>
<name>font13</name>
<filename>Roboto-Regular.ttf</filename>
<size>20</size>
</font>
<font>
<name>font14</name>
<filename>Roboto-Regular.ttf</filename>
<size>22</size>
</font>
<font>
<name>font16</name>
<filename>Roboto-Regular.ttf</filename>
<size>25</size>
</font>
<font>
<name>font30</name>
<filename>Roboto-Regular.ttf</filename>
<size>30</size>
</font>
<font>
<name>fontContextMenu</name>
<filename>Roboto-Regular.ttf</filename>
<size>18</size>
</font>
<font>
<name>font10_title</name>
<filename>Roboto-Bold.ttf</filename>
<size>12</size>
</font>
<font>
<name>font12_title</name>
<filename>Roboto-Bold.ttf</filename>
<size>17</size>
</font>
<font>
<name>font13_title</name>
<filename>Roboto-Bold.ttf</filename>
<size>20</size>
</font>
<font>
<name>font24_title</name>
<filename>Roboto-Bold.ttf</filename>
<size>24</size>
</font>
<font>
<name>font28_title</name>
<filename>Roboto-Bold.ttf</filename>
<size>28</size>
</font>
<font>
<name>font30_title</name>
<filename>Roboto-Bold.ttf</filename>
<size>30</size>
</font>
<font>
<name>font35_title</name>
<filename>Roboto-Bold.ttf</filename>
<size>35</size>
</font>
<font>
<name>font45caps_title</name>
<filename>Roboto-Bold.ttf</filename>
<size>45</size>
</font>
<font>
<name>font_MainMenu</name>
<filename>Roboto-Bold.ttf</filename>
<size>40</size>
</font>
<font>
<name>WeatherTemp</name>
<filename>Roboto-Bold.ttf</filename>
<size>80</size>
</font>
<font>
<name>osd1</name>
<filename>LightCaps.ttf</filename>
<size>36</size>
</font>
<font>
<name>osd2</name>
<filename>LightCaps.ttf</filename>
<size>30</size>
</font>
<font>
<name>osdTitle</name>
<filename>Ubuntu-L.ttf</filename>
<size>36</size>
</font>
<font>
<name>rss</name>
<filename>Ubuntu-R.ttf</filename>
<size>22</size>
</font>
<font>
<name>tvg_font10</name>
<filename>Ubuntu-R.ttf</filename>
<size>16</size>
</font>
<font>
<name>tvg_font13</name>
<filename>Ubuntu-R.ttf</filename>
<size>22</size>
</font>
<font>
<name>tvg_font14</name>
<filename>Ubuntu-R.ttf</filename>
<size>20</size>
</font>
</fontset>
<fontset id="Arial" idloc="31392" unicode="true">
<font>
<name>font10</name>
<filename>arial.ttf</filename>
<size>12</size>
</font>
<font>
<name>font12</name>
<filename>arial.ttf</filename>
<size>15</size>
</font>
<font>
<name>font13</name>
<filename>arial.ttf</filename>
<size>18</size>
</font>
<font>
<name>font14</name>
<filename>arial.ttf</filename>
<size>20</size>
</font>
<font>
<name>font16</name>
<filename>arial.ttf</filename>
<size>23</size>
</font>
<font>
<name>font30</name>
<filename>arial.ttf</filename>
<size>28</size>
</font>
<font>
<name>fontContextMenu</name>
<filename>arial.ttf</filename>
<size>16</size>
</font>
<font>
<name>font10_title</name>
<filename>arial.ttf</filename>
<size>10</size>
<style>bold</style>
</font>
<font>
<name>font12_title</name>
<filename>arial.ttf</filename>
<size>15</size>
<style>bold</style>
</font>
<font>
<name>font13_title</name>
<filename>arial.ttf</filename>
<size>18</size>
<style>bold</style>
</font>
<font>
<name>font24_title</name>
<filename>arial.ttf</filename>
<size>22</size>
<style>bold</style>
</font>
<font>
<name>font28_title</name>
<filename>arial.ttf</filename>
<size>26</size>
<style>bold</style>
</font>
<font>
<name>font30_title</name>
<filename>arial.ttf</filename>
<size>28</size>
<style>bold</style>
</font>
<font>
<name>font35_title</name>
<filename>arial.ttf</filename>
<size>33</size>
<style>bold</style>
</font>
<font>
<name>font45caps_title</name>
<filename>arial.ttf</filename>
<size>43</size>
<style>bold</style>
</font>
<font>
<name>font_MainMenu</name>
<filename>arial.ttf</filename>
<size>35</size>
<style>bold</style>
</font>
<font>
<name>WeatherTemp</name>
<filename>arial.ttf</filename>
<size>80</size>
<style>bold</style>
</font>
<font>
<name>osd1</name>
<filename>Arial.ttf</filename>
<size>36</size>
</font>
<font>
<name>osd2</name>
<filename>Arial.ttf</filename>
<size>30</size>
</font>
<font>
<name>osdTitle</name>
<filename>Arial.ttf</filename>
<size>36</size>
</font>
<font>
<name>rss</name>
<filename>Arial.ttf</filename>
<size>22</size>
</font>
<font>
<name>tvg_font10</name>
<filename>Arial.ttf</filename>
<size>16</size>
</font>
<font>
<name>tvg_font13</name>
<filename>Arial.ttf</filename>
<size>22</size>
</font>
<font>
<name>tvg_font14</name>
<filename>Arial.ttf</filename>
<size>20</size>
</font>
</fontset>
</fonts>

View File

@@ -0,0 +1,874 @@
<?xml version="1.0" encoding="utf-8"?>
<window>
<defaultcontrol always="true">100</defaultcontrol>
<coordinates>
<posx>0</posx>
<posy>0</posy>
</coordinates>
<animation effect="fade" time="250">WindowOpen</animation>
<animation effect="fade" time="250">WindowClose</animation>
<controls>
<control type="image">
<posx>0</posx>
<posy>0</posy>
<width>1280</width>
<height>720</height>
<texture>$INFO[Window(Home).Property(update_background)]</texture>
</control>
<control type="image">
<description>Update Circle</description>
<posx>200</posx>
<posy>130</posy>
<width>250</width>
<height>250</height>
<texture>$INFO[Window(Home).Property(update_icon)]</texture>
<animation effect="rotate" start="0" end="360" center="auto" time="3000" loop="true" condition="!IsEmpty(Window(Home).Property(update_spinner))">Conditional</animation>
</control>
<control type="label">
<description>Percent Text</description>
<posx>240</posx>
<posy>480</posy>
<width>800</width>
<height>50</height>
<align>center</align>
<aligny>center</aligny>
<font>font13</font>
<label>Status: $INFO[Window(Home).Property(update_percent)] %</label>
<textcolor>$INFO[Window(Home).Property(update_percent_color)]</textcolor>
<visible>!IsEmpty(Window(Home).Property(update_percent))</visible>
</control>
<control type="image">
<posx>240</posx>
<posy>530</posy>
<width>800</width>
<height>75</height>
<texture>transparent_box.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>8</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_1))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>16</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_2))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>24</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_3))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>32</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_4))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>40</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_5))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>48</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_6))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>56</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_7))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>64</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_8))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>72</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_9))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>80</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_10))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>88</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_11))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>96</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_12))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>104</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_13))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>112</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_14))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>120</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_15))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>128</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_16))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>136</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_17))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>144</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_18))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>152</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_19))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>160</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_20))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>168</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_21))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>176</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_22))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>184</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_23))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>192</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_24))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>200</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_25))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>208</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_26))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>216</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_27))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>224</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_28))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>232</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_29))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>240</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_30))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>248</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_31))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>256</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_32))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>264</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_33))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>272</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_34))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>280</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_35))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>288</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_36))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>296</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_37))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>304</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_38))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>312</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_39))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>320</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_40))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>328</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_41))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>336</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_42))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>344</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_43))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>352</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_44))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>360</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_45))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>368</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_46))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>376</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_47))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>384</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_48))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>392</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_49))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>400</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_50))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>408</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_51))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>416</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_52))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>424</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_53))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>432</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_54))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>440</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_55))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>448</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_56))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>456</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_57))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>464</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_58))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>472</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_59))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>480</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_60))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>488</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_61))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>496</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_62))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>504</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_63))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>512</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_64))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>520</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_65))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>528</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_66))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>536</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_67))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>544</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_68))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>552</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_69))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>560</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_70))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>568</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_71))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>576</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_72))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>584</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_73))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>592</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_74))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>600</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_75))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>608</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_76))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>616</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_77))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>624</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_78))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>632</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_79))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>640</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_80))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>648</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_81))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>656</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_82))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>664</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_83))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>672</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_84))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>680</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_85))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>688</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_86))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>696</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_87))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>704</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_88))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>712</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_89))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>720</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_90))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>728</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_91))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>736</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_92))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>744</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_93))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>752</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_94))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>760</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_95))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>768</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_96))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>776</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_97))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>784</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_98))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>792</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_99))</visible>
</control>
<control type="image">
<posx>243</posx>
<posy>533</posy>
<width>794</width>
<height>69</height>
<texture colordiffuse="$INFO[Window(Home).Property(update_bar_color)]">progress.png</texture>
<visible>!IsEmpty(Window(Home).Property(update_percent_100))</visible>
</control>
<control type="label">
<description>update header</description>
<left>500</left>
<top>150</top>
<width>550</width>
<height>300</height>
<font>font13</font>
<label>$INFO[Window(Home).Property(update_header)]</label>
<align>left</align>
<aligny>top</aligny>
<textcolor>$INFO[Window(Home).Property(update_header_color)]</textcolor>
</control>
<control type="label">
<description>update text</description>
<left>500</left>
<top>200</top>
<width>550</width>
<height>300</height>
<font>font13</font>
<label>$INFO[Window(Home).Property(update_main_text)]</label>
<align>left</align>
<aligny>top</aligny>
<wrapmultiline>true</wrapmultiline>
<textcolor>$INFO[Window(Home).Property(update_main_color)]</textcolor>
</control>
</controls>
</window>

View File

@@ -0,0 +1,98 @@
<?xml version="1.0" encoding="utf-8"?>
<window>
<defaultcontrol always="true">100</defaultcontrol>
<coordinates>
<posx>pos_x</posx>
<posy>pos_y</posy>
</coordinates>
<animation effect="fade" time="250">WindowOpen</animation>
<animation effect="fade" time="250">WindowClose</animation>
<controls>
<control type="image">
<posx>0</posx>
<posy>0</posy>
<width>dialog_width</width>
<height>dialog_height</height>
<texture border="40" colordiffuse="PK_TransparencyPK_Color">PK_Fanart</texture>
</control>
<control type="image">
<description>Dialog Header image</description>
<posx>40</posx>
<posy>16</posy>
<width>text_width</width>
<height>40</height>
<texture>dialogheader.png</texture>
</control>
<control type="label" id="1">
<description>header label</description>
<posx>40</posx>
<posy>20</posy>
<width>text_width</width>
<height>30</height>
<font>font13_title</font>
<label>$INFO[Window(Home).Property(PK_Header)]</label>
<align>center</align>
<aligny>center</aligny>
<textcolor>PK_Header_Color</textcolor>
<shadowcolor>FF000000</shadowcolor>
</control>
<control type="button" id="98">
<description>Close Window button</description>
<posx>0</posx>
<posy>15</posy>
<width>64</width>
<height>32</height>
<label>-</label>
<font>-</font>
<onclick>PreviousMenu</onclick>
<texturefocus>DialogCloseButton-focus.png</texturefocus>
<texturenofocus>DialogCloseButton.png</texturenofocus>
<onleft>100</onleft>
<onright>100</onright>
<onup>100</onup>
<ondown>100</ondown>
<visible>system.getbool(input.enablemouse)</visible>
</control>
<control type="group">
<control type="image">
<description>Icon image</description>
<posx>PK_I_X</posx>
<posy>65</posy>
<width>150</width>
<height>150</height>
<texture>PK_Icon</texture>
</control>
<control type="textbox" id="15">
<description>terms details</description>
<posx>30</posx>
<posy>75</posy>
<width>text_width</width>
<height>text_height</height>
<label>$INFO[Window(Home).Property(PK_Main_Text)]</label>
<font>font13</font>
<align>justify</align>
<textcolor>PK_Text_Color</textcolor>
<shadowcolor>black</shadowcolor>
<pagecontrol>99</pagecontrol>
</control>
<control type="scrollbar" id="99">
<posx>scroll_pos</posx>
<posy>73</posy>
<width>25</width>
<height>text_height</height>
<texturesliderbackground border="0,14,0,14">ScrollBarV.png</texturesliderbackground>
<texturesliderbar border="0,14,0,14">ScrollBarV_bar.png</texturesliderbar>
<texturesliderbarfocus border="0,14,0,14">ScrollBarV_bar_focus.png</texturesliderbarfocus>
<textureslidernib>ScrollBarNib.png</textureslidernib>
<textureslidernibfocus>ScrollBarNib.png</textureslidernibfocus>
<onleft>101</onleft>
<onright>100</onright>
<ondown>100</ondown>
<onup>98</onup>
<showonepage>true</showonepage>
<orientation>vertical</orientation>
</control>
<!-- buttons -->
</control>
</controls>
</window>

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 916 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 751 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 801 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 807 B

View File

@@ -1,263 +1,289 @@
<?xml version="1.0" encoding="UTF-8"?>
<addons>
<addon id="plugin.audio.kissfm" name="Kiss FM UK" version="0.5" provider-name="DoubleT">
<requires>
<import addon="xbmc.python" version="2.1.0"/>
</requires>
<extension point="xbmc.python.pluginsource" library="addon.py">
<provides>audio</provides>
</extension>
<extension point="xbmc.addon.metadata">
<summary lang="en">Kiss FM UK - Live Stream</summary>
<description lang="en">KissFMUK.com</description>
<disclaimer lang="en"></disclaimer>
<language>English</language>
<platform>all</platform>
<license></license>
<forum></forum>
<website>http://www.kissfmuk.com</website>
<email>oli@whitenoisehq.co.uk</email>
<source></source>
</extension>
</addon>
<addon id="plugin.audio.rinsefm" name="Rinse.FM" version="0.6" provider-name="DoubleT">
<requires>
<import addon="xbmc.python" version="2.1.0"/>
</requires>
<extension point="xbmc.python.pluginsource" library="addon.py">
<provides>audio</provides>
</extension>
<extension point="xbmc.addon.metadata">
<summary lang="en">Rinse.FM - Live Stream</summary>
<description lang="en">RinseFM live from Brick Lane, London</description>
<disclaimer lang="en"></disclaimer>
<language>English</language>
<platform>all</platform>
<license></license>
<forum></forum>
<website>http://rinse.fm</website>
<email>oli@whitenoisehq.co.uk</email>
<source></source>
</extension>
</addon>
<addon id="plugin.video.blackhatconf" name="Black Hat Conference" version="0.1.1" provider-name="Oli Passey">
<requires>
<import addon="xbmc.python" version="2.19.0" />
<import addon="script.module.python.koding.aio" version="0.0.1"/>
</requires>
<extension point="xbmc.python.pluginsource" library="default.py">
<provides>video</provides>
</extension>
<extension point="xbmc.addon.metadata">
<summary lang="en">Talks and Presentations from the Black Hat USA, Europe and Asia</summary>
<description lang="en"></description>
<platform>all</platform>
<forum></forum>
<website>https://olipassey.me.uk</website>
<news></news>
<genre>tech,uk</genre>
<video_guide></video_guide>
<video_preview></video_preview>
</extension>
</addon>
<addon id="plugin.video.blogscraper" name="olipassey.me.uk VidRepo" version="0.1.7" provider-name="Oli Passey">
<requires>
<import addon="xbmc.python" version="2.19.0" />
<import addon="script.module.python.koding.aio" version="0.0.1"/>
</requires>
<extension point="xbmc.python.pluginsource" library="default.py">
<provides>video</provides>
</extension>
<extension point="xbmc.addon.metadata">
<summary lang="en">Grab videos from my blog</summary>
<description lang="en"></description>
<platform>all</platform>
<forum></forum>
<website>https://olipassey.me.uk</website>
<news></news>
<genre>tech,uk</genre>
<video_guide></video_guide>
<video_preview></video_preview>
</extension>
</addon>
<addon id="plugin.video.defconconf" name="DEF CON Talks" version="0.1.8" provider-name="Oli Passey">
<requires>
<import addon="xbmc.python" version="2.19.0" />
<import addon="script.module.python.koding.aio" version="0.0.1"/>
</requires>
<extension point="xbmc.python.pluginsource" library="default.py">
<provides>video</provides>
</extension>
<extension point="xbmc.addon.metadata">
<summary lang="en">Talks and Presentations from the DEF CON Hacking Conference in Las Vegas</summary>
<description lang="en"></description>
<platform>all</platform>
<forum></forum>
<website>https://olipassey.me.uk</website>
<news></news>
<genre>tech,uk</genre>
<video_guide></video_guide>
<video_preview></video_preview>
</extension>
</addon>
<addon id="plugin.video.foeewni" name="Friends of the Earth - England, Wales, Northern Ireland" version="0.1" provider-name="Oli Passey">
<requires>
<import addon="xbmc.python" version="2.19.0" />
<import addon="script.module.python.koding.aio" version="0.0.1"/>
</requires>
<extension point="xbmc.python.pluginsource" library="default.py">
<provides>video</provides>
</extension>
<extension point="xbmc.addon.metadata">
<summary lang="en">Videos from Friends of the Earth</summary>
<description lang="en"></description>
<platform>all</platform>
<forum></forum>
<website>https://friendsoftheearth.uk</website>
<news></news>
<genre>tech,uk</genre>
<video_guide></video_guide>
<video_preview></video_preview>
<broken>BETA</broken>
</extension>
</addon>
<addon id="plugin.video.londonlive" name="London Live" version="0.4" provider-name="DoubleT">
<requires>
<import addon="xbmc.python" version="2.1.0"/>
</requires>
<extension point="xbmc.python.pluginsource" library="addon.py">
<provides>video</provides>
</extension>
<extension point="xbmc.addon.metadata">
<summary lang="en">London Live - Live Stream</summary>
<description lang="en">LondonLive.co.uk</description>
<disclaimer lang="en"></disclaimer>
<language>English</language>
<platform>all</platform>
<license></license>
<forum>http://forum.xbmc.org/showthread.php?tid=190935</forum>
<website>http://www.londonlive.co.uk</website>
<email>oli@whitenoisehq.co.uk</email>
<source></source>
</extension>
</addon>
<addon id="plugin.video.vidscraper" name="VidScraper2" version="0.0.2" provider-name="olipassey">
<requires>
<import addon="xbmc.python" version="2.19.0" />
<import addon="script.module.python.koding.aio" version="0.0.1"/>
</requires>
<extension point="xbmc.python.pluginsource" library="default.py">
<provides>video</provides>
</extension>
<extension point="xbmc.addon.metadata">
<summary lang="en">Looking at code for scraping stuff from my blog</summary>
<description lang="en"></description>
<platform>all</platform>
<forum></forum>
<website>olipassey.me.uk</website>
<news></news>
<genre>tech,uk</genre>
<video_guide></video_guide>
<video_preview></video_preview>
</extension>
</addon>
<addon id="plugin.video.wnhq"
name="WhiteNoiseHQ"
version="0.4"
provider-name="WNHQ">
<requires>
<import addon="xbmc.python" version="2.1.0"/>
</requires>
<extension point="xbmc.python.pluginsource"
library="default.py">
<provides>video</provides>
</extension>
<extension point="xbmc.addon.metadata">
<summary lang="en">White Noise HQ live on your TV!</summary>
<language>en</language>
<description lang="en">White Noise HQ is no longer operating, the back-end domain has gone away and will not be back.</description>
<platform>all</platform>
<broken>deprecated</broken>
</extension>
</addon>
<addon id="repository.olipassey" name="olipassey's repo" version="0.0.3" provider-name="Oli Passey">
<extension point="xbmc.addon.repository" name="olipassey's kodi repo">
<dir>
<info compressed="false">https://raw.githubusercontent.com/noobsandnerds/modules4all/master/zips/addons.xml</info>
<checksum>https://raw.githubusercontent.com/noobsandnerds/modules4all/master/zips/addons.xml.md5</checksum>
<datadir zip="true">https://raw.githubusercontent.com/noobsandnerds/modules4all/master/zips/</datadir>
</dir>
<info compressed="false">https://raw.githubusercontent.com/OliPassey/repository.olipassey/master/zips/addons.xml</info>
<checksum>https://raw.githubusercontent.com/OliPassey/repository.olipassey/master/zips/addons.xml.md5</checksum>
<datadir zip="true">https://raw.githubusercontent.com/OliPassey/repository.olipassey/master/zips/</datadir>
</extension>
<extension point="xbmc.addon.metadata">
<summary lang="en">Kodi Kontroller Required addons</summary>
<description lang="en">More info at olipassey.me.uk</description>
<genre>tech,uk</genre>
</extension>
</addon>
<addon id="script.json-cec" name="JSON-CEC" version="0.0.1" provider-name="joshjowen">
<requires>
<import addon="xbmc.python" version="2.1.0"/>
</requires>
<extension point="xbmc.python.script" library="script.py">
<provides>executable</provides>
</extension>
<extension point="xbmc.addon.metadata">
<language></language>
<summary lang="en">Send CEC commands from JSON</summary>
<description lang="en">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}</description>
<platform>all</platform>
<website></website>
<source>https://github.com/joshjowen</source>
<forum></forum>
<email></email>
</extension>
</addon>
<addon id="service.lowerthird" name="LowerThird" version="0.1.2" provider-name="Double T">
<requires>
<import addon="xbmc.python" version="2.1.0"/>
<import addon="script.module.pil" version="1.1.7"/>
</requires>
<extension point="xbmc.service" library="default.py" start="startup">
</extension>
<extension point="xbmc.python.script" library="standalone.py">
<provides>executable</provides>
</extension>
<extension point="xbmc.addon.metadata">
<platform>all</platform>
<website>https://olipassey.me.uk</website>
<summary lang="en">Display LowerThird Notifications via JSON</summary>
<license>GNU GENERAL PUBLIC LICENSE. Version 3</license>
<forum></forum>
<email>oli@olipassey.me.uk</email>
<description lang="en">Displays lower third notifications</description>
<news>This addon was forked from Lanik's Banners addon</news>
<assets>
<icon>icon.png</icon>
<fanart>fanart.jpg</fanart>
<screenshot>resources/screenshot-01.jpg</screenshot>
<screenshot>resources/screenshot-02.jpg</screenshot>
<screenshot>resources/screenshot-03.jpg</screenshot>
<banner>resources/banner.jpg</banner>
<logo>resources/logo.png</logo>
</assets>
</extension>
</addon>
</addons>
<?xml version="1.0" encoding="UTF-8"?>
<addons>
<addon id="plugin.audio.kissfm" name="Kiss FM UK" version="0.5" provider-name="DoubleT">
<requires>
<import addon="xbmc.python" version="2.1.0"/>
</requires>
<extension point="xbmc.python.pluginsource" library="addon.py">
<provides>audio</provides>
</extension>
<extension point="xbmc.addon.metadata">
<summary lang="en">Kiss FM UK - Live Stream</summary>
<description lang="en">KissFMUK.com</description>
<disclaimer lang="en"></disclaimer>
<language>English</language>
<platform>all</platform>
<license></license>
<forum></forum>
<website>http://www.kissfmuk.com</website>
<email>oli@whitenoisehq.co.uk</email>
<source></source>
</extension>
</addon>
<addon id="plugin.audio.rinsefm" name="Rinse.FM" version="0.6" provider-name="DoubleT">
<requires>
<import addon="xbmc.python" version="2.1.0"/>
</requires>
<extension point="xbmc.python.pluginsource" library="addon.py">
<provides>audio</provides>
</extension>
<extension point="xbmc.addon.metadata">
<summary lang="en">Rinse.FM - Live Stream</summary>
<description lang="en">RinseFM live from Brick Lane, London</description>
<disclaimer lang="en"></disclaimer>
<language>English</language>
<platform>all</platform>
<license></license>
<forum></forum>
<website>http://rinse.fm</website>
<email>oli@whitenoisehq.co.uk</email>
<source></source>
</extension>
</addon>
<addon id="plugin.video.blackhatconf" name="Black Hat Conference" version="0.1.1" provider-name="Oli Passey">
<requires>
<import addon="xbmc.python" version="2.19.0" />
<import addon="script.module.python.koding.aio" version="0.0.1"/>
</requires>
<extension point="xbmc.python.pluginsource" library="default.py">
<provides>video</provides>
</extension>
<extension point="xbmc.addon.metadata">
<summary lang="en">Talks and Presentations from the Black Hat USA, Europe and Asia</summary>
<description lang="en"></description>
<platform>all</platform>
<forum></forum>
<website>https://olipassey.me.uk</website>
<news></news>
<genre>tech,uk</genre>
<video_guide></video_guide>
<video_preview></video_preview>
</extension>
</addon>
<addon id="plugin.video.blogscraper" name="olipassey.me.uk VidRepo" version="0.1.7" provider-name="Oli Passey">
<requires>
<import addon="xbmc.python" version="2.19.0" />
<import addon="script.module.python.koding.aio" version="0.0.1"/>
</requires>
<extension point="xbmc.python.pluginsource" library="default.py">
<provides>video</provides>
</extension>
<extension point="xbmc.addon.metadata">
<summary lang="en">Grab videos from my blog</summary>
<description lang="en"></description>
<platform>all</platform>
<forum></forum>
<website>https://olipassey.me.uk</website>
<news></news>
<genre>tech,uk</genre>
<video_guide></video_guide>
<video_preview></video_preview>
</extension>
</addon>
<addon id="plugin.video.defconconf" name="DEF CON Talks" version="0.1.9" provider-name="Oli Passey">
<requires>
<import addon="xbmc.python" version="2.19.0" />
<import addon="script.module.python.koding.aio" version="0.0.1"/>
</requires>
<extension point="xbmc.python.pluginsource" library="default.py">
<provides>video</provides>
</extension>
<extension point="xbmc.addon.metadata">
<summary lang="en">Talks and Presentations from the DEF CON Hacking Conference in Las Vegas</summary>
<description lang="en"></description>
<platform>all</platform>
<forum></forum>
<website>https://olipassey.me.uk</website>
<news></news>
<genre>tech,uk</genre>
<video_guide></video_guide>
<video_preview></video_preview>
</extension>
</addon>
<addon id="plugin.video.foeewni" name="Friends of the Earth - England, Wales, Northern Ireland" version="0.1" provider-name="Oli Passey">
<requires>
<import addon="xbmc.python" version="2.19.0" />
<import addon="script.module.python.koding.aio" version="0.0.1"/>
</requires>
<extension point="xbmc.python.pluginsource" library="default.py">
<provides>video</provides>
</extension>
<extension point="xbmc.addon.metadata">
<summary lang="en">Videos from Friends of the Earth</summary>
<description lang="en"></description>
<platform>all</platform>
<forum></forum>
<website>https://friendsoftheearth.uk</website>
<news></news>
<genre>tech,uk</genre>
<video_guide></video_guide>
<video_preview></video_preview>
<broken>BETA</broken>
</extension>
</addon>
<addon id="plugin.video.londonlive" name="London Live" version="0.4" provider-name="DoubleT">
<requires>
<import addon="xbmc.python" version="2.1.0"/>
</requires>
<extension point="xbmc.python.pluginsource" library="addon.py">
<provides>video</provides>
</extension>
<extension point="xbmc.addon.metadata">
<summary lang="en">London Live - Live Stream</summary>
<description lang="en">LondonLive.co.uk</description>
<disclaimer lang="en"></disclaimer>
<language>English</language>
<platform>all</platform>
<license></license>
<forum>http://forum.xbmc.org/showthread.php?tid=190935</forum>
<website>http://www.londonlive.co.uk</website>
<email>oli@whitenoisehq.co.uk</email>
<source></source>
</extension>
</addon>
<addon id="plugin.video.vidscraper" name="VidScraper2" version="0.0.2" provider-name="olipassey">
<requires>
<import addon="xbmc.python" version="2.19.0" />
<import addon="script.module.python.koding.aio" version="0.0.1"/>
</requires>
<extension point="xbmc.python.pluginsource" library="default.py">
<provides>video</provides>
</extension>
<extension point="xbmc.addon.metadata">
<summary lang="en">Looking at code for scraping stuff from my blog</summary>
<description lang="en"></description>
<platform>all</platform>
<forum></forum>
<website>olipassey.me.uk</website>
<news></news>
<genre>tech,uk</genre>
<video_guide></video_guide>
<video_preview></video_preview>
</extension>
</addon>
<addon id="plugin.video.wnhq"
name="WhiteNoiseHQ"
version="0.4"
provider-name="WNHQ">
<requires>
<import addon="xbmc.python" version="2.1.0"/>
</requires>
<extension point="xbmc.python.pluginsource"
library="default.py">
<provides>video</provides>
</extension>
<extension point="xbmc.addon.metadata">
<summary lang="en">White Noise HQ live on your TV!</summary>
<language>en</language>
<description lang="en">White Noise HQ is no longer operating, the back-end domain has gone away and will not be back.</description>
<platform>all</platform>
<broken>deprecated</broken>
</extension>
</addon>
<addon id="repository.olipassey" name="olipassey's repo" version="0.0.3" provider-name="Oli Passey">
<extension point="xbmc.addon.repository" name="olipassey's kodi repo">
<dir>
<info compressed="false">https://raw.githubusercontent.com/noobsandnerds/modules4all/master/zips/addons.xml</info>
<checksum>https://raw.githubusercontent.com/noobsandnerds/modules4all/master/zips/addons.xml.md5</checksum>
<datadir zip="true">https://raw.githubusercontent.com/noobsandnerds/modules4all/master/zips/</datadir>
</dir>
<info compressed="false">https://raw.githubusercontent.com/OliPassey/repository.olipassey/master/zips/addons.xml</info>
<checksum>https://raw.githubusercontent.com/OliPassey/repository.olipassey/master/zips/addons.xml.md5</checksum>
<datadir zip="true">https://raw.githubusercontent.com/OliPassey/repository.olipassey/master/zips/</datadir>
</extension>
<extension point="xbmc.addon.metadata">
<summary lang="en">Kodi Kontroller Required addons</summary>
<description lang="en">More info at olipassey.me.uk</description>
<genre>tech,uk</genre>
</extension>
</addon>
<addon id="script.json-cec" name="JSON-CEC" version="0.0.1" provider-name="joshjowen">
<requires>
<import addon="xbmc.python" version="2.1.0"/>
</requires>
<extension point="xbmc.python.script" library="script.py">
<provides>executable</provides>
</extension>
<extension point="xbmc.addon.metadata">
<language></language>
<summary lang="en">Send CEC commands from JSON</summary>
<description lang="en">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}</description>
<platform>all</platform>
<website></website>
<source>https://github.com/joshjowen</source>
<forum></forum>
<email></email>
</extension>
</addon>
<addon id="script.module.python.koding.aio"
name="Python Koding AIO"
version="1.0"
provider-name="Total Revolution">
<requires>
<import addon="xbmc.python" version="2.1.0"/>
<import addon="script.module.urlresolver" version="1.2.0"/>
<import addon="script.module.requests" version="2.3.0"/>
<import addon="script.module.beautifulsoup" version="2.1.31"/>
<import addon="script.module.unidecode" version="0.4.16"/>
</requires>
<extension point="xbmc.python.module" library="lib" />
<extension point="xbmc.addon.metadata">
<platform>all</platform>
<summary lang="en">Python Koding All In One</summary>
<description lang="en">Python Koding AIO contains a bunch of time saving modules that allows for quick and simple development.</description>
<disclaimer lang="en"></disclaimer>
<language></language>
<license>Creative Commons 4.0-NC-ND</license>
<forum>http://totalrevolution.tv/forum</forum>
<website>http://totalrevolution.tv</website>
<news>1.0 Stable release</news>
<source>https://github.com/totalrevolution/python-koding/tree/master/script.module.python.koding.aio</source>
</extension>
</addon>
<addon id="service.lowerthird" name="LowerThird" version="0.1.2" provider-name="Double T">
<requires>
<import addon="xbmc.python" version="2.1.0"/>
<import addon="script.module.pil" version="1.1.7"/>
</requires>
<extension point="xbmc.service" library="default.py" start="startup">
</extension>
<extension point="xbmc.python.script" library="standalone.py">
<provides>executable</provides>
</extension>
<extension point="xbmc.addon.metadata">
<platform>all</platform>
<website>https://olipassey.me.uk</website>
<summary lang="en">Display LowerThird Notifications via JSON</summary>
<license>GNU GENERAL PUBLIC LICENSE. Version 3</license>
<forum></forum>
<email>oli@olipassey.me.uk</email>
<description lang="en">Displays lower third notifications</description>
<news>This addon was forked from Lanik's Banners addon</news>
<assets>
<icon>icon.png</icon>
<fanart>fanart.jpg</fanart>
<screenshot>resources/screenshot-01.jpg</screenshot>
<screenshot>resources/screenshot-02.jpg</screenshot>
<screenshot>resources/screenshot-03.jpg</screenshot>
<banner>resources/banner.jpg</banner>
<logo>resources/logo.png</logo>
</assets>
</extension>
</addon>
</addons>

View File

@@ -1 +1 @@
70f1e8dcbf99e4c8e7d1dea9f71a8753
6d600c3cd03dbc0469221fa13bc41f78

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="plugin.video.defconconf" name="DEF CON Talks" version="0.1.8" provider-name="Oli Passey">
<addon id="plugin.video.defconconf" name="DEF CON Talks" version="0.1.9" provider-name="Oli Passey">
<requires>
<import addon="xbmc.python" version="2.19.0" />
<import addon="script.module.python.koding.aio" version="0.0.1"/>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="script.module.python.koding.aio"
name="Python Koding AIO"
version="1.0"
provider-name="Total Revolution">
<requires>
<import addon="xbmc.python" version="2.1.0"/>
<import addon="script.module.urlresolver" version="1.2.0"/>
<import addon="script.module.requests" version="2.3.0"/>
<import addon="script.module.beautifulsoup" version="2.1.31"/>
<import addon="script.module.unidecode" version="0.4.16"/>
</requires>
<extension point="xbmc.python.module" library="lib" />
<extension point="xbmc.addon.metadata">
<platform>all</platform>
<summary lang="en">Python Koding All In One</summary>
<description lang="en">Python Koding AIO contains a bunch of time saving modules that allows for quick and simple development.</description>
<disclaimer lang="en"></disclaimer>
<language></language>
<license>Creative Commons 4.0-NC-ND</license>
<forum>http://totalrevolution.tv/forum</forum>
<website>http://totalrevolution.tv</website>
<news>1.0 Stable release</news>
<source>https://github.com/totalrevolution/python-koding/tree/master/script.module.python.koding.aio</source>
</extension>
</addon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB