Several improvements for the Google integrations.

1. Improved documentation. Every plugin now reports the exact steps to
   get the integration up and running with the right API scopes.

2. All Google plugins now have a standard process to get (and reuse) the
   client secret. Except for PubSub, Translate and Maps (which have
   their own flows), all the Google plugins now read the client secrets
   from `<WORKDIR>/credentials/google/client_secret.json` by default.

3. Black/LINT for some of those plugins, which hadn't been touched in a
   while.

4. The interface to pass API scopes is now leaner. It's now possible to
   pass a scope directly as e.g. `calendar.readonly` rather than
   `https://www.googleapis.com/auth/calendar.readonly`.

5. Improved the logic to retrieve the right scope tokens file. If e.g.
   an integration requires the role `A`, and a credentials file exists
   for the roles `A` and `B`, then this file will be used rather than
   prompting the user to authenticate again.
This commit is contained in:
Fabio Manganiello 2023-10-01 15:37:20 +02:00
parent 5ca3757834
commit 2aefc4e5c8
Signed by: blacklight
GPG key ID: D90FBA7F76362774
10 changed files with 401 additions and 120 deletions

View file

@ -1,56 +1,93 @@
from typing import Collection, Optional
from platypush.plugins import Plugin
class GooglePlugin(Plugin):
"""
Executes calls to the Google APIs using the google-api-python-client.
Integrates with the Google APIs using the google-api-python-client.
This class is extended by ``GoogleMailPlugin``, ``GoogleCalendarPlugin`` etc.
In order to use Google services (like GMail, Maps, Calendar etc.) with
your account you need to:
1. Create your Google application, if you don't have one already, on
the developers console, https://console.developers.google.com
the `developers console <https://console.developers.google.com>`_.
2. Click on "Credentials", then "Create credentials" -> "OAuth client ID"
2. You may have to explicitly enable your user to use the app if the app
is created in test mode. Go to "OAuth consent screen" and add your user's
email address to the list of authorized users.
3 Select "Other", enter whichever description you like, and create
3. Select the scopes that you want to enable for your application, depending
on the integrations that you want to use.
See https://developers.google.com/identity/protocols/oauth2/scopes
for a list of the available scopes.
4. Click on the "Download JSON" icon next to your newly created client ID
4. Click on "Credentials", then "Create credentials" -> "OAuth client ID".
5. Generate a credentials file for the needed scope::
5 Select "Desktop app", enter whichever name you like, and click "Create".
6. Click on the "Download JSON" icon next to your newly created client ID.
7. Generate a credentials file for the required scope:
.. code-block:: bash
mkdir -p <WORKDIR>/credentials/google
python -m platypush.plugins.google.credentials \
'https://www.googleapis.com/auth/gmail.compose' ~/client_secret.json
'calendar.readonly' \
<WORKDIR>/credentials/google/client_secret.json
"""
def __init__(self, scopes=None, **kwargs):
def __init__(
self,
scopes: Optional[Collection[str]] = None,
secrets_path: Optional[str] = None,
**kwargs
):
"""
Initialized the Google plugin with the required scopes.
:param scopes: List of required scopes
:type scopes: list
:param scopes: List of scopes required by the API.
See https://developers.google.com/identity/protocols/oauth2/scopes
for a list of the available scopes. Override it in your configuration
only if you need specific scopes that aren't normally required by the
plugin.
:param secrets_path: Path to the client secrets file.
You can create your secrets.json from https://console.developers.google.com.
Default: ``<PLATYPUSH_WORKDIR>/credentials/google/client_secret.json``.
"""
from platypush.plugins.google.credentials import get_credentials
from platypush.plugins.google.credentials import (
get_credentials,
default_secrets_file,
)
super().__init__(**kwargs)
self._scopes = scopes or []
self._secrets_path: str = secrets_path or default_secrets_file
if self._scopes:
scopes = ' '.join(sorted(self._scopes))
self.credentials = {scopes: get_credentials(scopes)}
scopes = " ".join(sorted(self._scopes))
try:
self.credentials = {
scopes: get_credentials(scopes, secrets_file=self._secrets_path)
}
except AssertionError as e:
self.logger.warning(str(e))
else:
self.credentials = {}
def get_service(self, service, version, scopes=None):
def get_service(
self, service: str, version: str, scopes: Optional[Collection[str]] = None
):
import httplib2
from apiclient import discovery
if scopes is None:
scopes = getattr(self, 'scopes', [])
scopes = getattr(self, "scopes", [])
scopes = ' '.join(sorted(scopes))
scopes = " ".join(sorted(scopes))
credentials = self.credentials[scopes]
http = credentials.authorize(httplib2.Http())
return discovery.build(service, version, http=http, cache_discovery=False)

View file

@ -6,11 +6,41 @@ from platypush.plugins.calendar import CalendarInterface
class GoogleCalendarPlugin(GooglePlugin, CalendarInterface):
"""
r"""
Google Calendar plugin.
In order to use this plugin:
1. Create your Google application, if you don't have one already, on
the `developers console <https://console.developers.google.com>`_.
2. You may have to explicitly enable your user to use the app if the app
is created in test mode. Go to "OAuth consent screen" and add your user's
email address to the list of authorized users.
3. Select the scopes that you want to enable for your application, depending
on the integrations that you want to use.
See https://developers.google.com/identity/protocols/oauth2/scopes
for a list of the available scopes.
4. Click on "Credentials", then "Create credentials" -> "OAuth client ID".
5 Select "Desktop app", enter whichever name you like, and click "Create".
6. Click on the "Download JSON" icon next to your newly created client ID.
7. Generate a credentials file for the required scope:
.. code-block:: bash
mkdir -p <WORKDIR>/credentials/google
python -m platypush.plugins.google.credentials \
'calendar.readonly' \
<WORKDIR>/credentials/google/client_secret.json
"""
scopes = ['https://www.googleapis.com/auth/calendar.readonly']
scopes = ['calendar.readonly']
def __init__(self, *args, **kwargs):
super().__init__(scopes=self.scopes, *args, **kwargs)

View file

@ -1,35 +1,93 @@
import argparse
import httplib2
import os
import re
import sys
import textwrap as tw
from typing import List, Optional
import httplib2
from oauth2client import client
from oauth2client import tools
from oauth2client.file import Storage
from platypush.config import Config
def get_credentials_filename(*scopes):
from platypush.config import Config
credentials_dir = os.path.join(Config.get_workdir(), "credentials", "google")
default_secrets_file = os.path.join(credentials_dir, "client_secret.json")
"""Default path for the Google API client secrets file"""
scope_name = '-'.join([scope.split('/')[-1] for scope in scopes])
credentials_dir = os.path.join(
Config.get('workdir'), 'credentials', 'google')
def _parse_scopes(*scopes: str) -> List[str]:
return sorted(
{
t.split("/")[-1].strip()
for scope in scopes
for t in re.split(r"[\s,]", scope)
if t
}
)
def get_credentials_filename(*scopes: str):
parsed_scopes = _parse_scopes(*scopes)
scope_name = "-".join([scope.split("/")[-1] for scope in parsed_scopes])
os.makedirs(credentials_dir, exist_ok=True)
return os.path.join(credentials_dir, scope_name + '.json')
matching_scope_file = next(
iter(
os.path.join(credentials_dir, scopes_file)
for scopes_file in {
os.path.basename(file)
for file in os.listdir(credentials_dir)
if file.endswith(".json")
}
if not set(parsed_scopes).difference(
set(scopes_file.split(".json")[0].split("-"))
)
),
None,
)
if matching_scope_file:
return matching_scope_file
return os.path.join(credentials_dir, scope_name + ".json")
def get_credentials(scope):
credentials_file = get_credentials_filename(*sorted(scope.split(' ')))
if not os.path.exists(credentials_file):
raise RuntimeError(('Credentials file {} not found. Generate it through:\n' +
'\tpython -m platypush.plugins.google.credentials "{}" ' +
'<path to client_secret.json>\n' +
'\t\t[--auth_host_name AUTH_HOST_NAME]\n' +
'\t\t[--noauth_local_webserver]\n' +
'\t\t[--auth_host_port [AUTH_HOST_PORT [AUTH_HOST_PORT ...]]]\n' +
'\t\t[--logging_level [DEBUG,INFO,WARNING,ERROR,CRITICAL]]\n').
format(credentials_file, scope))
def get_credentials(scope: str, secrets_file: Optional[str] = None):
scopes = _parse_scopes(scope)
credentials_file = get_credentials_filename(*scopes)
# If we don't have a credentials file for the required set of scopes, but we have a secrets file,
# then try and generate the credentials file from the stored secrets.
if (
not os.path.isfile(credentials_file)
and secrets_file
and os.path.isfile(secrets_file)
):
# If DISPLAY or BROWSER are set, then we can open the authentication URL in the browser.
# Otherwise, we'll have to use the --noauth_local_webserver flag and copy/paste the URL
args = (
["--noauth_local_webserver"]
if not (os.getenv("DISPLAY") or os.getenv("BROWSER"))
else []
)
generate_credentials(secrets_file, scope, *args)
assert os.path.isfile(credentials_file), tw.dedent(
f"""
Credentials file {credentials_file} not found. Generate it through:
python -m platypush.plugins.google.credentials "{','.join(scopes)}" /path/to/client_secret.json
[--auth_host_name AUTH_HOST_NAME]
[--noauth_local_webserver]
[--auth_host_port [AUTH_HOST_PORT [AUTH_HOST_PORT ...]]]
[--logging_level [DEBUG,INFO,WARNING,ERROR,CRITICAL]]
Specify --noauth_local_webserver if you're running this script on a headless machine.
You will then get an authentication URL on the logs.
Otherwise, the URL will be opened in the available browser.
"""
)
store = Storage(credentials_file)
credentials = store.get()
@ -40,16 +98,20 @@ def get_credentials(scope):
return credentials
def generate_credentials(client_secret_path, scope):
credentials_file = get_credentials_filename(*sorted(scope.split(' ')))
def generate_credentials(client_secret_path: str, scope: str, *args: str):
scopes = _parse_scopes(scope)
credentials_file = get_credentials_filename(*scopes)
store = Storage(credentials_file)
scope = ' '.join(
f'https://www.googleapis.com/auth/{scope}' for scope in _parse_scopes(scope)
)
flow = client.flow_from_clientsecrets(client_secret_path, scope)
flow.user_agent = 'Platypush'
flow.access_type = 'offline'
flags = argparse.ArgumentParser(parents=[tools.argparser]).parse_args()
flow.user_agent = "Platypush"
flow.access_type = "offline" # type: ignore
flags = argparse.ArgumentParser(parents=[tools.argparser]).parse_args(args) # type: ignore
tools.run_flow(flow, store, flags)
print('Storing credentials to ' + credentials_file)
print("Storing credentials to", credentials_file)
def main():
@ -57,23 +119,29 @@ def main():
Generates a Google API credentials file given client secret JSON and scopes.
Usage::
python -m platypush.plugins.google.credentials [client_secret.json location] [comma-separated list of scopes]
python -m platypush.plugins.google.credentials \
[spaces/comma-separated list of scopes] \
[client_secret.json location]
"""
scope = sys.argv.pop(1) if len(sys.argv) > 1 \
else input('Space separated list of OAuth scopes: ')
args = sys.argv[1:]
scope = (
args.pop(0) if args else input("Space/comma separated list of OAuth scopes: ")
).strip()
client_secret_path = os.path.expanduser(
sys.argv.pop(1) if len(sys.argv) > 1
else input('Google credentials JSON file location: '))
if args:
client_secret_path = args.pop(0)
elif os.path.isfile(default_secrets_file):
client_secret_path = default_secrets_file
else:
client_secret_path = input("Google credentials JSON file location: ")
# Uncomment to force headless (no browser spawned) authentication
# sys.argv.append('--noauth_local_webserver')
generate_credentials(client_secret_path, scope)
client_secret_path = os.path.abspath(os.path.expanduser(client_secret_path)).strip()
generate_credentials(client_secret_path, scope, *args)
if __name__ == '__main__':
if __name__ == "__main__":
main()
# vim:sw=4:ts=4:et:

View file

@ -8,8 +8,36 @@ from platypush.message.response.google.drive import GoogleDriveFile
class GoogleDrivePlugin(GooglePlugin):
"""
r"""
Google Drive plugin.
1. Create your Google application, if you don't have one already, on
the `developers console <https://console.developers.google.com>`_.
2. You may have to explicitly enable your user to use the app if the app
is created in test mode. Go to "OAuth consent screen" and add your user's
email address to the list of authorized users.
3. Select the scopes that you want to enable for your application, depending
on the integrations that you want to use.
See https://developers.google.com/identity/protocols/oauth2/scopes
for a list of the available scopes.
4. Click on "Credentials", then "Create credentials" -> "OAuth client ID".
5 Select "Desktop app", enter whichever name you like, and click "Create".
6. Click on the "Download JSON" icon next to your newly created client ID.
7. Generate a credentials file for the required scope:
.. code-block:: bash
mkdir -p <WORKDIR>/credentials/google
python -m platypush.plugins.google.credentials \
'drive,drive.appfolder,drive.photos.readonly' \
<WORKDIR>/credentials/google/client_secret.json
"""
scopes = [
@ -21,7 +49,7 @@ class GoogleDrivePlugin(GooglePlugin):
def __init__(self, *args, **kwargs):
super().__init__(scopes=self.scopes, *args, **kwargs)
def get_service(self, **kwargs):
def get_service(self, **_):
return super().get_service(service='drive', version='v3')
# noinspection PyShadowingBuiltins
@ -85,7 +113,7 @@ class GoogleDrivePlugin(GooglePlugin):
else:
filter += ' '
filter += "'{}' in parents".format(folder_id)
filter += f"'{folder_id}' in parents"
while True:
results = (
@ -216,7 +244,7 @@ class GoogleDrivePlugin(GooglePlugin):
while not done:
status, done = downloader.next_chunk()
self.logger.info('Download progress: {}%'.format(status.progress()))
self.logger.info('Download progress: %s%%', status.progress())
with open(path, 'wb') as f:
f.write(fh.getbuffer().tobytes())
@ -269,8 +297,8 @@ class GoogleDrivePlugin(GooglePlugin):
add_parents: Optional[List[str]] = None,
remove_parents: Optional[List[str]] = None,
mime_type: Optional[str] = None,
starred: bool = None,
trashed: bool = None,
starred: Optional[bool] = None,
trashed: Optional[bool] = None,
) -> GoogleDriveFile:
"""
Update the metadata or the content of a file.

View file

@ -3,8 +3,45 @@ from platypush.plugins.google import GooglePlugin
class GoogleFitPlugin(GooglePlugin):
"""
r"""
Google Fit plugin.
In order to use this plugin:
1. Create your Google application, if you don't have one already, on
the `developers console <https://console.developers.google.com>`_.
2. You may have to explicitly enable your user to use the app if the app
is created in test mode. Go to "OAuth consent screen" and add your user's
email address to the list of authorized users.
3. Select the scopes that you want to enable for your application, depending
on the integrations that you want to use.
See https://developers.google.com/identity/protocols/oauth2/scopes
for a list of the available scopes.
4. Click on "Credentials", then "Create credentials" -> "OAuth client ID".
5 Select "Desktop app", enter whichever name you like, and click "Create".
6. Click on the "Download JSON" icon next to your newly created client ID.
7. Generate a credentials file for the required scope:
.. code-block:: bash
$ mkdir -p <WORKDIR>/credentials/google
$ roles="
fitness.activity.read,
fitness.body.read,
fitness.body_temperature.read,
fitness.heart_rate.read,
fitness.sleep.read,
fitness.location.read
"
$ python -m platypush.plugins.google.credentials "$roles" \
<WORKDIR>/credentials/google/client_secret.json
"""
scopes = [

View file

@ -15,8 +15,38 @@ from platypush.plugins.google import GooglePlugin
class GoogleMailPlugin(GooglePlugin):
"""
GMail plugin. It allows you to programmatically compose and (TODO) get emails
r"""
GMail plugin. It allows you to programmatically compose and (TODO) get emails.
To use this plugin:
1. Create your Google application, if you don't have one already, on
the `developers console <https://console.developers.google.com>`_.
2. You may have to explicitly enable your user to use the app if the app
is created in test mode. Go to "OAuth consent screen" and add your user's
email address to the list of authorized users.
3. Select the scopes that you want to enable for your application, depending
on the integrations that you want to use.
See https://developers.google.com/identity/protocols/oauth2/scopes
for a list of the available scopes.
4. Click on "Credentials", then "Create credentials" -> "OAuth client ID".
5 Select "Desktop app", enter whichever name you like, and click "Create".
6. Click on the "Download JSON" icon next to your newly created client ID.
7. Generate a credentials file for the required scope:
.. code-block:: bash
mkdir -p <WORKDIR>/credentials/google
python -m platypush.plugins.google.credentials \
'gmail.modify' \
<WORKDIR>/credentials/google/client_secret.json
"""
scopes = ['https://www.googleapis.com/auth/gmail.modify']
@ -64,8 +94,7 @@ class GoogleMailPlugin(GooglePlugin):
content = fp.read()
if main_type == 'text':
# noinspection PyUnresolvedReferences
msg = mimetypes.MIMEText(content, _subtype=sub_type)
msg = MIMEText(str(content), _subtype=sub_type)
elif main_type == 'image':
msg = MIMEImage(content, _subtype=sub_type)
elif main_type == 'audio':

View file

@ -14,18 +14,24 @@ datetime_types = Union[str, int, float, datetime]
class GoogleMapsPlugin(GooglePlugin):
"""
Plugins that provides utilities to interact with Google Maps API services.
It requires you to create a Google application - you can create one at the
`developers console <https://console.developers.google.com>`_.
After that, you'll need to create a new API key from the _Credentials_ tab.
This integration doesn't require any additional scopes.
"""
scopes = []
def __init__(self, api_key, *args, **kwargs):
def __init__(self, api_key: str, **kwargs):
"""
:param api_key: Server-side API key to be used for the requests, get one at
https://console.developers.google.com
:type api_key: str
"""
super().__init__(scopes=self.scopes, *args, **kwargs)
super().__init__(scopes=self.scopes, **kwargs)
self.api_key = api_key
@action
@ -43,7 +49,7 @@ class GoogleMapsPlugin(GooglePlugin):
response = requests.get(
'https://maps.googleapis.com/maps/api/geocode/json',
params={
'latlng': '{},{}'.format(latitude, longitude),
'latlng': f'{latitude},{longitude}',
'key': self.api_key,
},
).json()
@ -59,9 +65,10 @@ class GoogleMapsPlugin(GooglePlugin):
if 'results' in response and response['results']:
result = response['results'][0]
self.logger.info(
'Google Maps geocode response for latlng ({},{}): {}'.format(
latitude, longitude, result
)
'Google Maps geocode response for latlng (%f,%f): %s',
latitude,
longitude,
result,
)
address['address'] = result['formatted_address'].split(',')[0]
@ -91,7 +98,7 @@ class GoogleMapsPlugin(GooglePlugin):
response = requests.get(
'https://maps.googleapis.com/maps/api/elevation/json',
params={
'locations': '{},{}'.format(latitude, longitude),
'locations': f'{latitude},{longitude}',
'key': self.api_key,
},
).json()
@ -192,16 +199,21 @@ class GoogleMapsPlugin(GooglePlugin):
"""
rs = requests.get(
'https://maps.googleapis.com/maps/api/distancematrix/json',
timeout=20,
params={
'origins': '|'.join(origins),
'destinations': '|'.join(destinations),
'units': units,
**(
{'departure_time': to_datetime(departure_time)}
{'departure_time': to_datetime(departure_time).isoformat()}
if departure_time
else {}
),
**({'arrival_time': to_datetime(arrival_time)} if arrival_time else {}),
**(
{'arrival_time': to_datetime(arrival_time).isoformat()}
if arrival_time
else {}
),
**({'avoid': '|'.join(avoid)} if avoid else {}),
**({'language': language} if language else {}),
**({'mode': mode} if mode else {}),

View file

@ -9,15 +9,18 @@ class GooglePubsubPlugin(Plugin):
Send messages over a Google pub/sub instance.
You'll need a Google Cloud active project and a set of credentials to use this plugin:
1. Create a project on the `Google Cloud console <https://console.cloud.google.com/projectcreate>`_ if
you don't have one already.
1. Create a project on the `Google Cloud console
<https://console.cloud.google.com/projectcreate>`_ if you don't have
one already.
2. In the `Google Cloud API console <https://console.cloud.google.com/apis/credentials/serviceaccountkey>`_
create a new service account key. Select "New Service Account", choose the role "Pub/Sub Editor" and leave
the key type as JSON.
2. In the `Google Cloud API console
<https://console.cloud.google.com/apis/credentials/serviceaccountkey>`_
create a new service account key. Select "New Service Account", choose
the role "Pub/Sub Editor" and leave the key type as JSON.
3. Download the JSON service credentials file. By default platypush will look for the credentials file under
~/.credentials/platypush/google/pubsub.json.
3. Download the JSON service credentials file. By default Platypush
will look for the credentials file under
``~/.credentials/platypush/google/pubsub.json``.
"""
@ -29,8 +32,8 @@ class GooglePubsubPlugin(Plugin):
def __init__(self, credentials_file: str = default_credentials_file, **kwargs):
"""
:param credentials_file: Path to the JSON credentials file for Google pub/sub (default:
~/.credentials/platypush/google/pubsub.json)
:param credentials_file: Path to the JSON credentials file for Google
pub/sub (default: ``~/.credentials/platypush/google/pubsub.json``)
"""
super().__init__(**kwargs)
self.credentials_file = credentials_file
@ -52,10 +55,13 @@ class GooglePubsubPlugin(Plugin):
"""
Sends a message to a topic
:param topic: Topic/channel where the message will be delivered. You can either specify the full topic name in
the format ``projects/<project_id>/topics/<topic_name>``, where ``<project_id>`` must be the ID of your
Google Pub/Sub project, or just ``<topic_name>`` - in such case it's implied that you refer to the
``topic_name`` under the ``project_id`` of your service credentials.
:param topic: Topic/channel where the message will be delivered. You
can either specify the full topic name in the format
``projects/<project_id>/topics/<topic_name>``, where
``<project_id>`` must be the ID of your Google Pub/Sub project, or
just ``<topic_name>`` - in such case it's implied that you refer to
the ``topic_name`` under the ``project_id`` of your service
credentials.
:param msg: Message to be sent. It can be a list, a dict, or a Message object
:param kwargs: Extra arguments to be passed to .publish()
"""
@ -65,8 +71,8 @@ class GooglePubsubPlugin(Plugin):
credentials = self.get_credentials(self.publisher_audience)
publisher = pubsub_v1.PublisherClient(credentials=credentials)
if not topic.startswith('projects/{}/topics/'.format(self.project_id)):
topic = 'projects/{}/topics/{}'.format(self.project_id, topic)
if not topic.startswith(f'projects/{self.project_id}/topics/'):
topic = f'projects/{self.project_id}/topics/{topic}'
try:
publisher.create_topic(topic)

View file

@ -1,7 +1,6 @@
import os
from typing import Optional, List
# noinspection PyPackageRequirements
from google.cloud import translate_v2 as translate
from platypush.message.response.translate import TranslateResponse
@ -13,16 +12,19 @@ class GoogleTranslatePlugin(Plugin):
Plugin to interact with the Google Translate API.
You'll need a Google Cloud active project and a set of credentials to use this plugin:
1. Create a project on the `Google Cloud console <https://console.cloud.google.com/projectcreate>`_ if
you don't have one already.
1. Create a project on the `Google Cloud console
<https://console.cloud.google.com/projectcreate>`_ if you don't
have one already.
2. In the menu navigate to the *Artificial Intelligence* section and select *Translations* and enable the API.
2. In the menu navigate to the *Artificial Intelligence* section and
select *Translations* and enable the API.
3. From the menu select *APIs & Services* and create a service account. You can leave role and permissions
empty.
3. From the menu select *APIs & Services* and create a service account.
You can leave role and permissions empty.
4. Create a new private JSON key for the service account and download it. By default platypush will look for the
credentials file under ``~/.credentials/platypush/google/translate.json``.
4. Create a new private JSON key for the service account and download
it. By default platypush will look for the credentials file under
``~/.credentials/platypush/google/translate.json``.
"""
@ -39,11 +41,14 @@ class GoogleTranslatePlugin(Plugin):
):
"""
:param target_language: Default target language (default: 'en').
:param credentials_file: Google service account JSON credentials file. If none is specified then the plugin will
search for the credentials file in the following order:
:param credentials_file: Google service account JSON credentials file.
If none is specified then the plugin will search for the credentials
file in the following order:
1. ``~/.credentials/platypush/google/translate.json``
2. Context from the ``GOOGLE_APPLICATION_CREDENTIALS`` environment variable.
2. Context from the ``GOOGLE_APPLICATION_CREDENTIALS``
environment variable.
"""
super().__init__(**kwargs)
self.target_language = target_language
@ -64,7 +69,7 @@ class GoogleTranslatePlugin(Plugin):
for i in range(min(pos, len(text) - 1), -1, -1):
if text[i] in [' ', '\t', ',', '.', ')', '>']:
return i
elif text[i] in ['(', '<']:
if text[i] in ['(', '<']:
return i - 1 if i > 0 else 0
return 0
@ -86,7 +91,6 @@ class GoogleTranslatePlugin(Plugin):
return parts
# noinspection PyShadowingBuiltins
@action
def translate(
self,
@ -121,7 +125,6 @@ class GoogleTranslatePlugin(Plugin):
if not result:
result = response
else:
# noinspection PyTypeChecker
result['translatedText'] += ' ' + response['translatedText']
return TranslateResponse(

View file

@ -1,10 +1,41 @@
from typing import Collection, Optional, Union
from platypush.plugins import action
from platypush.plugins.google import GooglePlugin
class GoogleYoutubePlugin(GooglePlugin):
"""
r"""
YouTube plugin.
Requirements:
1. Create your Google application, if you don't have one already, on
the `developers console <https://console.developers.google.com>`_.
2. You may have to explicitly enable your user to use the app if the app
is created in test mode. Go to "OAuth consent screen" and add your user's
email address to the list of authorized users.
3. Select the scopes that you want to enable for your application, depending
on the integrations that you want to use.
See https://developers.google.com/identity/protocols/oauth2/scopes
for a list of the available scopes.
4. Click on "Credentials", then "Create credentials" -> "OAuth client ID".
5 Select "Desktop app", enter whichever name you like, and click "Create".
6. Click on the "Download JSON" icon next to your newly created client ID.
7. Generate a credentials file for the required scope:
.. code-block:: bash
mkdir -p <WORKDIR>/credentials/google
python -m platypush.plugins.google.credentials \
'youtube.readonly' \
<WORKDIR>/credentials/google/client_secret.json
"""
scopes = ['https://www.googleapis.com/auth/youtube.readonly']
@ -19,28 +50,28 @@ class GoogleYoutubePlugin(GooglePlugin):
super().__init__(scopes=self.scopes, *args, **kwargs)
@action
def search(self, parts=None, query='', types=None, max_results=25, **kwargs):
def search(
self,
parts: Optional[Union[str, Collection[str]]] = None,
query: str = '',
types: Optional[Union[str, Collection[str]]] = None,
max_results: int = 25,
**kwargs
):
"""
Search for YouTube content.
:param parts: List of parts to get (default: snippet).
See the `Getting started - Part <https://developers.google.com/youtube/v3/getting-started#part>`_.
:type parts: list[str] or str
See the `Getting started - Part
<https://developers.google.com/youtube/v3/getting-started#part>`_.
:param query: Query string (default: empty string)
:type query: str
:param types: List of types to retrieve (default: video).
See the `Getting started - Resources <https://developers.google.com/youtube/v3/getting-started#resources>`_.
:type types: list[str] or str
See the `Getting started - Resources
<https://developers.google.com/youtube/v3/getting-started#resources>`_.
:param max_results: Maximum number of items that will be returned (default: 25).
:type max_results: int
:param kwargs: Any extra arguments that will be transparently passed to the YouTube API.
See the `Getting started - parameters
<https://developers.google.com/youtube/v3/docs/search/list#parameters>`_.
:return: A list of YouTube resources.
See the `Getting started - Resource
<https://developers.google.com/youtube/v3/docs/search#resource>`_.