Skip to content

Commit

Permalink
[plugin.audio.bbcpodcasts] 2.0.6
Browse files Browse the repository at this point in the history
  • Loading branch information
Heckie75 committed Feb 10, 2024
1 parent a19439f commit 7470dd8
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 102 deletions.
5 changes: 4 additions & 1 deletion plugin.audio.bbcpodcasts/addon.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="plugin.audio.bbcpodcasts" name="BBC Podcasts" version="2.0.5" provider-name="Heckie">
<addon id="plugin.audio.bbcpodcasts" name="BBC Podcasts" version="2.0.6" provider-name="Heckie">
<requires>
<import addon="xbmc.python" version="3.0.0" />
<import addon="script.module.requests" version="2.25.1" />
Expand All @@ -23,6 +23,9 @@
<website>https://github.com/Heckie75/kodi-addon-bbc-podcasts</website>
<source>https://github.com/Heckie75/kodi-addon-bbc-podcasts/tree/main/plugin.audio.bbcpodcasts</source>
<news>
v2.0.6 (2024-02-10)
- Changed parser after changes on website, i.e. switched to API

v2.0.5 (2023-11-15)
- Changed parser after changes on website, i.e. fix paging

Expand Down
145 changes: 44 additions & 101 deletions plugin.audio.bbcpodcasts/resources/lib/bbcpodcasts/bbcpodcastsaddon.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
import json
import os
import re

import xbmcgui
import xbmcplugin
from bs4 import BeautifulSoup
from resources.lib.rssaddon.abstract_rss_addon import AbstractRssAddon
from resources.lib.rssaddon.http_client import http_request


class BbcPodcastsAddon(AbstractRssAddon):

BBC_BASE_URL = "https://www.bbc.co.uk"
PODCASTS_URL = "/sounds/podcasts"

API_BASE = "https://rms.api.bbc.co.uk"
API_SPEECH = "/v2/experience/inline/speech"
RSS_URL_PATTERN = "https://podcasts.files.bbci.co.uk/%s.rss"

def __init__(self, addon_handle) -> None:
Expand All @@ -35,12 +32,12 @@ def _make_root_menu(self) -> None:

xbmcplugin.endOfDirectory(self.addon_handle, updateListing=True)

def _make_menu(self, path: str, page=None) -> None:
def _make_menu(self, path: str, params: 'dict[str]') -> None:

if path.endswith("/"):
path = path[:-1]

entries = self._get_podcasts(path, page)
entries = self._get_podcasts(path, params)
for entry in entries:
self.add_list_item(entry, path)

Expand All @@ -51,122 +48,73 @@ def _make_menu(self, path: str, page=None) -> None:

xbmcplugin.endOfDirectory(self.addon_handle, updateListing=False)

def _get_podcasts(self, url: str, page=None) -> 'list[dict]':

def _parse_pager(soup: BeautifulSoup) -> int:

navs = soup.find_all("nav", attrs={"aria-label" : "Page Navigation"})
if len(navs) == 1:
lis = navs[0].find_all("li")
if len(lis) > 0:
if lis[-1].a:
m = re.match(".*page=([0-9]+).*", lis[-1].a["href"])
return m.group(1) if m else None
def _get_podcasts(self, url: str, params: 'dict[str]') -> 'list[dict]':

return None
url_param = "?%s" % "&".join(
["%s=%s" % (k, params[k][0]) for k in params]) if len(params) > 0 else ""
url = self.API_BASE + "/" + "/".join(url.split("/")[2:])
_data, _cookies = http_request(self.addon, url + url_param)
_json = json.loads(_data)

params = list()
params.append("sort=title")
if page:
params.append("page=%s" % page)
entries = list()

_url_param = "?%s" % "&".join(params) if len(params) > 0 else ""
for _d in _json["data"]:
if "uris" not in _d or "download" not in _d or not _d["download"] or "quality_variants" not in _d["download"]:
continue

_data, _cookies = http_request(self.addon, "%s%s%s" % (
self.BBC_BASE_URL, url, _url_param))
soup = BeautifulSoup(_data, 'html.parser')
has_media = [True for _quality in _d["download"]["quality_variants"]
if _d["download"]["quality_variants"][_quality]["file_url"]]
if not has_media:
continue

entries = list()
for _tile in soup.select("article"):
entry = self._parse_podcast_tile(_tile)
if entry:
entries.append(entry)

pager_next = _parse_pager(soup)
if pager_next:
_params = [
{
"page": pager_next,
}
]

entries.append({
entry = {
"path": "",
"name": self.addon.getLocalizedString(32003) % pager_next,
"icon": os.path.join(
self.addon_dir, "resources", "assets", "icon_arrow_right.png"),
"specialsort": "bottom",
"params": _params,
"name": _d["titles"]["primary"],
"icon": _d["image_url"].replace("{recipe}", "896x896"),
"type": "music",
"params": [
{
"rss": self.RSS_URL_PATTERN % _d["container"]["id"]
}
],
"node": []
})
}

entries.append(entry)

return entries

def _get_entries_for_categories(self) -> 'list[dict]':

_data, _cookies = http_request(self.addon,
"%s%s" % (self.BBC_BASE_URL, self.PODCASTS_URL))

soup = BeautifulSoup(_data, 'html.parser')
_json = None
for _script in soup.select("script"):
if "__PRELOADED_STATE__" not in str(_script):
continue

_s = str(_script).replace(
"<script> window.__PRELOADED_STATE__ = ", "")
_s = _s.replace("; </script>", "")
_json = json.loads(_s)

if not _json:
return list()
_data, _cookies = http_request(
self.addon, "%s%s" % (self.API_BASE, self.API_SPEECH))
_json = json.loads(_data)

result = list()

for _d in _json["modules"]["data"]:
for _d in _json["data"]:

if "title" not in _d or not "controls" in _d or not _d["controls"] or not "navigation" in _d["controls"] or _d["controls"]["navigation"]["id"] != "see_more":
if _d["type"] != "inline_display_module" or not _d["uris"]:
continue

_name = _d["title"]
_path = _d["controls"]["navigation"]["target"]["urn"]
_name = _d["title"].strip()
_path: str = _d["uris"]["pagination"]["uri"]
_path = _path.replace("{offset}", str(
_d["uris"]["pagination"]["offset"]))
_path = _path.replace("{limit}", str(
_d["uris"]["pagination"]["total"]))
_data = "data" in _d and type(_d["data"]) == list

if _path and _name and _data:
result.append({
"path": "%s/%s" % (self.PODCASTS_URL, _path.split(":")[-1]),
"path": "/__CATEGORIES__%s" % _path,
"name": _name,
"icon": os.path.join(self.addon_dir, "resources", "assets", "icon_category.png"),
"node": []
})

return result

def _parse_podcast_tile(self, tile) -> dict:

_more_episodes = tile.select("a.more-episodes")
if len(_more_episodes) != 1:
return None

_bid = json.loads(_more_episodes[0]["data-bbc-metadata"])["BID"]
_title = re.sub(" +", " ", tile.span.text).replace("\n", "").strip()
_img = tile.a.div.img["src"]

entry = {
"path": _bid,
"name": _title,
"icon": _img,
"type": "music",
"params": [
{
"rss": self.RSS_URL_PATTERN % _bid
}
],
"node": []
}

return entry

def check_disclaimer(self) -> bool:

if self.addon.getSetting("agreement") != "1":
Expand All @@ -184,13 +132,8 @@ def check_disclaimer(self) -> bool:

def route(self, path: str, url_params: 'dict[str]') -> None:

splitted_path = path.split("/")

if path in ["/"]:
self._make_root_menu()

elif len(splitted_path) in [4, 5] and path.startswith(self.PODCASTS_URL):
page = self.decode_param(
url_params["page"][0]) if "page" in url_params else None

self._make_menu(path, page=page)
elif "__CATEGORIES__" in path:
self._make_menu(path, url_params)

0 comments on commit 7470dd8

Please sign in to comment.