diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index de04f93..5c1dc70 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -28,3 +28,19 @@ jobs: - name: Fail if autopep8 made changes if: steps.autopep8.outputs.exit-code == 2 run: exit 1 + + pylint: + runs-on: ubuntu-latest + name: pylint + steps: + - name: checkout + uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pylint + - name: Run pylint + id: pylint + run: pylint resources/lib diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000..6532dd6 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,45 @@ +[MAIN] + +# Add paths to the list of the source roots. Supports globbing patterns. The +# source root is an absolute path or a path relative to the current working +# directory used to determine a package namespace for modules located under the +# source root. +source-roots=resources/lib + + +[FORMAT] + +# Maximum number of characters on a single line. +max-line-length=173 + + +[MESSAGES CONTROL] + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once). You can also use "--disable=all" to +# disable everything first and then re-enable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use "--disable=all --enable=classes +# --disable=W". +disable= + consider-using-f-string, + consider-using-with, + dangerous-default-value, + import-error, + inconsistent-return-statements, + missing-class-docstring, + missing-function-docstring, + missing-module-docstring, + too-many-arguments, + too-many-branches, + too-many-instance-attributes, + too-many-locals, + too-many-nested-blocks, + too-many-public-methods, + too-many-return-statements, + too-many-statements, + unspecified-encoding, + use-implicit-booleaness-not-len, diff --git a/resources/lib/Addon.py b/resources/lib/addon.py similarity index 91% rename from resources/lib/Addon.py rename to resources/lib/addon.py index 1c3cda3..54a23a9 100644 --- a/resources/lib/Addon.py +++ b/resources/lib/addon.py @@ -1,17 +1,21 @@ +import re import sys -from Kodi import * import routing -settings_file = 'settings.json' -channel_map_file = 'channels.json' -search_history_file = 'search_history' +from directory import Directory +from kodi import Kodi +from orf_on import OrfOn + +SETTINGS_FILE = 'settings.json' +CHANNEL_MAP_FILE = 'channels.json' +SEARCH_HISTORY_FILE = 'search_history' route_plugin = routing.Plugin() kodi_worker = Kodi(route_plugin) if not sys.argv[0].startswith('plugin://' + kodi_worker.addon_id + '/dialog'): - channel_map, channel_map_cached = kodi_worker.get_cached_file(channel_map_file) - settings, settings_cached = kodi_worker.get_cached_file(settings_file) + channel_map, channel_map_cached = kodi_worker.get_cached_file(CHANNEL_MAP_FILE) + settings, settings_cached = kodi_worker.get_cached_file(SETTINGS_FILE) api = OrfOn(channel_map=channel_map, settings=settings, useragent=kodi_worker.useragent, kodi_worker=kodi_worker) api.set_pager_limit(kodi_worker.pager_limit) api.set_segments_behaviour(kodi_worker.use_segments) @@ -22,10 +26,10 @@ # Only overwrite if cache was invalidated if not channel_map_cached: - kodi_worker.save_json(channel_map, channel_map_file) + kodi_worker.save_json(channel_map, CHANNEL_MAP_FILE) if not settings_cached: - kodi_worker.save_json(settings, settings_file) + kodi_worker.save_json(settings, SETTINGS_FILE) @route_plugin.route('/') @@ -195,7 +199,7 @@ def get_search(): search_link = '/search/query' search_dir = Directory(kodi_worker.get_translation(30131, 'Enter search ...', '%s ...'), "", search_link, translator=kodi_worker) kodi_worker.render(search_dir) - directories = kodi_worker.get_stored_directories(search_history_file) + directories = kodi_worker.get_stored_directories(SEARCH_HISTORY_FILE) for directory in directories: kodi_worker.render(directory) kodi_worker.list_callback() @@ -237,7 +241,7 @@ def get_search_dialog(): def clear_search_history(): dialog = kodi_worker.get_progress_dialog(kodi_worker.get_translation(30132, 'Clearing search history')) dialog.update(0, kodi_worker.get_translation(30133, 'Clearing ...', '%s ...')) - kodi_worker.clear_stored_directories(search_history_file) + kodi_worker.clear_stored_directories(SEARCH_HISTORY_FILE) dialog.update(100, kodi_worker.get_translation(30134, 'Done')) dialog.close() @@ -247,19 +251,19 @@ def clear_cache(): dialog = kodi_worker.get_progress_dialog('Reloading cache') dialog.update(0, kodi_worker.get_translation(30136, 'Reloading cache ...', '%s ...')) kodi_worker.log("Reloading channel/settings cache", 'route') - tmp_channel_map, tmp_channel_map_cached = kodi_worker.get_cached_file(channel_map_file) - tmp_settings, tmp_settings_cached = kodi_worker.get_cached_file(settings_file) - kodi_worker.remove_file(settings_file) - kodi_worker.remove_file(channel_map_file) + tmp_channel_map, _ = kodi_worker.get_cached_file(CHANNEL_MAP_FILE) + tmp_settings, _ = kodi_worker.get_cached_file(SETTINGS_FILE) + kodi_worker.remove_file(SETTINGS_FILE) + kodi_worker.remove_file(CHANNEL_MAP_FILE) tmp_api = OrfOn(channel_map=tmp_channel_map, settings=tmp_settings, useragent=kodi_worker.useragent, kodi_worker=kodi_worker) tmp_api.channel_map = False tmp_api.settings = False dialog.update(33, kodi_worker.get_translation(30137, 'Loading channels')) tmp_channel_map = tmp_api.get_channel_map() - kodi_worker.save_json(tmp_channel_map, channel_map_file) + kodi_worker.save_json(tmp_channel_map, CHANNEL_MAP_FILE) dialog.update(66, kodi_worker.get_translation(30138, 'Loading settings')) tmp_settings = tmp_api.get_settings() - kodi_worker.save_json(tmp_settings, settings_file) + kodi_worker.save_json(tmp_settings, SETTINGS_FILE) dialog.update(100, kodi_worker.get_translation(30134, 'Done')) dialog.close() diff --git a/resources/lib/default.py b/resources/lib/default.py index 2c24f58..76a95e8 100644 --- a/resources/lib/default.py +++ b/resources/lib/default.py @@ -1,6 +1,6 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -import Addon +import addon -Addon.run() +addon.run() diff --git a/resources/lib/Directory.py b/resources/lib/directory.py similarity index 97% rename from resources/lib/Directory.py rename to resources/lib/directory.py index 5e580b7..0fcb85f 100644 --- a/resources/lib/Directory.py +++ b/resources/lib/directory.py @@ -3,7 +3,7 @@ class Directory: - def __init__(self, title, description, link, content_id="", content_type="", thumbnail="", backdrop="", poster="", source={}, translator=None, proxy=False): + def __init__(self, title, description, link, content_id="", content_type="", thumbnail="", backdrop="", poster="", source={}, translator=None): self.translator = translator self.title = title if description: @@ -70,8 +70,8 @@ def get_context_menu(self) -> list: def translate_string(self, translation_id, fallback, replace=None): if self.translator: return self.translator.get_translation(translation_id, fallback, replace) - else: - return fallback + + return fallback @staticmethod def build_meta(item) -> dict: @@ -245,8 +245,8 @@ def get_channel_logo(self): def get_resolution(self): if self.meta.get('uhd'): return 3840, 2160 - else: - return 1280, 720 + + return 1280, 720 def set_stream(self, sources): self.videos = sources @@ -266,8 +266,8 @@ def time(self): def get_description(self) -> str: if self.description is not None: return self.description - else: - return "" + + return "" def get_meta_description(self): meta_description = {} @@ -353,7 +353,7 @@ def get_cast(self): try: if part is not None and len(part) > 1: matches = re.findall(cast_extract_pattern, part[1], re.DOTALL) - for name, dirty_role, role in matches: + for name, _, role in matches: if name.strip() != "": if '\r\n' in name.strip() or 'Regie:' in name.strip(): break @@ -362,7 +362,7 @@ def get_cast(self): else: cast.append(name.strip()) return cast - except re.error as e: + except re.error: return cast def url(self) -> str: @@ -414,8 +414,8 @@ def debug(self): self.log('Thumbnail: %s' % self.thumbnail) self.log('Backdrop: %s' % self.backdrop) self.log('Poster: %s' % self.poster) - for item in self.meta: - self.log("%s: %s" % (item.capitalize().replace("_", " "), self.meta[item])) + for (key, value) in self.meta.items(): + self.log("%s: %s" % (key.capitalize().replace("_", " "), value)) for context_menu_item in self.context_menu: self.log('Context Menu Item: %s' % context_menu_item.get('title')) diff --git a/resources/lib/Kodi.py b/resources/lib/kodi.py similarity index 97% rename from resources/lib/Kodi.py rename to resources/lib/kodi.py index df1c694..d2750a2 100644 --- a/resources/lib/Kodi.py +++ b/resources/lib/kodi.py @@ -1,19 +1,19 @@ +import json +import os +import re +import sys +import time +from urllib.parse import unquote + import xbmcaddon from xbmc import PlayList, PLAYLIST_VIDEO, Player, Keyboard, executebuiltin, log, LOGDEBUG from xbmcgui import ListItem, Dialog, DialogProgress from xbmcaddon import Addon from xbmcplugin import addDirectoryItem, endOfDirectory, setContent, setResolvedUrl, addSortMethod, SORT_METHOD_VIDEO_TITLE, SORT_METHOD_DATE import xbmcvfs -import sys -import os -import time import inputstreamhelper -from urllib.parse import unquote -try: - from OrfOn import * -except ModuleNotFoundError: - from resources.lib.OrfOn import * +from directory import Directory class Kodi: @@ -58,8 +58,8 @@ def get_translation(self, translation_id, fallback, replace=None): if translation: if replace is not None: return replace % translation - else: - return translation + + return translation return fallback def is_geo_locked(self) -> bool: @@ -95,10 +95,7 @@ def render(self, item): list_item = self.render_video(item) link = item.url() route = self.plugin.url_for_path(link) - if self.use_segments and self.show_segments and item.has_segments(): - folder = True - else: - folder = False + folder = self.use_segments and self.show_segments and item.has_segments() addDirectoryItem(self.plugin.handle, url=route, listitem=list_item, isFolder=folder) else: list_item = self.render_directory(item) @@ -219,7 +216,7 @@ def render_video(self, teaser) -> ListItem: context_menu.append(self.build_context_menu(context_menu_item)) list_item.addContextMenuItems(context_menu, replaceItems=True) return list_item - elif not teaser.get_stream(): + if not teaser.get_stream(): Dialog().notification('No Stream available', 'Unable to find a stream for %s' % title, xbmcaddon.Addon().getAddonInfo('icon')) elif not is_helper.check_inputstream(): Dialog().notification('Inputstream Adaptive not available', 'Install Inputstream Adaptive and Inputstream Helper', xbmcaddon.Addon().getAddonInfo('icon')) @@ -259,8 +256,8 @@ def build_context_menu(self, item): route = self.plugin.url_for_path(item.get('url')) if item.get('type') == 'run': return item.get('title'), 'RunPlugin(%s)' % route - else: - return item.get('title'), 'Container.Update(%s)' % route + + return item.get('title'), 'Container.Update(%s)' % route def list_callback(self, content_type="movies", sort=False) -> None: if content_type: diff --git a/resources/lib/OrfOn.py b/resources/lib/orf_on.py similarity index 96% rename from resources/lib/OrfOn.py rename to resources/lib/orf_on.py index 88c1551..8b51556 100644 --- a/resources/lib/OrfOn.py +++ b/resources/lib/orf_on.py @@ -1,16 +1,14 @@ import json import re -from datetime import date, timedelta -try: - from Directory import * -except ModuleNotFoundError: - from resources.lib.Directory import * +from datetime import date, datetime, timedelta from urllib.request import Request as urllib_Request from urllib.request import urlopen as urllib_urlopen from urllib.error import HTTPError as urllib_HTTPError from urllib.error import URLError as urllib_URLError -from urllib.parse import urlparse, urlencode, quote_plus +from urllib.parse import quote_plus + +from directory import Directory class OrfOn: @@ -111,8 +109,8 @@ def is_geo_locked(self): def translate_string(self, translation_id, fallback, replace=None): if self.kodi_worker: return self.kodi_worker.get_translation(translation_id, fallback, replace) - else: - return fallback + + return fallback def set_pager_limit(self, limit): self.api_pager_limit = limit @@ -219,7 +217,7 @@ def get_channel_map(self) -> dict: def get_last_uploads(self, last_upload_range=12): current_date = datetime.now() - current_delta = (current_date - timedelta(hours=last_upload_range)) + current_delta = current_date - timedelta(hours=last_upload_range) today_filter = current_date.strftime("%Y-%m-%d") yesterday_filter = current_delta.strftime("%Y-%m-%d") @@ -249,7 +247,7 @@ def get_schedule_dates(self) -> tuple: day_items = [] filter_items = [] for day in range(replay_days): - days_before = (current_date - timedelta(days=day)) + days_before = current_date - timedelta(days=day) isodate = days_before.isoformat() prettydate = days_before.strftime("%A, %d.%m.%Y") day_items.append(prettydate) @@ -494,9 +492,9 @@ def load_stream_data(self, url) -> list: def get_preferred_source(self, item): if self.supported_delivery in item['sources']: for source in item['sources'][self.supported_delivery]: - for quality in self.quality_definitions: + for (quality, values) in self.quality_definitions.items(): if quality in source['quality_key']: - self.log("Found Stream %s" % self.quality_definitions[quality]['name']) + self.log("Found Stream %s" % values['name']) return source def render(self, data) -> list: @@ -572,23 +570,23 @@ def build(self, item) -> Directory: video_item = item['_embedded']['video_item']['_embedded']['item'] link = item['_embedded']['video_item']['_links']['self']['href'] return self.build_video(video_item, link) - elif 'sources' in item and 'segments' in item['_links']: + if 'sources' in item and 'segments' in item['_links']: link = item['_links']['segments']['href'] return self.build_video(item, link) - elif 'sources' in item and 'playlist' in item['_links']: + if 'sources' in item and 'playlist' in item['_links']: link = item['_links']['playlist']['href'] return self.build_video(item, link) - elif 'id' in item and 'type' in item: + if 'id' in item and 'type' in item: return self.build_directory(item) - elif 'id' in item and 'videos' in item: + if 'id' in item and 'videos' in item: return self.build_directory(item) - elif 'video_type' in item: + if 'video_type' in item: video_item = item link = item['_links']['self']['href'] return self.build_video(video_item, link) - else: - self.log("Unknown Type", 'error') - self.print_obj(item) + + self.log("Unknown Type", 'error') + self.print_obj(item) def build_directory(self, item) -> Directory: self.log("Building Directory %s (%s)" % (item['title'], item['id'])) @@ -619,17 +617,17 @@ def build_directory(self, item) -> Directory: if item_type == 'genre': link = "%s/profiles?limit=%d" % (link, self.api_pager_limit) return Directory(item['title'], description, link, item['id'], item['type'], banner, backdrop, poster, item, translator=self.kodi_worker) - elif item_id == 'lane': + if item_id == 'lane': return Directory(item['title'], description, link, item['id'], item['type'], banner, backdrop, poster, item, translator=self.kodi_worker) - elif item_id == 'highlights': + if item_id == 'highlights': return Directory(self.type_map['highlights'], description, link, item['id'], item['type'], banner, backdrop, poster, item, translator=self.kodi_worker) - elif item_id == 'genres': + if item_id == 'genres': return Directory(self.type_map['genres'], description, link, item['id'], item['type'], banner, backdrop, poster, item, translator=self.kodi_worker) - elif item_id == 'orflive': + if item_id == 'orflive': return Directory(self.type_map['orflive'], description, link, item['id'], item['type'], banner, backdrop, poster, item, translator=self.kodi_worker) - elif 'title' in item and item['title'] and 'type' in item: + if 'title' in item and item['title'] and 'type' in item: return Directory(item['title'], description, link, item['id'], item['type'], banner, backdrop, poster, item, translator=self.kodi_worker) - elif 'title' in item and item['title'] and 'children_count' in item: + if 'title' in item and item['title'] and 'children_count' in item: return Directory(item['title'], description, link, item['id'], 'directory', banner, backdrop, poster, item, translator=self.kodi_worker) def build_video(self, item, link) -> Directory: