Create a dedicated class to interact with PeerTube
The PeerTube class is responsible for providing methods to call easily the PeerTube REST APIs. Other changes: * the video filter is now also used when searching videos * in case of error when sending a request, the message from the response is displayed on the screen (even when listing the instances) * all the debug messages are now prefixed with the name of the add-on directly in kodi_utils: it allows an easier usage of this function anywhere in the add-on * first version of the design of the add-on added in contributing.md See merge request StCyr/plugin.video.peertube!14 for more information
This commit is contained in:
parent
142df05350
commit
074be7aa12
|
@ -24,6 +24,57 @@ The workflow is the following:
|
||||||
Note: more information about the pipeline is available in the
|
Note: more information about the pipeline is available in the
|
||||||
[CI file](.gitlab-ci.yml).
|
[CI file](.gitlab-ci.yml).
|
||||||
|
|
||||||
|
## Design
|
||||||
|
|
||||||
|
The add-on is based on the following python modules:
|
||||||
|
|
||||||
|
| Name | Description |
|
||||||
|
| ------ | ------ |
|
||||||
|
| main.py | Entry point of the add-on. |
|
||||||
|
| service.py | Service responsible for downloading torrent files in the background. |
|
||||||
|
| resources/lib/addon.py | It handles the routing and the interaction between the other modules. |
|
||||||
|
| resources/lib/peertube.py | Responsible for interacting with PeerTube. |
|
||||||
|
| resources/lib/kodi_utils.py | Provides utility functions to interact easily with Kodi. |
|
||||||
|
|
||||||
|
### main.py
|
||||||
|
|
||||||
|
The file `peertube.py` is currently being redesigned into the `main` module.
|
||||||
|
|
||||||
|
This module must be as short as possible (15 effective lines of code maximum)
|
||||||
|
to comply with Kodi add-on development best practices (checked by the
|
||||||
|
[Kodi add-on checker](https://github.com/xbmc/addon-check)).
|
||||||
|
|
||||||
|
### service.py
|
||||||
|
|
||||||
|
This module is being redesigned currently.
|
||||||
|
|
||||||
|
This module must be as short as possible (15 effective lines of code maximum)
|
||||||
|
to comply with Kodi add-on development best practices (checked by the
|
||||||
|
[Kodi add-on checker](https://github.com/xbmc/addon-check)).
|
||||||
|
|
||||||
|
### addon.py
|
||||||
|
|
||||||
|
This module does not exist yet.
|
||||||
|
|
||||||
|
### peertube.py
|
||||||
|
|
||||||
|
This file contains:
|
||||||
|
* the class PeerTube which provides simple method to send REST APIs to a
|
||||||
|
PeerTube instance
|
||||||
|
* the function `list_instances` which lists the PeerTube instances from
|
||||||
|
joinpeertube.org. The URL of the API used by this function and the structure
|
||||||
|
of the response in case of errors is different than the other PeerTube APIs
|
||||||
|
(which are sent to a specific instance) so it made sense to have it as a
|
||||||
|
dedicated function. If more instance-related API are used in the future, a
|
||||||
|
class could be created.
|
||||||
|
|
||||||
|
### kodi_utils.py
|
||||||
|
|
||||||
|
This module only contains functions (no classes) as no common data between them
|
||||||
|
was identified.
|
||||||
|
|
||||||
|
The functions must be sorted alphabetically to make the maintenance easier.
|
||||||
|
|
||||||
## Coding style
|
## Coding style
|
||||||
|
|
||||||
The code is still based on the design of the alpha version so the coding style
|
The code is still based on the design of the alpha version so the coding style
|
||||||
|
|
185
peertube.py
185
peertube.py
|
@ -19,16 +19,15 @@ except ImportError:
|
||||||
from urlparse import parse_qsl
|
from urlparse import parse_qsl
|
||||||
|
|
||||||
import AddonSignals # Module exists only in Kodi - pylint: disable=import-error
|
import AddonSignals # Module exists only in Kodi - pylint: disable=import-error
|
||||||
import requests
|
from requests.compat import urlencode
|
||||||
from requests.compat import urljoin, urlencode
|
|
||||||
import xbmc # Kodistubs for Leia is not compatible with python3 / pylint: disable=syntax-error
|
import xbmc # Kodistubs for Leia is not compatible with python3 / pylint: disable=syntax-error
|
||||||
import xbmcaddon
|
|
||||||
import xbmcgui # Kodistubs for Leia is not compatible with python3 / pylint: disable=syntax-error
|
import xbmcgui # Kodistubs for Leia is not compatible with python3 / pylint: disable=syntax-error
|
||||||
import xbmcplugin
|
import xbmcplugin
|
||||||
|
|
||||||
from resources.lib.kodi_utils import (
|
from resources.lib.kodi_utils import (
|
||||||
debug, get_property, get_setting, notif_error, notif_info, notif_warning,
|
debug, get_property, get_setting, notif_error, notif_info, notif_warning,
|
||||||
open_dialog, set_setting)
|
open_dialog, set_setting)
|
||||||
|
from resources.lib.peertube import PeerTube, list_instances
|
||||||
|
|
||||||
|
|
||||||
class PeertubeAddon():
|
class PeertubeAddon():
|
||||||
|
@ -45,10 +44,6 @@ class PeertubeAddon():
|
||||||
:param plugin, plugin_id: str, int
|
:param plugin, plugin_id: str, int
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# This step must be done first because the logging function requires
|
|
||||||
# the name of the add-on
|
|
||||||
self.addon_name = xbmcaddon.Addon().getAddonInfo('name')
|
|
||||||
|
|
||||||
# Save addon URL and ID
|
# Save addon URL and ID
|
||||||
self.plugin_url = plugin
|
self.plugin_url = plugin
|
||||||
self.plugin_id = plugin_id
|
self.plugin_id = plugin_id
|
||||||
|
@ -60,9 +55,6 @@ class PeertubeAddon():
|
||||||
# Get the number of videos to show per page
|
# Get the number of videos to show per page
|
||||||
self.items_per_page = int(get_setting('items_per_page'))
|
self.items_per_page = int(get_setting('items_per_page'))
|
||||||
|
|
||||||
# Get the video sort method
|
|
||||||
self.sort_method = get_setting('video_sort_method')
|
|
||||||
|
|
||||||
# Get the preferred resolution for video
|
# Get the preferred resolution for video
|
||||||
self.preferred_resolution = get_setting('preferred_resolution')
|
self.preferred_resolution = get_setting('preferred_resolution')
|
||||||
|
|
||||||
|
@ -71,14 +63,6 @@ class PeertubeAddon():
|
||||||
self.torrent_name = ''
|
self.torrent_name = ''
|
||||||
self.torrent_f = ''
|
self.torrent_f = ''
|
||||||
|
|
||||||
# Get the video filter from the settings that will be used when
|
|
||||||
# browsing the videos. The value from the settings is converted into
|
|
||||||
# one of the expected values by the REST APIs ("local" or "all-local")
|
|
||||||
if 'all-local' in get_setting('video_filter'):
|
|
||||||
self.video_filter = 'all-local'
|
|
||||||
else:
|
|
||||||
self.video_filter = 'local'
|
|
||||||
|
|
||||||
# Check whether libtorrent could be imported by the service. The value
|
# Check whether libtorrent could be imported by the service. The value
|
||||||
# of the associated property is retrieved only once and stored in an
|
# of the associated property is retrieved only once and stored in an
|
||||||
# attribute because libtorrent is imported only once at the beginning
|
# attribute because libtorrent is imported only once at the beginning
|
||||||
|
@ -87,51 +71,12 @@ class PeertubeAddon():
|
||||||
self.libtorrent_imported = \
|
self.libtorrent_imported = \
|
||||||
get_property('libtorrent_imported') == 'True'
|
get_property('libtorrent_imported') == 'True'
|
||||||
|
|
||||||
def debug(self, message):
|
# Create a PeerTube object to send requests: settings which are used
|
||||||
"""Log a debug message
|
# only by this object are directly retrieved from the settings
|
||||||
|
self.peertube = PeerTube(instance=self.selected_inst,
|
||||||
:param str message: Message to log (will be prefixed with the add-on
|
count=self.items_per_page,
|
||||||
name)
|
sort=get_setting('video_sort_method'),
|
||||||
"""
|
video_filter=get_setting('video_filter'))
|
||||||
debug('{0}: {1}'.format(self.addon_name, message))
|
|
||||||
|
|
||||||
def query_peertube(self, req):
|
|
||||||
"""
|
|
||||||
Issue a PeerTube API request and return the results
|
|
||||||
:param req: str
|
|
||||||
:result data: dict
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Send the PeerTube REST API request
|
|
||||||
self.debug('Issuing request {0}'.format(req))
|
|
||||||
response = requests.get(url=req)
|
|
||||||
data = response.json()
|
|
||||||
|
|
||||||
# Use Request.raise_for_status() to raise an exception if the HTTP
|
|
||||||
# request returned an unsuccessful status code.
|
|
||||||
try:
|
|
||||||
response.raise_for_status()
|
|
||||||
except requests.HTTPError as e:
|
|
||||||
notif_error(title='Communication error',
|
|
||||||
message='Error when sending request {}'.format(req))
|
|
||||||
# If the JSON contains an 'error' key, print it
|
|
||||||
error_details = data.get('error')
|
|
||||||
if error_details is not None:
|
|
||||||
self.debug('Error => "{}"'.format(data['error']))
|
|
||||||
raise e
|
|
||||||
|
|
||||||
# Try to get the number of elements in the response
|
|
||||||
results_found = data.get('total', None)
|
|
||||||
# If the information is available in the response, use it
|
|
||||||
if results_found is not None:
|
|
||||||
# Return when no results are found
|
|
||||||
if results_found == 0:
|
|
||||||
self.debug('No result found')
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
self.debug('Found {0} results'.format(results_found))
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
def create_list(self, lst, data_type, start):
|
def create_list(self, lst, data_type, start):
|
||||||
"""
|
"""
|
||||||
|
@ -227,9 +172,7 @@ class PeertubeAddon():
|
||||||
instance = 'https://{}'.format(instance)
|
instance = 'https://{}'.format(instance)
|
||||||
|
|
||||||
# Retrieve the information about the video
|
# Retrieve the information about the video
|
||||||
metadata = self.query_peertube(urljoin(instance,
|
metadata = self.peertube.get_video(video_id)
|
||||||
'/api/v1/videos/{}'
|
|
||||||
.format(video_id)))
|
|
||||||
|
|
||||||
# Depending if WebTorrent is enabled or not, the files corresponding to
|
# Depending if WebTorrent is enabled or not, the files corresponding to
|
||||||
# different resolutions available for a video may be stored in "files"
|
# different resolutions available for a video may be stored in "files"
|
||||||
|
@ -240,8 +183,7 @@ class PeertubeAddon():
|
||||||
else:
|
else:
|
||||||
files = metadata['streamingPlaylists'][0]['files']
|
files = metadata['streamingPlaylists'][0]['files']
|
||||||
|
|
||||||
self.debug(
|
debug('Looking for the best resolution matching the user preferences')
|
||||||
'Looking for the best resolution matching the user preferences')
|
|
||||||
|
|
||||||
current_res = 0
|
current_res = 0
|
||||||
higher_res = -1
|
higher_res = -1
|
||||||
|
@ -253,98 +195,33 @@ class PeertubeAddon():
|
||||||
if res == self.preferred_resolution:
|
if res == self.preferred_resolution:
|
||||||
# Stop directly when we find the exact same resolution as the
|
# Stop directly when we find the exact same resolution as the
|
||||||
# user's preferred one
|
# user's preferred one
|
||||||
self.debug('Found video with preferred resolution')
|
debug('Found video with preferred resolution')
|
||||||
torrent_url = f['torrentUrl']
|
torrent_url = f['torrentUrl']
|
||||||
break
|
break
|
||||||
elif res < self.preferred_resolution and res > current_res:
|
elif res < self.preferred_resolution and res > current_res:
|
||||||
# Otherwise, try to find the best one just below the user's
|
# Otherwise, try to find the best one just below the user's
|
||||||
# preferred one
|
# preferred one
|
||||||
self.debug('Found video with good lower resolution'
|
debug('Found video with good lower resolution ({0})'
|
||||||
'({0})'.format(f['resolution']['label']))
|
.format(f['resolution']['label']))
|
||||||
torrent_url = f['torrentUrl']
|
torrent_url = f['torrentUrl']
|
||||||
current_res = res
|
current_res = res
|
||||||
elif (res > self.preferred_resolution
|
elif (res > self.preferred_resolution
|
||||||
and (res < higher_res or higher_res == -1)):
|
and (res < higher_res or higher_res == -1)):
|
||||||
# In the worst case, we'll take the one just above the user's
|
# In the worst case, we'll take the one just above the user's
|
||||||
# preferred one
|
# preferred one
|
||||||
self.debug('Saving video with higher resolution ({0})'
|
debug('Saving video with higher resolution ({0}) as a possible'
|
||||||
'as a possible alternative'
|
' alternative'.format(f['resolution']['label']))
|
||||||
.format(f['resolution']['label']))
|
|
||||||
backup_url = f['torrentUrl']
|
backup_url = f['torrentUrl']
|
||||||
higher_res = res
|
higher_res = res
|
||||||
|
|
||||||
# When we didn't find a resolution equal or lower than the user's
|
# When we didn't find a resolution equal or lower than the user's
|
||||||
# preferred one, use the resolution just above the preferred one
|
# preferred one, use the resolution just above the preferred one
|
||||||
if not torrent_url:
|
if not torrent_url:
|
||||||
self.debug('Using video with higher resolution as alternative')
|
debug('Using video with higher resolution as alternative')
|
||||||
torrent_url = backup_url
|
torrent_url = backup_url
|
||||||
|
|
||||||
return torrent_url
|
return torrent_url
|
||||||
|
|
||||||
def build_video_rest_api_request(self, search, start):
|
|
||||||
"""Build the URL of an HTTP request using the PeerTube videos REST API.
|
|
||||||
|
|
||||||
The same function is used for browsing and searching videos.
|
|
||||||
|
|
||||||
:param search: keywords to search
|
|
||||||
:type search: string
|
|
||||||
:param start: offset
|
|
||||||
:type start: int
|
|
||||||
:return: the URL of the request
|
|
||||||
:rtype: str
|
|
||||||
|
|
||||||
Didn't yet find a correct way to do a search with a filter set to
|
|
||||||
local. Then if a search value is given it won't filter on local
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Common parameters of the request
|
|
||||||
params = {
|
|
||||||
'count': self.items_per_page,
|
|
||||||
'start': start,
|
|
||||||
'sort': self.sort_method
|
|
||||||
}
|
|
||||||
|
|
||||||
# Depending on the type of request (search or list videos), add
|
|
||||||
# specific parameters and define the API to use
|
|
||||||
if search is None:
|
|
||||||
# Video API does not provide "search" but provides "filter" so add
|
|
||||||
# it to the parameters
|
|
||||||
params.update({'filter': self.video_filter})
|
|
||||||
api_url = '/api/v1/videos'
|
|
||||||
else:
|
|
||||||
# Search API does not provide "filter" but provides "search" so add
|
|
||||||
# it to the parameters
|
|
||||||
params.update({'search': search})
|
|
||||||
api_url = '/api/v1/search/videos'
|
|
||||||
|
|
||||||
# Build the full URL of the request (instance + API + parameters)
|
|
||||||
req = '{0}?{1}'.format(urljoin(self.selected_inst, api_url),
|
|
||||||
urlencode(params))
|
|
||||||
|
|
||||||
return req
|
|
||||||
|
|
||||||
def build_browse_instances_rest_api_request(self, start):
|
|
||||||
"""Build the URL of an HTTP request using the PeerTube REST API to
|
|
||||||
browse the PeerTube instances.
|
|
||||||
|
|
||||||
:param start: offset
|
|
||||||
:type start: int
|
|
||||||
:return: the URL of the request
|
|
||||||
:rtype: str
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Create the parameters of the request
|
|
||||||
params = {
|
|
||||||
'count': self.items_per_page,
|
|
||||||
'start': start
|
|
||||||
}
|
|
||||||
|
|
||||||
# Join the base URL with the REST API and the parameters
|
|
||||||
req = 'https://instances.joinpeertube.org/api/v1/instances?{0}'\
|
|
||||||
.format(urlencode(params))
|
|
||||||
|
|
||||||
return req
|
|
||||||
|
|
||||||
def search_videos(self, start):
|
def search_videos(self, start):
|
||||||
"""
|
"""
|
||||||
Function to search for videos on a PeerTube instance and navigate
|
Function to search for videos on a PeerTube instance and navigate
|
||||||
|
@ -353,20 +230,17 @@ class PeertubeAddon():
|
||||||
:param start: string
|
:param start: string
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Show a 'Search videos' dialog
|
# Ask the user which keywords must be searched for
|
||||||
search = xbmcgui.Dialog().input(
|
keywords = xbmcgui.Dialog().input(
|
||||||
heading='Search videos on {}'.format(self.selected_inst),
|
heading='Search videos on {}'.format(self.selected_inst),
|
||||||
type=xbmcgui.INPUT_ALPHANUM)
|
type=xbmcgui.INPUT_ALPHANUM)
|
||||||
|
|
||||||
# Go back to main menu when user cancels
|
# Go back to main menu when user cancels
|
||||||
if not search:
|
if not keywords:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Create the PeerTube REST API request for searching videos
|
|
||||||
req = self.build_video_rest_api_request(search, start)
|
|
||||||
|
|
||||||
# Send the query
|
# Send the query
|
||||||
results = self.query_peertube(req)
|
results = self.peertube.search_videos(keywords, start)
|
||||||
|
|
||||||
# Exit directly when no result is found
|
# Exit directly when no result is found
|
||||||
if not results:
|
if not results:
|
||||||
|
@ -389,11 +263,8 @@ class PeertubeAddon():
|
||||||
:param start: string
|
:param start: string
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Create the PeerTube REST API request for listing videos
|
|
||||||
req = self.build_video_rest_api_request(None, start)
|
|
||||||
|
|
||||||
# Send the query
|
# Send the query
|
||||||
results = self.query_peertube(req)
|
results = self.peertube.list_videos(start)
|
||||||
|
|
||||||
# Create array of xmbcgui.ListItem's
|
# Create array of xmbcgui.ListItem's
|
||||||
listing = self.create_list(results, 'videos', start)
|
listing = self.create_list(results, 'videos', start)
|
||||||
|
@ -408,11 +279,8 @@ class PeertubeAddon():
|
||||||
:param start: str
|
:param start: str
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Create the PeerTube REST API request for browsing PeerTube instances
|
|
||||||
req = self.build_browse_instances_rest_api_request(start)
|
|
||||||
|
|
||||||
# Send the query
|
# Send the query
|
||||||
results = self.query_peertube(req)
|
results = list_instances(start)
|
||||||
|
|
||||||
# Create array of xmbcgui.ListItem's
|
# Create array of xmbcgui.ListItem's
|
||||||
listing = self.create_list(results, 'instances', start)
|
listing = self.create_list(results, 'instances', start)
|
||||||
|
@ -429,8 +297,7 @@ class PeertubeAddon():
|
||||||
:param data: dict
|
:param data: dict
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.debug(
|
debug('Received metadata_downloaded signal, will start playing media')
|
||||||
'Received metadata_downloaded signal, will start playing media')
|
|
||||||
self.play = 1
|
self.play = 1
|
||||||
self.torrent_f = data['file']
|
self.torrent_f = data['file']
|
||||||
|
|
||||||
|
@ -448,7 +315,7 @@ class PeertubeAddon():
|
||||||
' at {}'.format(self.HELP_URL))
|
' at {}'.format(self.HELP_URL))
|
||||||
return
|
return
|
||||||
|
|
||||||
self.debug('Starting torrent download ({0})'.format(torrent_url))
|
debug('Starting torrent download ({0})'.format(torrent_url))
|
||||||
|
|
||||||
# Start a downloader thread
|
# Start a downloader thread
|
||||||
AddonSignals.sendSignal('start_download', {'url': torrent_url})
|
AddonSignals.sendSignal('start_download', {'url': torrent_url})
|
||||||
|
@ -473,7 +340,7 @@ class PeertubeAddon():
|
||||||
xbmc.sleep(3000)
|
xbmc.sleep(3000)
|
||||||
|
|
||||||
# Pass the item to the Kodi player for actual playback.
|
# Pass the item to the Kodi player for actual playback.
|
||||||
self.debug('Starting video playback ({0})'.format(torrent_url))
|
debug('Starting video playback ({0})'.format(torrent_url))
|
||||||
play_item = xbmcgui.ListItem(path=self.torrent_f)
|
play_item = xbmcgui.ListItem(path=self.torrent_f)
|
||||||
xbmcplugin.setResolvedUrl(self.plugin_id, True, listitem=play_item)
|
xbmcplugin.setResolvedUrl(self.plugin_id, True, listitem=play_item)
|
||||||
|
|
||||||
|
@ -495,7 +362,7 @@ class PeertubeAddon():
|
||||||
message = '{0} is now the selected instance'.format(self.selected_inst)
|
message = '{0} is now the selected instance'.format(self.selected_inst)
|
||||||
notif_info(title='Current instance changed',
|
notif_info(title='Current instance changed',
|
||||||
message=message)
|
message=message)
|
||||||
self.debug(message)
|
debug(message)
|
||||||
|
|
||||||
def build_kodi_url(self, parameters):
|
def build_kodi_url(self, parameters):
|
||||||
"""Build a Kodi URL based on the parameters.
|
"""Build a Kodi URL based on the parameters.
|
||||||
|
|
|
@ -15,9 +15,11 @@ import xbmcgui # Kodistubs for Leia is not compatible with python3 / pylint: dis
|
||||||
def debug(message):
|
def debug(message):
|
||||||
"""Log a message in Kodi's log with the level xbmc.LOGDEBUG
|
"""Log a message in Kodi's log with the level xbmc.LOGDEBUG
|
||||||
|
|
||||||
:param str message: Message to log
|
:param str message: Message to log prefixed with the name of the add-on
|
||||||
|
(the name is hard-coded to avoid calling xbmcaddon each time since the name
|
||||||
|
should not change)
|
||||||
"""
|
"""
|
||||||
xbmc.log(message, xbmc.LOGDEBUG)
|
xbmc.log('[PeerTube] {}'.format(message), xbmc.LOGDEBUG)
|
||||||
|
|
||||||
def get_property(name):
|
def get_property(name):
|
||||||
"""Retrieve the value of a window property related to the add-on
|
"""Retrieve the value of a window property related to the add-on
|
||||||
|
|
|
@ -0,0 +1,192 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
PeerTube related classes and functions
|
||||||
|
|
||||||
|
Copyright (C) 2018 Cyrille Bollu
|
||||||
|
Copyright (C) 2021 Thomas Bétous
|
||||||
|
|
||||||
|
SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
See LICENSE.txt for more information.
|
||||||
|
"""
|
||||||
|
import requests
|
||||||
|
from requests.compat import urljoin
|
||||||
|
|
||||||
|
from resources.lib.kodi_utils import debug, get_setting, notif_error
|
||||||
|
|
||||||
|
|
||||||
|
class PeerTube:
|
||||||
|
"""A class to interact easily with PeerTube instances using REST APIs"""
|
||||||
|
|
||||||
|
def __init__(self, instance, sort, count, video_filter):
|
||||||
|
"""Constructor
|
||||||
|
|
||||||
|
:param str instance: URL of the PeerTube instance
|
||||||
|
:param str sort: sort method to use when listing items
|
||||||
|
:param int count: number of items to display
|
||||||
|
:param str sort: filter to apply when listing/searching videos
|
||||||
|
"""
|
||||||
|
self.instance = instance
|
||||||
|
|
||||||
|
self.list_settings = {
|
||||||
|
"sort": sort,
|
||||||
|
"count": count
|
||||||
|
}
|
||||||
|
|
||||||
|
# The value "video_filter" is directly retrieved from the settings so
|
||||||
|
# it must be converted into one of the expected values by the REST APIs
|
||||||
|
if 'all-local' in video_filter:
|
||||||
|
self.filter = 'all-local'
|
||||||
|
else:
|
||||||
|
self.filter = 'local'
|
||||||
|
|
||||||
|
def _request(self, method, url, params=None, data=None):
|
||||||
|
"""Call a REST API on the instance
|
||||||
|
|
||||||
|
:param str method: REST API method (get, post, put, delete, etc.)
|
||||||
|
:param str url: URL of the REST API endpoint relative to the PeerTube
|
||||||
|
instance
|
||||||
|
:param dict params: dict of the parameters to send in the request
|
||||||
|
:param dict data: dict of the data to send with the request
|
||||||
|
:return: the response as JSON data
|
||||||
|
:rtype: dict
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Build the URL of the REST API
|
||||||
|
api_url = urljoin("{}/api/v1/".format(self.instance), url)
|
||||||
|
|
||||||
|
# Send a request with a time-out of 5 seconds
|
||||||
|
response = requests.request(method=method,
|
||||||
|
url=api_url,
|
||||||
|
timeout=5,
|
||||||
|
params=params,
|
||||||
|
data=data)
|
||||||
|
|
||||||
|
json = response.json()
|
||||||
|
|
||||||
|
# Use Request.raise_for_status() to raise an exception if the HTTP
|
||||||
|
# request didn't succeed
|
||||||
|
try:
|
||||||
|
response.raise_for_status()
|
||||||
|
except requests.HTTPError as exception:
|
||||||
|
# Print in Kodi's log some information about the request
|
||||||
|
debug("Error when sending a {} request to {} with params={} and"
|
||||||
|
" data={}".format(method, url, params, data))
|
||||||
|
|
||||||
|
# Report the error to the user with a notification: if the response
|
||||||
|
# contains an "error" attribute, use it as error message, otherwise
|
||||||
|
# use a default message.
|
||||||
|
if "error" in json:
|
||||||
|
message = json["error"]
|
||||||
|
debug(message)
|
||||||
|
else:
|
||||||
|
message = ("No details returned by the server. Check the log"
|
||||||
|
" for more information.")
|
||||||
|
notif_error(title="Request error", message=message)
|
||||||
|
raise exception
|
||||||
|
|
||||||
|
return json
|
||||||
|
|
||||||
|
def _build_params(self, **kwargs):
|
||||||
|
"""Build the parameters to send with a request from the common settings
|
||||||
|
|
||||||
|
This method returns a dictionnary containing the common settings from
|
||||||
|
self.list_settings plus the arguments passed to this function. The keys
|
||||||
|
in the dictionnary will have the same name as the arguments passed to
|
||||||
|
this function.
|
||||||
|
|
||||||
|
:return: the common settings plus other parameters
|
||||||
|
:rtype: dict
|
||||||
|
"""
|
||||||
|
# Initialize the dict from the common settings (the common settings are
|
||||||
|
# copied otherwise any modification will also impact the attribute).
|
||||||
|
params = self.list_settings.copy()
|
||||||
|
|
||||||
|
# Add all the arguments to the dict
|
||||||
|
for param in kwargs:
|
||||||
|
params[param] = kwargs[param]
|
||||||
|
|
||||||
|
return params
|
||||||
|
|
||||||
|
def get_video(self, video_id):
|
||||||
|
"""Get the information of a video
|
||||||
|
|
||||||
|
:param str video_id: ID or UUID of the video
|
||||||
|
:return: the information of the video as returned by the REST API
|
||||||
|
:rtype: dict
|
||||||
|
"""
|
||||||
|
|
||||||
|
return self._request(method="GET", url="videos/{}".format(video_id))
|
||||||
|
|
||||||
|
def list_videos(self, start):
|
||||||
|
"""List the videos in the instance
|
||||||
|
|
||||||
|
:param str start: index of the first video to display
|
||||||
|
:return: the list of videos as returned by the REST API
|
||||||
|
:rtype: dict
|
||||||
|
"""
|
||||||
|
# Build the parameters that will be sent in the request
|
||||||
|
params = self._build_params(filter=self.filter, start=start)
|
||||||
|
|
||||||
|
return self._request(method="GET", url="videos", params=params)
|
||||||
|
|
||||||
|
def search_videos(self, keywords, start):
|
||||||
|
"""Search for videos on the instance and beyond.
|
||||||
|
|
||||||
|
:param str keywords: keywords to seach for
|
||||||
|
:param str start: index of the first video to display
|
||||||
|
:return: the videos matching the keywords as returned by the REST API
|
||||||
|
:rtype: dict
|
||||||
|
"""
|
||||||
|
# Build the parameters that will be send in the request
|
||||||
|
params = self._build_params(search=keywords,
|
||||||
|
filter=self.filter,
|
||||||
|
start=start)
|
||||||
|
|
||||||
|
return self._request(method="GET", url="search/videos", params=params)
|
||||||
|
|
||||||
|
|
||||||
|
def list_instances(start):
|
||||||
|
"""List all the peertube instances from joinpeertube.org
|
||||||
|
|
||||||
|
:param str start: index of the first instance to display
|
||||||
|
:return: the list of instances as returned by the REST API
|
||||||
|
:rtype: dict
|
||||||
|
"""
|
||||||
|
# URL of the REST API
|
||||||
|
api_url = "https://instances.joinpeertube.org/api/v1/instances"
|
||||||
|
# Build the parameters that will be sent in the request from the settings
|
||||||
|
params = {
|
||||||
|
"count": get_setting("items_per_page"),
|
||||||
|
"start": start
|
||||||
|
}
|
||||||
|
|
||||||
|
# Send a request with a time-out of 5 seconds
|
||||||
|
response = requests.get(url=api_url, timeout=5, params=params)
|
||||||
|
|
||||||
|
json = response.json()
|
||||||
|
|
||||||
|
# Use Request.raise_for_status() to raise an exception if the HTTP
|
||||||
|
# request didn't succeed
|
||||||
|
try:
|
||||||
|
response.raise_for_status()
|
||||||
|
except requests.HTTPError as exception:
|
||||||
|
# Print in Kodi's log some information about the request
|
||||||
|
debug("Error when getting the list of instances with params={}"
|
||||||
|
.format(params))
|
||||||
|
|
||||||
|
# Report the error to the user with a notification: use the details of
|
||||||
|
# the error if it exists in the response, otherwise use a default
|
||||||
|
# message.
|
||||||
|
try:
|
||||||
|
# Convert the reponse to a list to get the first error whatever its
|
||||||
|
# name. Then get the second element in the sublist which contains
|
||||||
|
# the details of the error.
|
||||||
|
message = list(json["errors"].items())[0][1]["msg"]
|
||||||
|
debug(message)
|
||||||
|
except KeyError:
|
||||||
|
message = ("No details returned by the server. Check the log"
|
||||||
|
" for more information.")
|
||||||
|
notif_error(title="Request error", message=message)
|
||||||
|
raise exception
|
||||||
|
|
||||||
|
return json
|
Loading…
Reference in New Issue