Skip to content

Commit

Permalink
Add support for portal that works differently and show series menu if… (
Browse files Browse the repository at this point in the history
#20)

* Refactor menu option and add support for portals with different API calls and support for UpNext addon

* Fix subsitile search when episode played via UpNext
  • Loading branch information
rickeylohia authored Jul 16, 2024
1 parent 9f4cfd5 commit a6500bb
Show file tree
Hide file tree
Showing 18 changed files with 951 additions and 157 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/python-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ jobs:
basename build/*.zip
echo "ARTIFACT_NAME=$(basename build/*.zip)" >> $GITHUB_ENV
- name: Upload ${{ env.ARTIFACT_NAME }}
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: ${{ env.ARTIFACT_NAME }}
path: build/${{ env.ARTIFACT_NAME }}
Expand Down
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ build_dir = build

check: check-pylint

check-pylint:
check-pylint: clean
@echo "Running pylint"
$(PYTHON) -m pylint *.py lib/ tests/

test:
test: check
@echo "Running tests"
@mkdir $(KODI_PROFILE)/addon_data
@mkdir $(KODI_PROFILE)/addon_data/$(CURR_PROJECT_DIR)
Expand All @@ -22,7 +22,7 @@ test:
coverage run -m pytest -v tests --junitxml=test-results/junit.xml
@coverage xml

build: clean check test
build: test
@echo "Building new package"
@rm -rf $(build_dir)
@rm -rf resources/tokens
Expand Down
30 changes: 7 additions & 23 deletions addon.xml
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="plugin.video.stalkervod" name="Stalker VOD Client" version="0.0.7" provider-name="rickey">
<addon id="plugin.video.stalkervod" name="Stalker VOD Client" version="1.0.0" provider-name="rickey">
<requires>
<import addon="xbmc.python" version="3.0.1"/>
<import addon="script.module.requests" version="2.27.1"/>
</requires>
<extension point="xbmc.python.pluginsource" library="addon_entry.py">
<provides>video</provides>
</extension>
<extension point="xbmc.service" library="service_entry.py"/>
<extension point="xbmc.addon.metadata">
<reuselanguageinvoker>true</reuselanguageinvoker>
<platform>all</platform>
Expand All @@ -24,28 +25,11 @@
<screenshot>resources/media/screenshot_4.png</screenshot>
</assets>
<news>
v0.0.7 (2024-06-10)
- Bug fix

v0.0.6 (2024-03-28)
- Bug fix

v0.0.5 (2024-03-23)
- Fix for portals with no stalker_portal in context path
Please try re-entering the server address based on the format provided if it does not work after upgrading to v0.0.5
https://github.com/rickeylohia/plugin.video.stalkervod/issues/12

v0.0.4 (2024-01-20)
- Refactor, debug logs and fix for https://github.com/rickeylohia/plugin.video.stalkervod/issues/10

v0.0.3 (2024-01-05)
- Minor bug fixes and highlight favorites

v0.0.2 (2023-12-27)
- Add favorites context menu option and re-use existing token if possible

v0.0.1 (2023-10-22)
- Initial Release
v1.0.0 (2024-07-13)
- Major version release with refactor of the menu options. Support for portal with separate menu option for Series.
If using kodi Add to Favourites option, existing Favourites link would break.
Ability to use Portal Favorite option for both VOD and TV Channels
Add support for UpNext addon.
</news>
</extension>
</addon>
2 changes: 2 additions & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
ignore:
- "test_.*.py"
coverage:
status:
project:
Expand Down
305 changes: 263 additions & 42 deletions lib/addon.py

Large diffs are not rendered by default.

134 changes: 113 additions & 21 deletions lib/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,22 @@
import requests
from .globals import G
from .auth import Auth
from .utils import Logger
from .loggers import Logger


class Api:
"""API calls"""

@staticmethod
def __call_stalker_portal(params):
def __call_stalker_portal(params, return_response_body=True):
"""Method to call portal"""
response = Api.__call_stalker_portal_return_response(params)
if return_response_body:
return response.json()
return None

@staticmethod
def __call_stalker_portal_return_response(params):
"""Method to call portal"""
retries = 0
url = G.portal_config.portal_url
Expand All @@ -27,8 +35,10 @@ def __call_stalker_portal(params):
Logger.debug("Calling Stalker portal {} with params {}".format(url, json.dumps(params)))
response = requests.get(url=url,
headers={'Cookie': mac_cookie,
'SN': G.portal_config.serial_number,
'Authorization': 'Bearer ' + token,
'X-User-Agent': 'Model: MAG250; Link: WiFi', 'Referrer': referrer},
'X-User-Agent': 'Model: MAG250; Link: WiFi', 'Referrer': referrer,
'User-Agent': 'Mozilla/5.0 (QtEmbedded; U; Linux; C) AppleWebKit/533.3 (KHTML, like Gecko) MAG200 stbapp ver: 2 rev: 250 Safari/533.3'},
params=params,
timeout=30
)
Expand All @@ -37,42 +47,97 @@ def __call_stalker_portal(params):
if retries > 1:
auth.clear_cache()
retries += 1
return response.json()
return response

@staticmethod
def get_vod_categories():
"""Get video categories"""
params = {'type': 'vod', 'action': 'get_categories'}
return Api.__call_stalker_portal(params)['js']

@staticmethod
def get_series_categories():
"""Get video categories"""
params = {'type': 'series', 'action': 'get_categories'}
return Api.__call_stalker_portal(params).get('js', False)

@staticmethod
def get_tv_genres():
"""Get tv genres"""
params = {'type': 'itv', 'action': 'get_genres'}
return Api.__call_stalker_portal(params)['js']

@staticmethod
def remove_favorites(video_id):
def remove_favorites(video_id, _type):
"""Remove from favorites"""
params = {'type': 'vod', 'action': 'del_fav', 'video_id': video_id}
Api.__call_stalker_portal(params)
if _type == 'itv':
Api.__remove_tv_favorites(video_id)
else:
params = {'type': _type, 'action': 'del_fav', 'video_id': video_id}
Api.__call_stalker_portal(params, False)

@staticmethod
def add_favorites(video_id):
def add_favorites(video_id, _type):
"""Add to favorites"""
params = {'type': 'vod', 'action': 'set_fav', 'video_id': video_id}
Api.__call_stalker_portal(params)
if _type == 'itv':
Api.__add_tv_favorites(video_id)
else:
params = {'type': _type, 'action': 'set_fav', 'video_id': video_id}
Api.__call_stalker_portal(params, False)

@staticmethod
def __add_tv_favorites(video_id):
"""Add to tv favorites"""
params = {'type': 'itv', 'action': 'get_all_fav_channels'}
fav_channels = Api.__call_stalker_portal(params)['js']['data']
fav_ch = [video_id]
for fav_channel in fav_channels:
fav_ch.append(fav_channel['id'])
params = {'type': 'itv', 'action': 'set_fav', 'fav_ch': ','.join(fav_ch)}
Api.__call_stalker_portal(params, False)

@staticmethod
def __remove_tv_favorites(video_id):
"""Add to tv favorites"""
params = {'type': 'itv', 'action': 'get_all_fav_channels'}
fav_channels = Api.__call_stalker_portal(params)['js']['data']
fav_ch = []
for fav_channel in fav_channels:
if video_id != fav_channel['id']:
fav_ch.append(fav_channel['id'])
params = {'type': 'itv', 'action': 'set_fav', 'fav_ch': ','.join(fav_ch)}
Api.__call_stalker_portal(params, False)

@staticmethod
def get_vod_favorites(page):
"""Get favorites"""
params = {'type': 'vod', 'action': 'get_ordered_list', 'fav': 'true', 'sortby': 'added'}
params = {'type': 'vod', 'action': 'get_ordered_list', 'fav': '1', 'sortby': 'added'}
return Api.get_listing(params, page)

@staticmethod
def get_tv_channels(category_id, page):
def get_series_favorites(page):
"""Get favorites"""
params = {'type': 'series', 'action': 'get_ordered_list', 'fav': '1', 'sortby': 'added'}
return Api.get_listing(params, page)

@staticmethod
def get_tv_favorites(page):
"""Get favorites"""
params = {'type': 'itv', 'action': 'get_ordered_list', 'fav': '1', 'sortby': 'number'}
return Api.get_listing(params, page)

@staticmethod
def get_seasons(video_id):
"""Get favorites"""
params = {'type': 'series', 'action': 'get_ordered_list', 'movie_id': video_id, 'sortby': 'added'}
return Api.__call_stalker_portal(params)['js']

@staticmethod
def get_tv_channels(category_id, page, search_term, fav):
"""Get videos for a category"""
params = {'type': 'itv', 'action': 'get_ordered_list', 'genre': category_id, 'sortby': 'number'}
params = {'type': 'itv', 'action': 'get_ordered_list', 'genre': category_id, 'sortby': 'number', 'fav': fav}
if bool(search_term.strip()):
params.update({'search': search_term})
return Api.get_listing(params, page)

@staticmethod
Expand All @@ -83,6 +148,14 @@ def get_videos(category_id, page, search_term, fav):
params.update({'search': search_term})
return Api.get_listing(params, page)

@staticmethod
def get_series(category_id, page, search_term, fav):
"""Get videos for a category"""
params = {'type': 'series', 'action': 'get_ordered_list', 'category': category_id, 'sortby': 'added', 'fav': fav}
if bool(search_term.strip()):
params.update({'search': search_term})
return Api.get_listing(params, page)

@staticmethod
def get_listing(params, page):
"""Generic method to get listing"""
Expand All @@ -99,19 +172,38 @@ def get_listing(params, page):
return {'max_page_items': max_page_items, 'total_items': total_items, 'data': videos}

@staticmethod
def get_vod_stream_url(video_id, series):
def get_vod_stream_url(video_id, series, cmd, use_cmd):
"""Get VOD stream url"""
stream_url = Api.__call_stalker_portal(
{'type': 'vod', 'action': 'create_link', 'cmd': '/media/' + video_id + '.mpg', 'series': str(series)}
)['js']['cmd']
Api.__call_stalker_portal({'type': 'stb', 'action': 'log', 'real_action': 'play', 'param': stream_url,
'content_id': video_id})
if use_cmd == '0':
response = Api.__get_vod_stream_url_video_id(video_id, series)
if response.status_code != 200:
stream_url = Api.__get_vod_stream_url_cmd(cmd, series)
else:
stream_url = response.json()['js']['cmd']
else:
stream_url = Api.__get_vod_stream_url_cmd(cmd, series)
if stream_url.find(' ') != -1:
stream_url = stream_url[(stream_url.find(' ') + 1):]
# Api.__call_stalker_portal({'type': 'stb', 'action': 'log', 'real_action': 'play', 'param': stream_url, 'content_id': video_id})
return stream_url

@staticmethod
def __get_vod_stream_url_cmd(cmd, series):
"""Get VOD stream url"""
return Api.__call_stalker_portal({'type': 'vod', 'action': 'create_link', 'cmd': cmd, 'series': str(series)})['js']['cmd']

@staticmethod
def __get_vod_stream_url_video_id(video_id, series):
"""Get VOD stream url"""
return Api.__call_stalker_portal_return_response({'type': 'vod', 'action': 'create_link', 'cmd': '/media/' + video_id + '.mpg', 'series': str(series)}
)

@staticmethod
def get_tv_stream_url(cmd):
"""Get TV Channel stream url"""
stream_url = Api.__call_stalker_portal(
cmd = Api.__call_stalker_portal(
{'type': 'itv', 'action': 'create_link', 'cmd': cmd}
)['js']['cmd']
return stream_url
if cmd.find(' ') != -1:
cmd = cmd[(cmd.find(' ') + 1):]
return cmd
16 changes: 9 additions & 7 deletions lib/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import xbmcvfs
import xbmcgui
from .globals import G
from .utils import Logger
from .loggers import Logger


@dataclasses.dataclass
Expand Down Expand Up @@ -39,8 +39,8 @@ def get_token(self, refresh_token):
self.clear_cache()
Logger.debug('Getting token from {}'.format(self.__url))
response = requests.get(url=self.__url,
headers={'Cookie': self.__mac_cookie, 'X-User-Agent': 'Model: MAG250; Link: WiFi',
'Referrer': self.__referrer},
headers={'Cookie': self.__mac_cookie, 'X-User-Agent': 'Model: MAG250; Link: WiFi', 'Referrer': self.__referrer,
'User-Agent': 'Mozilla/5.0 (QtEmbedded; U; Linux; C) AppleWebKit/533.3 (KHTML, like Gecko) MAG200 stbapp ver: 2 rev: 250 Safari/533.3'},
params={'type': 'stb', 'action': 'handshake'},
timeout=30
)
Expand All @@ -64,8 +64,9 @@ def __refresh_token(self):
"""Refresh token"""
Logger.debug('Refreshing token')
requests.get(url=self.__url,
headers={'Cookie': self.__mac_cookie, 'Authorization': 'Bearer ' + self.__token.value,
'X-User-Agent': 'Model: MAG250; Link: WiFi', 'Referrer': self.__referrer},
headers={'Cookie': self.__mac_cookie, 'SN': G.portal_config.serial_number, 'Authorization': 'Bearer ' + self.__token.value,
'X-User-Agent': 'Model: MAG250; Link: WiFi', 'Referrer': self.__referrer,
'User-Agent': 'Mozilla/5.0 (QtEmbedded; U; Linux; C) AppleWebKit/533.3 (KHTML, like Gecko) MAG200 stbapp ver: 2 rev: 250 Safari/533.3'},
params={
'type': 'stb',
'action': 'get_profile',
Expand All @@ -88,8 +89,9 @@ def __refresh_token(self):
timeout=30
)
requests.get(url=self.__url,
headers={'Cookie': self.__mac_cookie, 'Authorization': 'Bearer ' + self.__token.value,
'X-User-Agent': 'Model: MAG250; Link: WiFi', 'Referrer': self.__referrer},
headers={'Cookie': self.__mac_cookie, 'SN': G.portal_config.serial_number, 'Authorization': 'Bearer ' + self.__token.value,
'X-User-Agent': 'Model: MAG250; Link: WiFi', 'Referrer': self.__referrer,
'User-Agent': 'Mozilla/5.0 (QtEmbedded; U; Linux; C) AppleWebKit/533.3 (KHTML, like Gecko) MAG200 stbapp ver: 2 rev: 250 Safari/533.3'},
params={
'type': 'watchdog', 'action': 'get_events',
'init': '0', 'cur_play_type': '1', 'event_active_id': '0'
Expand Down
11 changes: 7 additions & 4 deletions lib/globals.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import dataclasses
import xbmcaddon
import xbmcvfs
from .utils import Logger
from .loggers import Logger


@dataclasses.dataclass
Expand All @@ -21,6 +21,7 @@ class PortalConfig:
serial_number: str = None
portal_base_url: str = None
server_address: str = None
alternative_context_path: bool = False


@dataclasses.dataclass
Expand Down Expand Up @@ -65,6 +66,7 @@ def init_globals(self):
self.portal_config.device_id_2 = self.__addon.getSetting('device_id_2')
self.portal_config.signature = self.__addon.getSetting('signature')
self.portal_config.serial_number = self.__addon.getSetting('serial_number')
self.portal_config.alternative_context_path = self.__addon.getSetting('alternative_context_path') == 'true'
self.__set_portal_addresses()

def get_handle(self):
Expand Down Expand Up @@ -92,11 +94,12 @@ def __set_portal_addresses(self):

def get_portal_url(self):
"""Get portal url"""
portal_url = self.portal_config.portal_base_url + '/stalker_portal/server/load.php'
context_path = '/portal.php' if self.portal_config.alternative_context_path else '/server/load.php'
portal_url = self.portal_config.portal_base_url + '/stalker_portal' + context_path
if self.portal_config.server_address.endswith('/c/'):
portal_url = self.portal_config.server_address.replace('/c/', '') + '/server/load.php'
portal_url = self.portal_config.server_address.replace('/c/', '') + context_path
elif self.portal_config.server_address.endswith('/c'):
portal_url = self.portal_config.server_address.replace('/c', '') + '/server/load.php'
portal_url = self.portal_config.server_address.replace('/c', '') + context_path
return portal_url


Expand Down
Loading

0 comments on commit a6500bb

Please sign in to comment.