################################################################################ # Copyright (C) 2019 drinfernoo # # # # This Program is free software; you can redistribute it and/or modify # # it under the terms of the GNU General Public License as published by # # the Free Software Foundation; either version 2, or (at your option) # # any later version. # # # # This Program is distributed in the hope that it will be useful, # # but WITHOUT ANY WARRANTY; without even the implied warranty of # # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # # GNU General Public License for more details. # # # # You should have received a copy of the GNU General Public License # # along with XBMC; see the file COPYING. If not, write to # # the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. # # http://www.gnu.org/copyleft/gpl.html # ################################################################################ import xbmc import xbmcgui import xbmcvfs import glob import os import re try: # Python 3 import zipfile except ImportError: # Python 2 from resources.libs import zipfile from sqlite3 import dbapi2 as database from datetime import datetime from resources.libs.common.config import CONFIG from resources.libs.common import logging from resources.libs.common import tools def addon_database(addon=None, state=1, array=False): dbfile = latest_db('Addons') dbfile = os.path.join(CONFIG.DATABASE, dbfile) installedtime = str(datetime.now())[:-7] if os.path.exists(dbfile): try: textdb = database.connect(dbfile) textexe = textdb.cursor() except Exception as e: logging.log("DB Connection Error: {0}".format(str(e)), level=xbmc.LOGERROR) return False else: return False if state == 2: try: textexe.execute("DELETE FROM installed WHERE addonID = ?", (addon,)) textdb.commit() textexe.close() except: logging.log("Error Removing {0} from DB".format(addon)) return True try: if not array: textexe.execute('INSERT or IGNORE into installed (addonID , enabled, installDate) VALUES (?,?,?)', (addon, state, installedtime,)) textexe.execute('UPDATE installed SET enabled = ? WHERE addonID = ? ', (state, addon,)) else: for item in addon: textexe.execute('INSERT or IGNORE into installed (addonID , enabled, installDate) VALUES (?,?,?)', (item, state, installedtime,)) textexe.execute('UPDATE installed SET enabled = ? WHERE addonID = ? ', (state, item,)) textdb.commit() textexe.close() except: logging.log("Erroring enabling addon: {0}".format(addon)) def latest_db(db): if db in CONFIG.DB_FILES: match = glob.glob(os.path.join(CONFIG.DATABASE, '{0}*.db'.format(db))) comp = '{0}(.+?).db'.format(db[1:]) highest = 0 for file in match: try: check = int(re.compile(comp).findall(file)[0]) except: check = 0 if highest < check: highest = check return '{0}{1}.db'.format(db, highest) else: return False def force_check_updates(auto=False, over=False): import time if not over: logging.log_notify(CONFIG.ADDONTITLE, '[COLOR {0}]Force Checking for Updates[/COLOR]'.format(CONFIG.COLOR2)) dbfile = latest_db('Addons') dbfile = os.path.join(CONFIG.DATABASE, dbfile) sqldb = database.connect(dbfile) sqlexe = sqldb.cursor() # force rollback all installed repos sqlexe.execute("UPDATE repo SET version = ?, checksum = ?, lastcheck = ?", ('', '', '',)) sqldb.commit() # trigger kodi to check them for updates xbmc.executebuiltin('UpdateAddonRepos') # wait until they have finished updating with tools.busy_dialog(): installed_repos = sqlexe.execute('SELECT addonID FROM repo') start_time = time.time() checked_time = 0 for repo in installed_repos.fetchall(): repo = repo[0] logging.log('Force checking {0}...'.format(repo), level=xbmc.LOGDEBUG) while checked_time < start_time: if time.time() >= start_time + 20: logging.log('{0} timed out during repo force check.'.format(repo), level=xbmc.LOGDEBUG) break lastcheck = sqlexe.execute('SELECT lastcheck FROM repo WHERE addonID = ?', (repo,)) if lastcheck: checked_time = lastcheck.fetchone()[0] checked_time = time.mktime(time.strptime(checked_time, '%Y-%m-%d %H:%M:%S')) if checked_time else 0 xbmc.sleep(1000) checked_time = 0 logging.log('{0} successfully force checked.'.format(repo), level=xbmc.LOGDEBUG) logging.log_notify('[COLOR {0}]{1}[/COLOR]'.format(CONFIG.COLOR1, CONFIG.ADDONTITLE), "[COLOR {0}]{1} successfully force checked.[/COLOR]".format(CONFIG.COLOR2, repo)) sqlexe.close() if auto: xbmc.executebuiltin('UpdateLocalAddons') def purge_db_file(name): logging.log('Purging DB {0}.'.format(name)) if os.path.exists(name): try: textdb = database.connect(name) textexe = textdb.cursor() except Exception as e: logging.log("DB Connection Error: {0}".format(str(e)), level=xbmc.LOGERROR) return False else: logging.log('{0} not found.'.format(name), level=xbmc.LOGERROR) return False textexe.execute("SELECT name FROM sqlite_master WHERE type = 'table'") for table in textexe.fetchall(): if table[0] == 'version': logging.log('Data from table `{0}` skipped.'.format(table[0])) else: try: textexe.execute("DELETE FROM {0}".format(table[0])) textdb.commit() logging.log('Data from table `{0}` cleared.'.format(table[0])) except Exception as e: logging.log("DB Remove Table `{0}` Error: {1}".format(table[0], str(e)), level=xbmc.LOGERROR) textexe.close() logging.log('{0} DB Purging Complete.'.format(name)) show = name.replace('\\', '/').split('/') logging.log_notify("[COLOR {0}]Purge Database[/COLOR]".format(CONFIG.COLOR1), "[COLOR {0}]{1} Complete[/COLOR]".format(CONFIG.COLOR2, show[len(show)-1])) def depends_list(plugin): addonxml = os.path.join(CONFIG.ADDONS, plugin, 'addon.xml') if os.path.exists(addonxml): match = tools.parse_dom(tools.read_from_file(addonxml), 'import', ret='addon') items = [] for depends in match: if not 'xbmc.python' in depends: items.append(depends) return items return [] def purge_db(): dialog = xbmcgui.Dialog() DB = [] display = [] for dirpath, dirnames, files in os.walk(CONFIG.HOME): import fnmatch for f in fnmatch.filter(files, '*.db'): if f != 'Thumbs.db': found = os.path.join(dirpath, f) DB.append(found) dir = found.replace('\\', '/').split('/') display.append('({0}) {1}'.format(dir[len(dir)-2], dir[len(dir)-1])) choice = dialog.multiselect("[COLOR {0}]Select DB File to Purge[/COLOR]".format(CONFIG.COLOR2), display) if choice is None or len(choice) == 0: logging.log_notify("[COLOR {0}]Purge Database[/COLOR]".format(CONFIG.COLOR1), "[COLOR {0}]Cancelled[/COLOR]".format(CONFIG.COLOR2)) else: for purge in choice: purge_db_file(DB[purge]) def kodi_17_fix(): from resources.libs.common import tools from resources.libs import update addonlist = glob.glob(os.path.join(CONFIG.ADDONS, '*/')) disabledAddons = [] for folder in sorted(addonlist, key=lambda x: x): addonxml = os.path.join(folder, 'addon.xml') if os.path.exists(addonxml): fold = folder.replace(CONFIG.ADDONS, '')[1:-1] aid = tools.parse_dom(tools.read_from_file(addonxml), 'addon', ret='id') try: if len(aid) > 0: addonid = aid[0] else: addonid = fold except: try: logging.log("{0} was disabled".format(aid[0])) disabledAddons.append(addonid) except: logging.log("Unable to enable: {0}".format(folder), level=xbmc.LOGERROR) if len(disabledAddons) > 0: addon_database(disabledAddons, 1, True) logging.log_notify(CONFIG.ADDONTITLE, "[COLOR {0}]Enabling Addons Complete![/COLOR]".format(CONFIG.COLOR2)) update.force_update() xbmc.executebuiltin("ReloadSkin()") def toggle_addon(id, value, over=None): from resources.libs.common import tools from xml.etree import ElementTree logging.log("Toggling {0}".format(id)) addonid = id addonxml = os.path.join(CONFIG.ADDONS, id, 'addon.xml') if os.path.exists(addonxml): root = ElementTree.parse(addonxml).getroot() tid = root.get('id') tname = root.get('name') tservice = root.find('extension').get('point') try: if len(tid) > 0: addonid = tid if tservice == 'xbmc.service': logging.log("We got a live one, stopping script: {0}".format(tid)) xbmc.executebuiltin('StopScript({0})'.format(os.path.join(CONFIG.ADDONS, addonid))) xbmc.executebuiltin('StopScript({0})'.format(addonid)) xbmc.executebuiltin('StopScript({0})'.format(os.path.join(CONFIG.ADDONS, addonid, tservice[0]))) xbmc.sleep(500) except: pass query = '{{"jsonrpc":"2.0", "method":"Addons.SetAddonEnabled","params":{{"addonid":"{0}","enabled":{1}}}, "id":1}}'.format(addonid, value) response = xbmc.executeJSONRPC(query) if 'error' in response and over is None: dialog = xbmcgui.Dialog() v = 'Enabling' if value == 'true' else 'Disabling' dialog.ok(CONFIG.ADDONTITLE, "[COLOR {0}]Error {1} [COLOR {2}]{3}[/COLOR]".format(CONFIG.COLOR2, v, CONFIG.COLOR1, id) + '\n' + "Check to make sure the add-on list is up to date and try again.[/COLOR]") def toggle_dependency(name, dp=None): from resources.libs.common import tools dep = os.path.join(CONFIG.ADDONS, name, 'addon.xml') if os.path.exists(dep): match = tools.parse_dom(tools.read_from_file(dep), 'import', ret='addon') for depends in match: if 'xbmc.python' not in depends: dependspath = os.path.join(CONFIG.ADDONS, depends) if dp is not None: dp.update("", "Checking Dependency [COLOR yellow]{0}[/COLOR] for [COLOR yellow]{1}[/COLOR]".format(depends, name), "") if os.path.exists(dependspath): toggle_addon(name, 'true') xbmc.sleep(100) # NOT CURRENTLY IN USE, BROKEN DUE TO DEAD NaN LINK # def toggle_adult(): # from resources.libs import gui # from resources.libs.common import tools # # do = gui.DIALOG.yesno(CONFIG.ADDONTITLE, # "[COLOR {0}]Would you like to [COLOR {1}]Enable[/COLOR] or [COLOR {2}]Disable[/COLOR] all Adult add-ons?[/COLOR]".format(CONFIG.COLOR2, CONFIG.COLOR1, CONFIG.COLOR1), # yeslabel="[B][COLOR springgreen]Enable[/COLOR][/B]", # nolabel="[B][COLOR red]Disable[/COLOR][/B]") # state = 'true' if do == 1 else 'false' # goto = 'Enabling' if do == 1 else 'Disabling' # link = tools.open_url('http://noobsandnerds.com/TI/AddonPortal/adult.php').replace('\n', '').replace('\r', '').replace('\t', '') # list = re.compile('i="(.+?)"').findall(link) # found = [] # for item in list: # fold = os.path.join(CONFIG.ADDONS, item) # if os.path.exists(fold): # found.append(item) # toggle_addon(item, state, True) # logging.log("[Toggle Adult] {0} {1}".format(goto, item)) # if len(found) > 0: # if gui.DIALOG.yesno(CONFIG.ADDONTITLE, # "[COLOR {0}]Would you like to view a list of the add-ons that where {1}?[/COLOR]".format(CONFIG.COLOR2, goto.replace('ing', 'ed')), # yeslabel="[B][COLOR springgreen]View List[/COLOR][/B]", # nolabel="[B][COLOR red]Cancel[/COLOR][/B]"): # editlist = '[CR]'.join(found) # gui.show_text_box(CONFIG.ADDONTITLE, # "[COLOR {0}]Here are a list of the add-ons that where {1} for Adult Content:[/COLOR][CR][CR][COLOR {2}]{3}[/COLOR]".format(CONFIG.COLOR1, goto.replace('ing', 'ed'), CONFIG.COLOR2, editlist)) # else: # logging.log_notify("[COLOR {0}]{1}[/COLOR]".format(CONFIG.COLOR1, CONFIG.ADDONTITLE), # "[COLOR {0}[COLOR {1}]{2}[/COLOR] Adult Addons {3}[/COLOR]".format(CONFIG.COLOR2, CONFIG.COLOR1, count, goto.replace('ing', 'ed'))) # from resources.libs import update # update.force_update(True) # else: # logging.log_notify("[COLOR {0}]{1}[/COLOR]".format(CONFIG.COLOR1, CONFIG.ADDONTITLE), # "[COLOR {0}]No Adult Addons Found[/COLOR]".format(CONFIG.COLOR2)) def create_temp(plugin): from resources.libs.common import tools temp = os.path.join(CONFIG.PLUGIN, 'resources', 'tempaddon.xml') r = tools.read_from_file(temp) plugdir = os.path.join(CONFIG.ADDONS, plugin) if not os.path.exists(plugdir): os.makedirs(plugdir) tools.write_to_file(os.path.join(plugdir, 'addon.xml'), r.replace('testid', plugin).replace('testversion', '0.0.1')) logging.log("{0}: wrote addon.xml".format(plugin)) def fix_metas(): from resources.libs.common import tools idlist = [] for item in idlist: fold = os.path.join(CONFIG.ADDOND, item) if os.path.exists(fold): storage = os.path.join(fold, '.storage') if os.path.exists(storage): tools.clean_house(storage) tools.remove_folder(storage) # def hide_password(): # from resources.libs.common import tools # from resources.libs.common import logging # # dialog = xbmcgui.Dialog() # # if dialog.yesno(CONFIG.ADDONTITLE, # "[COLOR {0}]Would you like to [COLOR {1}]hide[/COLOR] all passwords when typing in the add-on settings menus?[/COLOR]".format(CONFIG.COLOR2), # yeslabel="[B][COLOR springgreen]Hide Passwords[/COLOR][/B]", # nolabel="[B][COLOR red]No Cancel[/COLOR][/B]"): # count = 0 # for folder in glob.glob(os.path.join(CONFIG.ADDONS, '*/')): # sett = os.path.join(folder, 'resources', 'settings.xml') # if os.path.exists(sett): # f = tools.read_from_file(sett) # match = tools.parse_dom(f, 'addon', ret='id') # for line in match: # if 'pass' in line: # if 'option="hidden"' not in line: # try: # change = line.replace('/', 'option="hidden" /') # f.replace(line, change) # count += 1 # logging.log("[Hide Passwords] found in {0} on {1}".format(sett.replace(CONFIG.HOME, ''), line)) # except: # pass # tools.write_to_file(sett, f) # logging.log_notify("[COLOR {0}]Hide Passwords[/COLOR]".format(CONFIG.COLOR1), # "[COLOR {0}]{1} items changed[/COLOR]".format(CONFIG.COLOR2, count)) # logging.log("[Hide Passwords] {0} items changed".format(count)) # else: # logging.log("[Hide Passwords] Cancelled") # # # def unhide_password(): # from resources.libs.common import tools # from resources.libs.common import logging # # dialog = xbmcgui.Dialog() # # if dialog.yesno(CONFIG.ADDONTITLE, # "[COLOR {0}]Would you like to [COLOR {1}]unhide[/COLOR] all passwords when typing in the add-on settings menus?[/COLOR]".format(CONFIG.COLOR2, CONFIG.COLOR1), # yeslabel="[B][COLOR springgreen]Unhide Passwords[/COLOR][/B]", # nolabel="[B][COLOR red]No Cancel[/COLOR][/B]"): # count = 0 # for folder in glob.glob(os.path.join(CONFIG.ADDONS, '*/')): # sett = os.path.join(folder, 'resources', 'settings.xml') # if os.path.exists(sett): # f = tools.read_from_file(sett) # match = tools.parse_dom(f, 'addon', ret='id') # for line in match: # if 'pass' in line: # if 'option="hidden"' in line: # try: # change = line.replace('option="hidden"', '') # f.replace(line, change) # count += 1 # logging.log("[Unhide Passwords] found in {0} on {1}".format(sett.replace(CONFIG.HOME, ''), line)) # except: # pass # tools.write_to_file(sett, f) # logging.log_notify("[COLOR {0}]Unhide Passwords[/COLOR]".format(CONFIG.COLOR1), # "[COLOR {0}]{1} items changed[/COLOR]".format(CONFIG.COLOR2, count)) # logging.log("[Unhide Passwords] {0} items changed".format(count)) # else: # logging.log("[Unhide Passwords] Cancelled") def fix_update(): if os.path.exists(os.path.join(CONFIG.USERDATA, 'autoexec.py')): temp = os.path.join(CONFIG.USERDATA, 'autoexec_temp.py') if os.path.exists(temp): xbmcvfs.delete(temp) xbmcvfs.rename(os.path.join(CONFIG.USERDATA, 'autoexec.py'), temp) xbmcvfs.copy(os.path.join(CONFIG.PLUGIN, 'resources', 'libs', 'autoexec.py'), os.path.join(CONFIG.USERDATA, 'autoexec.py')) dbfile = os.path.join(CONFIG.DATABASE, latest_db('Addons')) try: os.remove(dbfile) except: logging.log("Unable to remove {0}, Purging DB".format(dbfile)) purge_db_file(dbfile) from resources.libs.common import tools tools.kill_kodi(over=True) def grab_addons(path): zfile = zipfile.ZipFile(path, allowZip64=True) addonlist = [] for item in zfile.infolist(): if str(item.filename).find('addon.xml') == -1: continue info = str(item.filename).split('/') if not info[-2] in addonlist: addonlist.append(info[-2]) return addonlist def find_binary_addons(addon='all'): from xml.etree import ElementTree dialog = xbmcgui.Dialog() logging.log('Checking {} for platform-dependence...'.format(addon), level=xbmc.LOGDEBUG) if addon == 'all': addonfolders = glob.iglob(os.path.join(CONFIG.ADDONS, '*/')) addonids = [] addonnames = [] for folder in addonfolders: foldername = os.path.split(folder[:-1])[1] if foldername in CONFIG.EXCLUDES: continue elif foldername in CONFIG.DEFAULTPLUGINS: continue elif foldername == 'packages': continue xml = os.path.join(folder, 'addon.xml') if os.path.exists(xml): root = ElementTree.parse(xml).getroot() addonid = root.get('id') addonname = root.get('name') extension = root.find('extension') try: ext_attrs = extension.keys() except: continue for attr in ext_attrs: if attr.startswith('library_'): try: addonnames.append(addonname) addonids.append(addonid) except: pass dialog.ok(CONFIG.ADDONTITLE, "[COLOR {0}]Found [COLOR {1}]{2}[/COLOR] platform-specific addons installed:[/COLOR]".format(CONFIG.COLOR2, CONFIG.COLOR1, len(addonnames)), "[COLOR {0}]{1}[/COLOR]".format(CONFIG.COLOR1, addonnames)) return addonids, addonnames else: if addon in CONFIG.EXCLUDES: return None, None elif addon in CONFIG.DEFAULTPLUGINS: return None, None xml = os.path.join(CONFIG.ADDONS, addon, 'addon.xml') if os.path.exists(xml): logging.log('Checking {0}'.format(xml), level=xbmc.LOGINFO) root = ElementTree.parse(xml).getroot() addonid = root.get('id') addonname = root.get('name') extension = root.find('extension') try: ext_attrs = extension.keys() except: return None, None for attr in ext_attrs: if attr.startswith('library_'): return addonid, addonname return None, None