Esempio n. 1
0
def get_extensions(user, repo, addon_xml=None):
    extensions = []
    if not addon_xml:
        addon_xml = API.get_file(user, repo, "addon.xml", text=True)

    if addon_xml:
        tools.log("Checking for extensions in {}/{}".format(user, repo))
        root = tools.parse_xml(text=addon_xml.encode("utf-8"))

        try:
            tags = root.findall("extension")
            if tags is not None:
                for ext in tags:
                    point = ext.get("point")
                    if point and point in _extensions:
                        ext_point = _extensions[point]
                        if isinstance(ext_point, dict):
                            provides = ext.find("provides")
                            if provides is not None and provides.text:
                                all_provides = provides.text.split(" ")
                                for p in all_provides:
                                    if p in ext_point:
                                        extensions.append(ext_point[p])
                            else:
                                extensions.append(ext_point[None])
                        else:
                            extensions.append(ext_point)
        except Exception as e:
            tools.log("Could not check for extensions: {}".format(e),
                      level="warning")
    return extensions
Esempio n. 2
0
    def run(self):
        """
        Executes the workload
        :return:
        :rtype:
        """
        while not self.tasks.empty() and not self.stop_flag.is_set():

            try:
                func, result_callback, args, kwargs = self.tasks.get(
                    timeout=0.1)
                self.name = func
                result_callback(func(*args, **kwargs))
            except Empty:
                break
            except BaseException as ex:
                tools.log(traceback.format_exc(), "error")
                self.exception_handler(ex)
                break
            finally:
                try:
                    self.tasks.task_done()
                except Exception as e:
                    print("task done error: {}".format(repr(e)))
                    pass
Esempio n. 3
0
    def setupForMusicVideo(self):
        log('settings() - setupForMusicVideo')

        if self.music_preset == 1:  #preset Ballad
            saturation = 3.0
            value = 10.0
            speed = 20.0
            autospeed = 0.0
            interpolation = 1
            threshold = 0.0
        elif self.music_preset == 2:  #preset Rock
            saturation = 3.0
            value = 10.0
            speed = 80.0
            autospeed = 0.0
            interpolation = 0
            threshold = 0.0
        elif self.music_preset == 3:  #preset disabled
            saturation = 0.0
            value = 0.0
            speed = 0.0
            autospeed = 0.0
            interpolation = 0
            threshold = 0.0
        elif self.music_preset == 0:  #custom
            saturation = self.music_saturation
            value = self.music_value
            speed = self.music_speed
            autospeed = self.music_autospeed
            interpolation = self.music_interpolation
            threshold = self.music_threshold
        return (saturation, value, speed, autospeed, interpolation, threshold)
Esempio n. 4
0
def _get_log_contents(logfile=None):
    if logfile == None:
        logfile = os.path.join(_log_location, "kodi.log")
    if os.path.exists(logfile):
        return tools.read_from_file(logfile)
    else:
        tools.log("Error finding logs!", "error")
Esempio n. 5
0
def get_icon(user, repo, plugin_id, addon_xml=None):
    icon = ""

    addon_path = os.path.join(_addons, plugin_id)
    if os.path.exists(addon_path):
        addon_xml = tools.read_from_file(os.path.join(addon_path, "addon.xml"))
    if not addon_xml:
        addon_xml = API.get_file(user, repo, "addon.xml", text=True)

    if addon_xml:
        tools.log("Finding icon in addon.xml from {}/{}".format(user, repo))
        addon = tools.parse_xml(text=addon_xml.encode("utf-8"))

        try:
            icon_path = "icon.png"
            def_icon = list(addon.iter("icon"))

            if def_icon and len(def_icon) > 0:
                icon_path = def_icon[0].text

            if os.path.exists(addon_path):
                icon = os.path.join(addon_path, icon_path)
            else:
                icon_url = API.get_file(user, repo, icon_path)["download_url"]
                icon = requests.head(icon_url, allow_redirects=True).url
        except Exception as e:
            tools.log("Could not get icon: {}".format(e), level="warning")

    return icon
Esempio n. 6
0
    def setupForFiles(self):
        log('settings() - setupForFiles')

        if self.files_preset == 1:  #preset smooth
            saturation = 3.0
            value = 10.0
            speed = 20.0
            autospeed = 0.0
            interpolation = 0
            threshold = 0.0
        elif self.files_preset == 2:  #preset action
            saturation = 3.0
            value = 10.0
            speed = 80.0
            autospeed = 0.0
            interpolation = 0
            threshold = 0.0
        elif self.files_preset == 3:  #preset disabled
            saturation = 0.0
            value = 0.0
            speed = 0.0
            autospeed = 0.0
            interpolation = 0
            threshold = 0.0
        elif self.files_preset == 0:  #custom
            saturation = self.files_saturation
            value = self.files_value
            speed = self.files_speed
            autospeed = self.files_autospeed
            interpolation = self.files_interpolation
            threshold = self.files_threshold
        return (saturation, value, speed, autospeed, interpolation, threshold)
Esempio n. 7
0
 def handleStereoscopic(self, isStereoscopic):
     log('settings() - handleStereoscopic(%s) - disableon3d (%s)' %
         (isStereoscopic, self.bobdisableon3d))
     if self.bobdisableon3d and isStereoscopic:
         log('settings()- disable due to 3d')
         self.bobdisable = True
     else:
         self.resetBobDisable()
Esempio n. 8
0
 def setupForStatic(self):
     log('settings() - setupForStatic')
     saturation = 4.0
     value = 1.0
     speed = 50.0
     autospeed = 0.0
     interpolation = 1
     threshold = 0.0
     return (saturation, value, speed, autospeed, interpolation, threshold)
Esempio n. 9
0
 def setupForOther(self):
     log('settings() - setupForOther')
     # FIXME don't use them for now - reactivate when boblight works on non rendered scenes (e.x. menu)
     #  saturation      =  float(__addon__.getSetting("other_saturation"))
     #  value           =  float(__addon__.getSetting("other_value"))
     #  speed           =  float(__addon__.getSetting("other_speed"))
     #  autospeed       =  float(__addon__.getSetting("other_autospeed"))
     #  interpolation   =  __addon__.getSetting("other_interpolation") == "true"
     #  threshold       =  float(__addon__.getSetting("other_threshold"))
     return self.setupForStatic()
Esempio n. 10
0
def raise_issue(repo):
    dialog = xbmcgui.Dialog()
    title = dialog.input(settings.get_localized_string(30006))
    if title:
        description = dialog.input(settings.get_localized_string(30007))
        log_key = None
        response, log_key = logging.upload_log()

        if response:
            try:
                resp = API.raise_issue(
                    repo["user"],
                    repo["repo"],
                    _format_issue(title, description, log_key),
                )

                if "message" not in resp:
                    qr_code = qr.generate_qr(
                        resp["html_url"],
                        _addon_data,
                        "{}.png".format(resp["number"]),
                    )
                    top = [
                        (
                            settings.get_localized_string(30008),
                            "#efefefff",
                        ),
                        (
                            "{}/{}".format(repo["user"], repo["repo"]),
                            _color,
                        ),
                    ]
                    bottom = [
                        (settings.get_localized_string(30079), "#efefefff"),
                        (resp["html_url"], _color),
                    ]
                    qr.qr_dialog(
                        qr_code,
                        top_text=top,
                        bottom_text=bottom,
                    )

                    tools.execute_builtin("ShowPicture({})".format(qr_code))
                    while tools.get_condition("Window.IsActive(slideshow)"):
                        xbmc.sleep(1000)
                    os.remove(qr_code)
                else:
                    dialog.ok(_addon_name, resp["message"])
            except requests.exceptions.RequestException as e:
                dialog.notification(_addon_name,
                                    settings.get_localized_string(30009))
                tools.log("Error opening issue: {}".format(e), "error")
    else:
        dialog.ok(_addon_name, settings.get_localized_string(30010))
    del dialog
Esempio n. 11
0
 def __init__(self, *args, **kwargs):
     log('settings() - __init__')
     self.staticBobActive = False
     self.run_init = True
     self.category = "static"
     self.networkaccess = __addon__.getSetting("networkaccess") == "true"
     if not self.networkaccess:
         self.hostip = None
         self.hostport = -1
     else:
         self.hostip = __addon__.getSetting("hostip")
         self.hostport = int(__addon__.getSetting("hostport"))
     self.start()
Esempio n. 12
0
def _update_addon_version(addon, gitsha):
    addon_xml = os.path.join(_addons, addon, "addon.xml")
    tools.log("Rewriting addon version: {}".format(addon_xml))

    replace_regex = r'<\1"\2.\3.\4-{}"\7>'.format(gitsha[:7])

    content = tools.read_from_file(addon_xml)
    content = re.sub(
        r"<(addon id.*version=)\"([0-9]+)\.([0-9]+)\.([0-9]+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+)?(-.*?)?\"(.*)>",
        replace_regex,
        content,
    )
    tools.write_to_file(addon_xml, content)
Esempio n. 13
0
    def handleStaticBgSettings(self):
        log('settings() - handleStaticBgSettings')
        if (self.category == "static" and  # only for 'static' category
                self.other_static_bg
            ):  # only if we want it displayed on static

            bob.bob_set_priority(128)  # allow lights to be turned on
            rgb = (c_int * 3)(self.other_static_red, self.other_static_green,
                              self.other_static_blue)
            ret = bob.bob_set_static_color(byref(rgb))
            self.staticBobActive = True
        else:
            bob.bob_set_priority(255)
            self.staticBobActive = False
Esempio n. 14
0
def _exists(addon):
    params = {
        "jsonrpc": "2.0",
        "method": "Addons.GetAddons",
        "id": 1,
    }

    addons = tools.execute_jsonrpc(params)
    exists = False
    if addon in [
            a.get("addonid")
            for a in addons.get("result", {}).get("addons", {})
    ]:
        exists = True

    tools.log("{} {} installed".format(addon, "is" if exists else "not"))
    return exists
Esempio n. 15
0
def listVideos(url, pattern):
	data = tools.getUrl(url)

	matches = re.compile(pattern, re.DOTALL).findall(data)

	for match in matches:
		tools.log("listVideos->match: "+str(match))
		
	for scrapedurl, scrapedthumbnail, scrapedtitle, scrapeddate in matches:
		scrapedtitle = tools.cleanText(scrapedtitle)
		scrapeddate = scrapeddate.replace('<span class="dateseparator">|</span>\r\n                            ', ' | ').strip()
		tools.addLink(scrapedtitle+" ["+scrapeddate+"]", scrapedurl, 'playVideo', scrapedthumbnail)

	xbmcplugin.endOfDirectory(pluginhandle)
	
	if forceViewMode:
		tools.set_view(viewMode)
Esempio n. 16
0
def _install_deps(addon):
    failed_deps = []
    visible_cond = "Window.IsTopMost(yesnodialog)"

    xml_path = os.path.join(_addons, addon, "addon.xml")
    tools.log("Finding dependencies in {}".format(xml_path))
    root = tools.parse_xml(file=xml_path)
    requires = root.find("requires")
    if not requires:
        return
    deps = requires.findall("import")

    for dep in [
            d for d in deps if not d.get("addon").startswith("xbmc")
            and not d.get("optional") == "true"
    ]:
        plugin_id = dep.get("addon")
        installed_cond = "System.HasAddon({0})".format(plugin_id)
        if tools.get_condition(installed_cond):
            continue

        tools.log("Installing dependency: {}".format(plugin_id))
        tools.execute_builtin("InstallAddon({0})".format(plugin_id))

        clicked = False
        start = time.time()
        timeout = 10
        while not tools.get_condition(installed_cond):
            if time.time() >= start + timeout:
                tools.log(
                    "Timed out installing dependency: {}".format(plugin_id),
                    level="warning",
                )
                failed_deps.append(plugin_id)
                break

            tools.sleep(500)

            if tools.get_condition(visible_cond) and not clicked:
                tools.log("Dialog to click open")
                tools.execute_builtin("SendClick(yesnodialog, 11)")
                clicked = True
            else:
                tools.log("...waiting")
    return failed_deps
Esempio n. 17
0
def _set_enabled(addon, enabled, exists=True):
    enabled_params = {
        "jsonrpc": "2.0",
        "method": "Addons.GetAddonDetails",
        "params": {
            "addonid": addon,
            "properties": ["enabled"]
        },
    }

    params = {
        "jsonrpc": "2.0",
        "method": "Addons.SetAddonEnabled",
        "params": {
            "addonid": addon,
            "enabled": enabled
        },
    }

    if not exists and not enabled:
        return False
    elif not exists and enabled:
        db_file = _get_addons_db()
        connection = sqlite3.connect(db_file)
        cursor = connection.cursor()
        date = time.strftime("%Y-%m-%d %H:%M:%S")
        cursor.execute("DELETE FROM installed WHERE addonID = ?", (addon, ))
        cursor.execute(
            "INSERT INTO installed (addonID, enabled, installDate) VALUES (?, 1, ?)",
            (addon, date),
        )
        connection.commit()

        connection.close()
    else:
        tools.execute_jsonrpc(params)

    new_status = (tools.execute_jsonrpc(enabled_params).get("result", {}).get(
        "addon", {}).get("enabled", enabled)) == enabled

    tools.log("{}{}{}abled".format(addon, " " if new_status else " not ",
                                   "en" if enabled else "dis"))
    return new_status
Esempio n. 18
0
def _rewrite_kodi_dependency_versions(addon):
    kodi_version = tools.kodi_version()
    tools.log("KODI_VERSION: {}".format(kodi_version))
    kodi_dep_versions = {
        18: {
            "xbmc.python": "2.26.0",
            "xbmc.gui": "5.14.0"
        },
        19: {
            "xbmc.python": "3.0.0",
            "xbmc.gui": "5.15.0"
        },
    }

    if kodi_version in kodi_dep_versions:
        kodi_deps = kodi_dep_versions[kodi_version]
    else:
        # Take latest version if we don't have version specific
        kodi_deps = kodi_dep_versions[max(kodi_dep_versions)]
    tools.log("KODI DEPENDENCY VERSIONS: {}".format(kodi_deps))

    addon_xml = os.path.join(_addons, addon, "addon.xml")
    tools.log("Rewriting {}".format(addon_xml))

    content = tools.read_from_file(addon_xml)
    for dep in kodi_deps:
        content = re.sub(
            '<import addon="' + dep + r'" version=".*?"\s?/>',
            '<import addon="' + dep + '" version="' + kodi_deps[dep] + '" />',
            content,
        )
    tools.write_to_file(addon_xml, content)
Esempio n. 19
0
def upload_log(choose=False, dialog=False):
    logfile = _select_log_file() if choose else None
    log_data = _censor_log_content(_get_log_contents(logfile))
    user_agent = "{}: {}".format(_addon_id, _addon_version)

    try:
        response = requests.post(
            _paste_url + "documents",
            data=log_data.encode("utf-8"),
            headers={
                "User-Agent": user_agent
            },
        ).json()
        if "key" in response:
            if dialog:
                _log_dialog(response["key"])
            return True, response["key"]
        elif "message" in response:
            tools.log("Upload failed: {}".format(response["message"]),
                      level="error")
            return False, response["message"]
        else:
            tools.log("Invalid response: {}".format(response), level="error")
            return False, "Error posting the log file."
    except requests.exceptions.RequestException as e:
        tools.log("Failed to retrieve the paste URL: {}".format(e),
                  level="error")
        return False, "Failed to retrieve the paste URL."
Esempio n. 20
0
 def handleGlobalSettings(self):
     log('settings() - handleGlobalSettings')
     if (self.current_option != self.category) or self.force_update:
         #call the right setup function according to category
         #switch case in python - dictionary with function pointers
         option = {
             "movie": self.setupForMovie,
             "tvshow": self.setupForTVShow,
             "livetv": self.setupForLiveTV,
             "files": self.setupForFiles,
             "musicvideo": self.setupForMusicVideo,
             "other": self.setupForOther,
             "static": self.setupForStatic,
         }
         saturation, value, speed, autospeed, interpolation, threshold = option[
             self.category]()
         for opt in OPTS:
             ret = bob.bob_setoption("%s    %s" % (opt, str(locals()[opt])))
             log("changed %s    to %s ret:  %s" %
                 (opt, str(locals()[opt]), ret))
         self.current_option = self.category
         self.force_update = False
Esempio n. 21
0
def get_repo_info(repo_def):
    user = repo_def["owner"]["login"]
    repo = repo_def["name"]
    addon_xml = API.get_file(user, repo, "addon.xml", text=True)
    if not addon_xml:
        return

    tools.log("Reading addon.xml from {}/{}".format(user, repo))
    addon = tools.parse_xml(text=addon_xml.encode("utf-8"))

    def_name = addon.get("name")
    icon = get_icon(user, repo, addon_xml)
    extensions = get_extensions(user, repo, addon_xml)

    return [{
        "name": def_name,
        "user": user,
        "repo_name": repo,
        "updated_at": repo_def["updated_at"],
        "icon": icon,
        "extensions": extensions,
    }]
Esempio n. 22
0
def _extract_addon(zip_location, repo):
    tools.log("Opening {}".format(zip_location))
    with zipfile.ZipFile(zip_location) as file:
        base_directory = file.namelist()[0]
        tools.log("Extracting to: {}".format(
            os.path.join(_temp, base_directory)))
        for f in [
                i for i in file.namelist()
                if all(e not in i for e in repo.get("exclude_items", []))
        ]:
            try:
                file.extract(f, _temp)
            except Exception as e:
                tools.log("Could not extract {}: {}".format(f, e))
    install_path = os.path.join(_addons, repo["plugin_id"])
    tools.copytree(os.path.join(_temp, base_directory),
                   install_path,
                   ignore=True)
    tools.remove_folder(os.path.join(_temp, base_directory))
    tools.remove_file(zip_location)
Esempio n. 23
0
    def bob_init(self):
        if self.run_init:
            log('bob_init')
            nrLights = bob.bob_getnrlights()
            log("settings() - Found %s lights" % str(nrLights))
            for i in range(nrLights):
                lightname = bob.bob_getlightname(i)
                log("settings() - Light[%.2d] - %s" % (i + 1, lightname))

            self.handleGlobalSettings()
            bob.bob_set_priority(
                128)  # allow lights to be turned on, we will switch them off
            # in 'handleStaticBgSettings()' if they are not needed
            if self.other_misc_initialflash:
                for i in range(len(BLING)):
                    rgb = (c_int * 3)(BLING[i][0], BLING[i][1], BLING[i][2])
                    bob.bob_set_static_color(byref(rgb))
                    xbmc.sleep(1000)
            else:
                rgb = (c_int * 3)(0, 0, 0)
                bob.bob_set_static_color(byref(rgb))
            self.run_init = False
            xbmc.sleep(500)
        return True
Esempio n. 24
0
 def _try_raise(self):
     if self.exception:
         tools.log(traceback.format_exc(), "error")
         raise self.exception
Esempio n. 25
0
    def start(self):
        log('settings() - start')
        self.reconnect = False
        self.force_update = True
        self.networkaccess = __addon__.getSetting("networkaccess") == "true"
        self.overwrite_cat = __addon__.getSetting("overwrite_cat") == "true"
        self.overwrite_cat_val = int(__addon__.getSetting("overwrite_cat_val"))
        self.bobdisableonscreensaver = __addon__.getSetting(
            "bobdisableonscreensaver") == "true"
        self.bobdisable = __addon__.getSetting("bobdisable") == "true"
        self.bobdisableon3d = __addon__.getSetting("bobdisableon3d") == "true"
        self.current_option = ""

        if not self.networkaccess:
            self.hostip = None
            self.hostport = -1
            self.reconnect = True
        else:
            hostip = __addon__.getSetting("hostip")
            hostport = int(__addon__.getSetting("hostport"))
            if ((self.hostip != hostip) or (self.hostport != hostport)):
                self.hostip = hostip
                self.hostport = hostport
                self.reconnect = True

        # Other settings
        self.other_static_bg = __addon__.getSetting(
            "other_static_bg") == "true"
        self.other_static_red = int(
            float(__addon__.getSetting("other_static_red")))
        self.other_static_green = int(
            float(__addon__.getSetting("other_static_green")))
        self.other_static_blue = int(
            float(__addon__.getSetting("other_static_blue")))
        self.other_misc_initialflash = __addon__.getSetting(
            "other_misc_initialflash") == "true"
        self.other_misc_notifications = __addon__.getSetting(
            "other_misc_notifications") == "true"

        # Movie settings
        self.movie_saturation = float(__addon__.getSetting("movie_saturation"))
        self.movie_value = float(__addon__.getSetting("movie_value"))
        self.movie_speed = float(__addon__.getSetting("movie_speed"))
        self.movie_autospeed = float(__addon__.getSetting("movie_autospeed"))
        self.movie_interpolation = int(
            __addon__.getSetting("movie_interpolation") == "true")
        self.movie_threshold = float(__addon__.getSetting("movie_threshold"))
        self.movie_preset = int(__addon__.getSetting("movie_preset"))

        # TV Shows settings
        self.tvshow_saturation = float(
            __addon__.getSetting("tvshow_saturation"))
        self.tvshow_value = float(__addon__.getSetting("tvshow_value"))
        self.tvshow_speed = float(__addon__.getSetting("tvshow_speed"))
        self.tvshow_autospeed = float(__addon__.getSetting("tvshow_autospeed"))
        self.tvshow_interpolation = int(
            __addon__.getSetting("tvshow_interpolation") == "true")
        self.tvshow_threshold = float(__addon__.getSetting("tvshow_threshold"))
        self.tvshow_preset = int(__addon__.getSetting("tvshow_preset"))

        # LiveTV settings
        self.livetv_saturation = float(
            __addon__.getSetting("livetv_saturation"))
        self.livetv_value = float(__addon__.getSetting("livetv_value"))
        self.livetv_speed = float(__addon__.getSetting("livetv_speed"))
        self.livetv_autospeed = float(__addon__.getSetting("livetv_autospeed"))
        self.livetv_interpolation = int(
            __addon__.getSetting("livetv_interpolation") == "true")
        self.livetv_threshold = float(__addon__.getSetting("livetv_threshold"))
        self.livetv_preset = int(__addon__.getSetting("livetv_preset"))

        # Files settings
        self.files_saturation = float(__addon__.getSetting("files_saturation"))
        self.files_value = float(__addon__.getSetting("files_value"))
        self.files_speed = float(__addon__.getSetting("files_speed"))
        self.files_autospeed = float(__addon__.getSetting("files_autospeed"))
        self.files_interpolation = int(
            __addon__.getSetting("files_interpolation") == "true")
        self.files_threshold = float(__addon__.getSetting("files_threshold"))
        self.files_preset = int(__addon__.getSetting("files_preset"))

        # Music Video settings
        self.music_saturation = float(
            __addon__.getSetting("musicvideo_saturation"))
        self.music_value = float(__addon__.getSetting("musicvideo_value"))
        self.music_speed = float(__addon__.getSetting("musicvideo_speed"))
        self.music_autospeed = float(__addon__.getSetting("movie_autospeed"))
        self.music_interpolation = int(
            __addon__.getSetting("musicvideo_interpolation") == "true")
        self.music_threshold = float(
            __addon__.getSetting("musicvideo_threshold"))
        self.music_preset = int(__addon__.getSetting("musicvideo_preset"))
Esempio n. 26
0
 def handleCategory(self, category):
     log('settings() - handleCategory(%s)' % category)
     self.category = category
     self.handleGlobalSettings()
     self.handleStaticBgSettings()
Esempio n. 27
0
def add_repository():
    dialog = xbmcgui.Dialog()
    pool = ThreadPool()

    user = dialog.input(settings.get_localized_string(30022)).lower()
    if not user:
        dialog.notification(_addon_name, settings.get_localized_string(30023))
        del dialog
        return

    if API.get_user(user).get("type", "User") == "Organization":
        user_repos = API.get_org_repos(user)
    elif user == _user.lower():
        access_level = [
            "owner",
            "collaborator" if _collaborator else "",
            "organization_member" if _organization else "",
        ]
        user_repos = API.get_repos(",".join(access_level))
    else:
        user_repos = API.get_user_repos(user)

    addon_repos = []
    repo_items = []

    with tools.busy_dialog():
        for user_repo in user_repos:
            if "message" in user_repo:
                dialog.ok(_addon_name, settings.get_localized_string(30065))
                del dialog
                return
            pool.put(get_repo_info, user_repo)
        repos = pool.wait_completion()
        if not repos:
            dialog.ok(_addon_name, settings.get_localized_string(30066))
            del dialog
            return

        repos.sort(key=lambda b: b["updated_at"], reverse=True)
        addon_repos = [i["repo_name"] for i in repos]
        for i in repos:
            byline = ("{} - ".format(i["repo_name"]) + ", ".join([
                settings.get_localized_string(30049).format(i["user"]),
                settings.get_localized_string(30016).format(
                    tools.to_local_time(i["updated_at"])),
            ]) if i["user"].lower() != user else
                      "{} - ".format(i["repo_name"]) +
                      settings.get_localized_string(30016).format(
                          tools.to_local_time(i["updated_at"])))
            li = xbmcgui.ListItem(
                "{} - ({})".format(
                    i["name"],
                    ", ".join([e.title() for e in i["extensions"]])),
                label2=byline,
            )

            if not _compact:
                li.setArt({"thumb": i["icon"]})

            repo_items.append(li)

    if len(addon_repos) == 0:
        dialog.ok(_addon_name, settings.get_localized_string(30058))
        del dialog
        return

    selection = dialog.select(settings.get_localized_string(30011),
                              repo_items,
                              useDetails=not _compact)
    if selection < 0:
        del dialog
        return
    user = repos[selection]["user"]
    repo = addon_repos[selection]

    if not _check_repo(user, repo):
        del dialog
        return

    addon_xml = API.get_file(user, repo, "addon.xml", text=True)
    if not addon_xml:
        del dialog
        return

    tools.log("Reading addon.xml from {}/{}".format(user, repo))
    addon = tools.parse_xml(text=addon_xml.encode("utf-8"))

    name = addon.get("name")
    plugin_id = addon.get("id")

    if dialog.yesno(_addon_name,
                    settings.get_localized_string(30059).format(name)):
        _add_repo(user, repo, name, plugin_id)
    del dialog