Example #1
0
    def get_groups(self):
        response = self.make_request("channel_list", method="POST")
        is_error, error = Api.is_error_response(response)
        if is_error:
            raise ApiException(error.get("message"), error.get("code"))
        Kartina.raise_api_exception_on_error(response)

        groups = OrderedDict()
        for group_data in response["groups"]:
            if all(k in group_data
                   for k in ("id", "name", "channels")) is False:
                continue
            channels = OrderedDict()
            for channel_data in group_data["channels"]:
                if bool(channel_data.get("is_video", 1)) is False:
                    continue
                group_data["id"] = str(group_data["id"])
                if self.adult is False and bool(
                        channel_data.get("protected", False)) is True:
                    continue
                channel = Channel(
                    cid=str(channel_data["id"]),
                    gid=group_data["id"],
                    name=channel_data["name"],
                    icon=self.base_icon_url % channel_data["id"],
                    epg=True
                    if int(channel_data.get("epg_start", 0)) > 0 else False,
                    archive=bool(channel_data.get("have_archive", 0)),
                    protected=bool(channel_data.get("protected", False)))
                channels[channel.cid] = channel
            groups[group_data["id"]] = Group(group_data["id"],
                                             group_data["name"], channels)
        return groups
Example #2
0
    def get_epg(self, cid):
        # type: (str) -> OrderedDict[int, Program]
        programs = self.get_epg_gh(self.channels[cid])
        if len(programs):
            return programs

        settings = self.read_settings_file()
        if not settings:
            self.login()
            return self.get_epg(cid)
        start = int(time_now() - self.archive_ttl)
        end = int(time_now() + (DAY * 2))
        uri = "api/api_v2.php?_resource=users/%s/tv-channels/%s/epg&from=%s&to=%s" % \
              (settings.get("user_id"), cid, start, end)
        response = self.make_request(uri, headers=self.default_headers())
        is_error, error = Api.is_error_response(response)
        if is_error:
            raise ApiException(error.get("message"), error.get("code"))

        programs = OrderedDict()
        prev = None  # type: Program
        for entry in response.get("results", []):
            program = Program(cid, self.channels[cid].gid, entry["start"],
                              entry["end"], entry["name"], "",
                              bool(entry["in_archive"]))
            program.data["id"] = entry["id"]
            if prev is not None:
                program.prev_program = prev
                prev.next_program = program
            programs[program.ut_start] = prev = program
        return programs
Example #3
0
    def get_epg(self, cid):
        # type: (str) -> OrderedDict[int, Program]
        programs = self.get_epg_gh(self.channels[cid])
        if len(programs):
            return programs

        programs = OrderedDict()
        json_data = self.make_request("channel/%s" % cid)
        is_error, error = Api.is_error_response(json_data)
        if is_error:
            raise ApiException(error.get("message"), error.get("code"))
        response = json.loads(json_data) if not isinstance(json_data,
                                                           dict) else json_data
        prev = None
        response = {int(k): v for k, v in response.items()}
        for k in sorted(response.iterkeys()):
            v = response[k]
            program = Program(
                cid, self.channels[cid].gid, v["time"], v["time_to"],
                v["name"], v["descr"],
                bool(v["rec"])
                if v.has_key("rec") else self.channels[cid].archive)
            if prev is not None:
                program.prev_program = prev
                prev.next_program = program
            programs[program.ut_start] = prev = program
        return programs
Example #4
0
 def do_login(self, device_id=""):
     response = self.make_api_request(
         "login", [self.username, self.password, device_id])
     is_error, error = Api.is_error_response(response)
     if is_error:
         raise ApiException(error.get("message"), error.get("code"))
     Ottplayer.raise_api_exception_on_error(response["error"])
     return response
Example #5
0
 def get_devices(self):
     # type: () -> list[dict]
     response = self.make_api_request("get_devices", ["unknown"], 1)
     is_error, error = Api.is_error_response(response)
     if is_error:
         raise ApiException(error.get("message"), error.get("code"))
     Ottplayer.raise_api_exception_on_error(response["error"])
     return response["result"]
Example #6
0
    def get_epg(self, cid):
        # type: (str) -> OrderedDict[int, Program]
        programs = self.get_epg_gh(self.channels[cid])
        if len(programs):
            return programs

        requests = []
        days = (self.archive_ttl / DAY) + 2
        while days % 4:
            days += 1
        start = int(time_now() - self.archive_ttl)
        for i in range(days):
            day = format_date(start + (i * DAY), custom_format="%d%m%y")
            request = self.prepare_request(
                "epg.php", self.auth_payload({
                    "cid": cid,
                    "day": day
                }))
            requests.append(request)

        results = self.send_parallel_requests(requests, 0.40, 4)

        epg = dict()
        prev_ts = None
        for key in sorted(results.iterkeys()):
            response = results[key]
            is_error, error = Api.is_error_response(response)
            if not is_error:
                for entry in response["epg"]:
                    title, descr = (HTMLParser().unescape(entry["progname"]) +
                                    "\n").split("\n", 1)
                    ts = int(entry["ut_start"])
                    epg[ts] = dict({
                        "time": ts,
                        "time_to": 0,
                        "duration": 0,
                        "name": title,
                        "descr": descr
                    })
                    if prev_ts is not None:
                        epg[prev_ts]["time_to"] = ts
                    prev_ts = ts
            else:
                log("error: %s" % error, xbmc.LOGDEBUG)

        programs = OrderedDict()
        prev = None  # type: Program
        for key in sorted(epg.iterkeys()):
            val = epg[key]
            program = Program(cid, self.channels[cid].gid, val["time"],
                              val["time_to"], val["name"], val["descr"],
                              self.channels[cid].archive)
            if prev is not None:
                program.prev_program = prev
                prev.next_program = program
            programs[program.ut_start] = prev = program
        return programs
Example #7
0
 def get_stream_url(self, cid, ut_start=None):
     payload = {"cid": cid}
     if ut_start:
         payload["gmt"] = int(ut_start)
     response = self.make_request("get_url", payload)
     is_error, error = Api.is_error_response(response)
     if is_error:
         raise ApiException(error.get("message"), error.get("code"))
     Kartina.raise_api_exception_on_error(response)
     url = response["url"]
     return url.replace("http/ts", "http").split()[0]
Example #8
0
 def get_random_token(self):
     payload = {
         "apiAction": "getRandomTokens",
         "cnt": "1",
         "fingerPrint": "%d" % self.device_id,
         "sessionId": self._session_id
     }
     response = self.make_request("", payload)
     is_error, error = Api.is_error_response(response)
     if is_error:
         raise ApiException(error.get("message"), error.get("code"))
     return response.get("data").get("tokens")[0]
Example #9
0
 def register_device(self):
     # type: () -> str
     response = self.make_api_request("register_device",
                                      [self.DEVICE_TYPE, "unknown"], 2)
     is_error, error = Api.is_error_response(response)
     if is_error:
         raise ApiException(
             error.get("message", get_string(TEXT_HTTP_REQUEST_ERROR_ID)),
             error.get("code", Api.E_UNKNOW_ERROR))
     if "error" in response and response["error"]:
         raise ApiException(response["error"], Api.E_API_ERROR)
     return response["result"]
Example #10
0
    def get_stream_url(self, cid, ut_start=None):
        payload = self.auth_payload({"zone_id": "1", "nohls": "0", "channel_id": cid})
        response = self.make_request("translation_http.php", payload)
        is_error, error = Api.is_error_response(response)
        if is_error:
            raise ApiException(error.get("message"), error.get("code"))

        self.raise_api_exception_on_error(response["error"])

        url = response["source"]
        if ut_start is not None:
            url = "%s%sutc=%s&lutc=%s" % (url, "&" if "?" in url else "?", ut_start, int(time_now()))
        return url
Example #11
0
    def login(self):
        payload = {
            "username": self.username,
            "password": self.password,
            "typeresult": "json",
            "application": "xbmc",
            "guid": self._guid
        }
        response = self.make_request("auth.php", payload)
        is_error, error = Api.is_error_response(response)
        if is_error:
            raise ApiException(error.get("message"), error.get("code"))

        self.raise_api_exception_on_error(response["error"])

        self.auth_status = self.AUTH_STATUS_OK
        self.write_cookie_file("%s" % response["session"])
Example #12
0
    def get_groups(self):
        payload = self.auth_payload({
            "type": "channel",
        })
        response = self.make_request("translation_list.php", payload)
        is_error, error = Api.is_error_response(response)
        if is_error:
            raise ApiException(error.get("message"), error.get("code"))

        self.raise_api_exception_on_error(response["error"])

        groups = OrderedDict()
        channels = OrderedDict()
        for group_data in response.get("categories", []):
            if all(k in group_data for k in ("id", "name", "adult")) is False:
                continue
            gid = str(group_data["id"])
            groups[gid] = Group(gid, group_data["name"], OrderedDict(),
                                group_data["position"])

        for channel_data in response.get("channels", []):
            gid = str(channel_data["group"])
            if groups.has_key(gid) is False:
                continue
            group = groups[gid]

            if self.adult is False and bool(channel_data["adult"]):
                continue

            access = channel_data.get("ts_on_air", 0) and \
                     (channel_data.get("access_user", 0) or channel_data.get("access_free", 0))
            if bool(access) is False:
                continue

            cid = str(channel_data["id"])
            channel = Channel(cid=cid,
                              gid=group.gid,
                              name=channel_data["name"],
                              icon=self.base_icon_url % channel_data["logo"],
                              epg=channel_data["epg_id"] > 0,
                              archive=bool(
                                  channel_data.get("access_archive", 0)),
                              protected=bool(channel_data["adult"]))
            channel.data.update({"epg_id": channel_data["epg_id"]})
            group.channels[cid] = channels[cid] = channel
        return groups
Example #13
0
    def login(self):
        passwd = hashlib.md5(self.password).hexdigest()
        response = self.make_request("", {"userLogin": self.username, "userPasswd": passwd})
        if response.get("error") != "":
            raise ApiException(
                addon.getLocalizedString(TEXT_AUTHENTICATION_FAILED_ID),
                Api.E_API_ERROR
            )
        else:
            is_error, error = Api.is_error_response(response)
            if is_error:
                raise ApiException(error.get("message"), error.get("code"))

        self._session_id = response.get("data").get("sessionId")
        self.auth_status = self.AUTH_STATUS_OK
        self._random_token = self.get_random_token()
        return response
Example #14
0
    def get_groups(self):
        if self._session_id is None:
            self.login()
        payload = {
            "apiAction": "getUserChannels",
            "resultType": "tree",
            "sessionId": self._session_id
        }
        response = self.make_api_request(payload)
        is_error, error = Api.is_error_response(response)
        if is_error:
            raise ApiException(error.get("message"), error.get("code"))
        data = response.get("data")
        groups = OrderedDict()
        channels = OrderedDict()
        for group_data in data.get("userChannelsTree"):
            gid = group_data.get("groupId")
            if gid == 0:
                continue
            group = Group(
                gid=gid,
                name=group_data.get("groupName"),
                channels=OrderedDict(),
                number=int(group_data.get("sortOrder"))
            )
            groups[gid] = group

            for channel_data in group_data.get("channelsList"):
                is_adult = bool(int(channel_data.get("isPorno")))
                if self.adult is False and is_adult is True:
                    continue

                cid = channel_data.get("channelId")
                has_epg = len(channel_data.get("curProgram").get("prTitle")) > 0
                channel = Channel(
                    cid=cid,
                    gid=group.gid,
                    name=channel_data.get("channelName"),
                    icon=channel_data.get("channelLogo"),
                    epg=has_epg,
                    archive=has_epg,
                    protected=is_adult
                )
                channel.data.update({"stream_url": channel_data["liveLink"]})
                group.channels[cid] = channels[cid] = channel
        return groups
Example #15
0
    def login(self):
        payload = {
            "login": self.username,
            "pass": self.password,
            "settings": "all"
        }
        response = self.make_request("login.php", payload=payload)
        is_error, error = Api.is_error_response(response)
        if is_error:
            raise ApiException(error.get("message"), error.get("code"))
        Novoetv.raise_api_exception_on_error(response)

        self.auth_status = self.AUTH_STATUS_OK
        self.write_cookie_file("%s=%s" %
                               (response["sid_name"], response["sid"]))
        self.write_settings_file(response)

        return response
Example #16
0
 def get_channels(self):
     # type: () -> list
     settings = self.read_settings_file()
     if not settings:
         self.login()
         return self.get_channels()
     uri = "api/api_v2.php?_resource=users/%s/tv-channels" % settings.get(
         "user_id")
     response = self.make_request(uri, headers=self.default_headers())
     is_error, error = Api.is_error_response(response)
     if is_error:
         raise ApiException(error.get("message"), error.get("code"))
     if "error" in response:
         raise ApiException(
             response.get("error_description",
                          get_string(TEXT_SERVICE_ERROR_OCCURRED_ID)),
             Api.E_API_ERROR)
     return response.get("results")
Example #17
0
    def get_epg(self, cid):
        # type: (str) -> OrderedDict[int, Program]
        channel = self.channels[cid]
        programs = self.get_epg_gh(channel)
        if len(programs):
            return programs

        response = self.make_api_request(
            "get_epg2",
            [channel.epg_id, 2, int(self.archive_ttl / DAY)])
        is_error, error = Api.is_error_response(response)
        if is_error:
            raise ApiException(error.get("message"), error.get("code"))
        Ottplayer.raise_api_exception_on_error(response["error"])

        epg = dict()
        for v in response["result"]:
            start = int(
                str_to_timestamp(v["start"], "%Y-%m-%d %H:%M:%S") +
                self._utc_local_offset)
            stop = int(
                str_to_timestamp(v["stop"], "%Y-%m-%d %H:%M:%S") +
                self._utc_local_offset)
            epg[start] = dict({
                "time": start,
                "time_to": stop,
                "duration": stop - start,
                "name": v["title"],
                "descr": v["desc"]
            })

        programs = OrderedDict()
        prev = None  # type: Program
        for key in sorted(epg.iterkeys()):
            val = epg[key]
            program = Program(cid, channel.gid, val["time"], val["time_to"],
                              val["name"], val["descr"], channel.archive)
            if prev is not None:
                if program.ut_start != prev.ut_end:
                    continue
                program.prev_program = prev
                prev.next_program = program
            programs[program.ut_start] = prev = program
        return programs
Example #18
0
    def login(self):
        self._player_info = []
        response = self.make_request("", {
            "action": "playerInfo",
            "ukey": self.key
        })
        if isinstance(response, list) and len(response) and response[0].get(
                "response") == "No Token":
            raise ApiException(
                addon.getLocalizedString(self.TEXT_ERROR_WRONG_KEY_ID),
                Api.E_API_ERROR)
        else:
            is_error, error = Api.is_error_response(response)
            if is_error:
                raise ApiException(error.get("message"), error.get("code"))

        self._player_info = response
        self.auth_status = self.AUTH_STATUS_OK
        return response
Example #19
0
    def get_groups(self):
        settings = self.read_settings_file()
        if not settings:
            self.login()
            return self.get_groups()
        uri = "api/api_v2.php?_resource=users/%s/tv-genres" % settings.get(
            "user_id")
        response = self.make_request(uri, headers=self.default_headers())
        is_error, error = Api.is_error_response(response)
        if is_error:
            raise ApiException(error.get("message"), error.get("code"))
        if "error" in response:
            raise ApiException(
                response.get("error_description",
                             get_string(TEXT_SERVICE_ERROR_OCCURRED_ID)),
                Api.E_API_ERROR)

        groups = OrderedDict()
        for group_data in response.get("results"):
            if all(k in group_data
                   for k in ("id", "title", "censored", "number")) is False:
                continue
            groups[str(group_data["id"])] = Group(str(group_data["id"]),
                                                  group_data["title"],
                                                  OrderedDict(),
                                                  int(group_data["number"]))

        channels = self.get_channels()
        for channel_data in channels:
            if self.adult is False and bool(channel_data.get(
                    "censored", False)) is True:
                continue
            channel = Channel(cid=str(channel_data["id"]),
                              gid=str(channel_data["genre_id"]),
                              name=channel_data["name"],
                              icon=self.base_icon_url % channel_data["logo"],
                              epg=True,
                              archive=bool(channel_data.get("archive", 0)),
                              protected=bool(
                                  channel_data.get("censored", False)),
                              url=channel_data["url"])
            groups[channel.gid].channels[channel.cid] = channel
        return groups
Example #20
0
    def get_epg(self, cid):
        # type: (str) -> OrderedDict[int, Program]
        obj = quote(json.dumps({"action": "epg", "chid": cid}))
        response = self.make_request("epg.php?obj=%s" %
                                     obj)  # type: dict[str, list[dict]]
        is_error, error = Api.is_error_response(response)
        if is_error:
            raise ApiException(error.get("message"), error.get("code"))

        programs = OrderedDict()
        prev = None
        for v in response["res"]:
            program = Program(cid, self.channels[cid].gid, int(v["startTime"]),
                              int(v["stopTime"]), v["title"], v["desc"],
                              self.channels[cid].archive)
            if prev is not None:
                program.prev_program = prev
                prev.next_program = program
            programs[program.ut_start] = prev = program
        return programs
Example #21
0
    def login(self):
        payload = {
            "grant_type": "password",
            "username": self.username,
            "password": self.password
        }
        response = self.make_request("auth/token.php", payload, method="POST")
        is_error, error = Api.is_error_response(response)
        if is_error:
            raise ApiException(error.get("message"), error.get("code"))
        if "error" in response:
            raise ApiException(
                response.get("error_description",
                             get_string(TEXT_SERVICE_ERROR_OCCURRED_ID)),
                Api.E_API_ERROR)

        self.auth_status = self.AUTH_STATUS_OK
        token_type = self.get_token_type(response)
        self.write_cookie_file(
            "%s %s" % (token_type, response.get("access_token", "Unknown")))
        self.write_settings_file(response)

        return response
Example #22
0
    def get_epg(self, cid):
        # type: (str) -> OrderedDict[int, Program]
        programs = self.get_epg_gh(self.channels[cid])
        if len(programs):
            return programs

        if self._session_id is None:
            self.login()
        payload = {
            "apiAction": "getTvProgram",
            "channelId": cid,
            "sessionId": self._session_id
        }
        response = self.make_api_request(payload)  # type: dict[str, list[dict]]
        is_error, error = Api.is_error_response(response)
        if is_error:
            raise ApiException(error.get("message"), error.get("code"))

        programs = OrderedDict()
        prev = None
        for v in response.get("data").get("tvProgram"):
            program = Program(
                cid,
                self.channels[cid].gid,
                int(v.get("prStartSec")),
                int(v.get("prStopSec")),
                v.get("prTitle"),
                v.get("prSubTitle"),
                (v.get("streamLink") != "")
            )
            program.data.update({"stream_url": v.get("streamLink")})
            if prev is not None:
                program.prev_program = prev
                prev.next_program = program
            programs[program.ut_start] = prev = program
        return programs
Example #23
0
    def get_epg(self, cid):
        # type: (str) -> OrderedDict[int, Program]
        channel = self.channels[cid]
        if channel.epg is False or channel.epg_id == 0:
            return OrderedDict()

        requests = []
        days = (self.archive_ttl / DAY)
        start = int(time_now() - self.archive_ttl)
        for i in range(days):
            day = format_date(start + (i * DAY), custom_format="%d-%m-%Y")
            payload = self.auth_payload({
                "epg_id": channel.epg_id,
                "date": day
            })
            request = self.prepare_request("arc_records.php", payload)
            requests.append(request)

        requests.append(
            self.prepare_request("translation_epg.php",
                                 self.auth_payload({"epg_id":
                                                    channel.epg_id})))

        results = self.send_parallel_requests(requests)

        epg = dict()
        prev_ts = None
        for key in sorted(results.iterkeys()):
            response = results[key]
            is_error, error = Api.is_error_response(response)
            if not is_error and "success" in response and response[
                    "success"] == 1:
                data = response[
                    "records"] if "records" in response else response[
                        "data"] if "data" in response else []
                for entry in data:
                    time = int(entry["time"]) if "time" in entry else int(
                        entry["btime"]) if "btime" in entry else 0
                    time_to = int(entry["etime"]) if "etime" in entry else 0
                    epg[time] = dict({
                        "time": time,
                        "time_to": time_to,
                        "duration": 0,
                        "name": entry["name"]
                    })
                    if prev_ts is not None and epg[prev_ts]["time_to"] == 0:
                        epg[prev_ts]["time_to"] = time
                    prev_ts = time
            else:
                log("error: %s" % error if is_error else response,
                    xbmc.LOGDEBUG)

        programs = OrderedDict()
        prev = None  # type: Program
        for key in sorted(epg.iterkeys()):
            val = epg[key]
            program = Program(cid, self.channels[cid].gid, val["time"],
                              val["time_to"], val["name"], "",
                              self.channels[cid].archive)
            if prev is not None:
                program.prev_program = prev
                prev.next_program = program
            programs[program.ut_start] = prev = program
        return programs
Example #24
0
    def get_groups(self):
        if self.auth_status != self.AUTH_STATUS_OK:
            self.login()
        get_groups_request = self.prepare_api_request("get_groups", [], 0)
        get_playlists_request = self.prepare_api_request(
            "get_playlists", [], 0)
        requests = [get_groups_request, get_playlists_request]
        results = self.send_parallel_requests(requests)

        get_groups_response = results[get_groups_request.ident]
        is_error, error = Api.is_error_response(get_groups_response)
        if is_error:
            raise ApiException(error.get("message"), error.get("code"))
        Ottplayer.raise_api_exception_on_error(get_groups_response["error"])

        groups = OrderedDict()
        number = 1
        for group_data in get_groups_response["result"]:
            if all(k in group_data for k in ("id", "name", "title")) is False:
                continue
            gid = str(group_data["id"])
            groups[gid] = Group(gid, group_data["title"], OrderedDict(),
                                number)
            number += 1

        get_playlists_response = results[get_playlists_request.ident]
        is_error, error = Api.is_error_response(get_playlists_response)
        if is_error:
            raise ApiException(error.get("message"), error.get("code"))
        Ottplayer.raise_api_exception_on_error(get_playlists_response["error"])

        playlists = get_playlists_response.get("result")  # type: list[dict]
        if len(playlists) == 0:
            raise ApiException(
                "%s\n%s" %
                (addon.getLocalizedString(self.TEXT_NO_PLAYLIST_BOUND_ID),
                 addon.getLocalizedString(self.TEXT_YOU_CAN_BIND_DEVICE_ID)),
                Api.E_UNKNOW_ERROR)

        channels = OrderedDict()
        requests = []
        for playlist in playlists:
            requests.append(
                self.prepare_api_request("get_channels", [playlist.get("id")],
                                         0, playlist.get("id")))

        results = self.send_parallel_requests(requests)
        for playlist_id, result in results.iteritems():
            is_error, error = Api.is_error_response(result)
            if is_error:
                raise ApiException(error.get("message"), error.get("code"))
            Ottplayer.raise_api_exception_on_error(result["error"])
            for channel_data in result["result"]:
                if self.adult is False and bool(
                        channel_data.get("adult", False)) is True:
                    continue
                playlist = next((playlist for playlist in playlists
                                 if playlist.get("id") == playlist_id), {})
                cid = "%s-%s" % (
                    channel_data["group_id"],
                    channel_data["id"],
                )
                channel = Channel(
                    cid=cid,
                    gid=str(channel_data["group_id"]),
                    name=channel_data["name"],
                    icon=channel_data["pict"],
                    epg=True if channel_data["epg_id"] > 0 else False,
                    archive=playlist.get("have_archive", False),
                    protected=bool(channel_data.get("adult", False)),
                    url=channel_data["href"])
                channel.data['epg_id'] = channel_data["epg_id"]
                groups[str(channel_data["group_id"])].channels[
                    channel.cid] = channels[channel.cid] = channel
        return groups
Example #25
0
    def get_epg(self, cid):
        # type: (str) -> OrderedDict[int, Program]
        programs = self.get_epg_gh(self.channels[cid])
        if len(programs):
            return programs

        if self._open_epg_cids is None:
            try:
                url = "https://iptv.kartina.tv/api/json/open_epg?get=channels"
                request = self.prepare_request(url)
                response = self.send_parallel_requests([request])[url]
                is_error, error = Api.is_error_response(response)
                if not is_error:
                    self._open_epg_cids = []
                    for entry in response["channels"]:
                        self._open_epg_cids.append(entry["id"])
                else:
                    self._open_epg_cids = []
            except:
                self._open_epg_cids = []

        if cid not in self._open_epg_cids:
            return self.get_real_epg(cid)

        requests = []
        days = (self.archive_ttl / DAY) + 2
        while days % self.API_REQUESTS_NUM_THREADS:
            days += 1
        start = int(time_now() - self.archive_ttl)
        for i in range(days):
            day = format_date(start + (i * DAY), custom_format="%d%m%y")
            request = self.prepare_request(
                "https://iptv.kartina.tv/api/json/open_epg",
                payload={
                    "period": "day",
                    "cid": cid,
                    "dt": day
                })
            requests.append(request)

        results = self.send_parallel_requests(requests, 0.20,
                                              self.API_REQUESTS_NUM_THREADS)

        epg = dict()
        prev_ts = None
        for key in sorted(results.iterkeys()):
            response = results[key]
            is_error, error = Api.is_error_response(response)
            if not is_error:
                for entry in response["report"][0]["list"]:
                    title, descr = (entry["progname"] + "\n").split("\n", 1)
                    ts = int(entry["ts"])
                    epg[ts] = dict({
                        "time": ts,
                        "time_to": 0,
                        "duration": 0,
                        "name": title,
                        "descr": descr
                    })
                    if prev_ts is not None:
                        epg[prev_ts]["time_to"] = ts
                    prev_ts = ts
            else:
                log("error: %s" % error, xbmc.LOGDEBUG)

        programs = OrderedDict()
        prev = None  # type: Program
        for key in sorted(epg.iterkeys()):
            val = epg[key]
            program = Program(cid, self.channels[cid].gid, val["time"],
                              val["time_to"], val["name"], val["descr"],
                              self.channels[cid].archive)
            if prev is not None:
                program.prev_program = prev
                prev.next_program = program
            programs[program.ut_start] = prev = program
        return programs