Added GMail actions support, solves #49

This commit is contained in:
Fabio Manganiello 2018-01-17 03:16:59 +01:00
parent d629fc897f
commit 834b700d5f
7 changed files with 219 additions and 8 deletions

View file

@ -112,15 +112,20 @@ class Request(Message):
path = m.group(2)
if context_argname in context:
try:
try:
context_value = eval("context['{}']{}".format(
context_argname, path if path else ''))
except:
context_value = eval(inner_expr)
if callable(context_value):
context_value = context_value()
if isinstance(context_value, datetime.date):
context_value = context_value.isoformat()
except: context_value = expr
except Exception as e:
logging.exception(e)
context_value = expr
parsed_value += prefix + (
json.dumps(context_value)
@ -128,7 +133,12 @@ class Request(Message):
or isinstance(context_value, dict)
else str(context_value))
else: parsed_value += prefix + expr
else:
try:
parsed_value += prefix + eval(inner_expr)
except Exception as e:
logging.exception(e)
parsed_value += prefix + expr
else:
parsed_value += value
value = ''
@ -162,7 +172,8 @@ class Request(Message):
def _thread_func(n_tries):
if self.action.startswith('procedure.'):
response = self._execute_procedure(n_tries=n_tries)
context['n_tries'] = n_tries
response = self._execute_procedure(**context)
self._send_response(response)
return response
else:

View file

@ -0,0 +1,36 @@
import os
from platypush.plugins import Plugin
from platypush.plugins.google.credentials import get_credentials
class GooglePlugin(Plugin):
"""
Executes calls to the Google APIs using the google-api-python-client.
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
2. Click on "Credentials", then "Create credentials" -> "OAuth client ID"
3 Select "Other", enter whichever description you like, and create
4. Click on the "Download JSON" icon next to your newly created client ID
5. Generate a credentials file for the needed scope:
$ python -m platypush.plugins.google.credentials 'https://www.googleapis.com/auth/gmail.compose' ~/client_secret.json
"""
def __init__(self, scopes, *args, **kwargs):
super().__init__(*args, **kwargs)
self.credentials = {}
for scope in scopes:
self.credentials[scope] = get_credentials(scope)
# vim:sw=4:ts=4:et:

View file

@ -0,0 +1,82 @@
import argparse
import httplib2
import os
import sys
from oauth2client import client
from oauth2client import tools
from oauth2client.file import Storage
def get_credentials_filename(scope):
from platypush.config import Config
scope_name = scope.split('/')[-1]
credentials_dir = os.path.join(
Config.get('workdir'), 'credentials', 'google')
if not os.path.exists(credentials_dir):
os.makedirs(credentials_dir)
return os.path.join(credentials_dir, scope_name + '.json')
def get_credentials(scope):
credentials_file = get_credentials_filename(scope)
if not os.path.exists(credentials_file):
raise RuntimeError('Credentials file {} not found. Generate it through:\n' +
'\tpython -m platypush.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_name))
store = Storage(credentials_file)
credentials = store.get()
if not credentials or credentials.invalid:
credentials.refresh(httplib2.Http())
return credentials
def generate_credentials(client_secret_path, scope):
credentials_file = get_credentials_filename(scope)
store = Storage(credentials_file)
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()
credentials = tools.run_flow(flow, store, flags)
print('Storing credentials to ' + credentials_file)
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]
"""
scope = sys.argv.pop(1) if len(sys.argv) > 1 \
else input('Comma separated list of OAuth scopes: ')
client_secret_path = os.path.expanduser(
sys.argv.pop(1) if len(sys.argv) > 1
else 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)
if __name__ == '__main__':
main()
# vim:sw=4:ts=4:et:

View file

@ -0,0 +1,77 @@
import base64
import httplib2
import mimetypes
from apiclient import discovery
from email.mime.audio import MIMEAudio
from email.mime.base import MIMEBase
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from platypush.message.response import Response
from platypush.plugins.google import GooglePlugin
class GoogleMailPlugin(GooglePlugin):
scopes = ['https://www.googleapis.com/auth/gmail.modify']
def __init__(self, *args, **kwargs):
super().__init__(scopes=self.scopes, *args, **kwargs)
def compose(self, sender, to, subject, body, file=None):
message = MIMEMultipart() if file else MIMEText(body)
message['to'] = to
message['from'] = sender
message['subject'] = subject
if file:
msg = MIMEText(body)
message.attach(msg)
content_type, encoding = mimetypes.guess_type(file)
if content_type is None or encoding is not None:
content_type = 'application/octet-stream'
main_type, sub_type = content_type.split('/', 1)
with open(file, 'rb') as fp:
if main_type == 'text':
msg = mimetypes.MIMEText(fp.read(), _subtype=sub_type)
elif main_type == 'image':
msg = MIMEImage(fp.read(), _subtype=sub_type)
elif main_type == 'audio':
msg = MIMEAudio(fp.read(), _subtype=sub_type)
else:
msg = MIMEBase(main_type, sub_type)
msg.set_payload(fp.read())
filename = os.path.basename(file)
msg.add_header('Content-Disposition', 'attachment', filename=filename)
message.attach(msg)
service = self._get_service()
body = { 'raw': base64.urlsafe_b64encode(message.as_bytes()).decode() }
message = (service.users().messages().send(
userId='me', body=body).execute())
return Response(output=message)
def get_labels(self):
service = self._get_service()
results = service.users().labels().list(userId='me').execute()
labels = results.get('labels', [])
return Response(output=labels)
def _get_service(self):
scope = self.scopes[0]
credentials = self.credentials[scope]
http = credentials.authorize(httplib2.Http())
return discovery.build('gmail', 'v1', http=http, cache_discovery=False)
# vim:sw=4:ts=4:et:

View file

@ -78,7 +78,8 @@ class Procedure(object):
response = Response()
for request in self.requests:
response = request.execute(n_tries, async=self.async, **context)
context['async'] = self.async; context['n_tries'] = n_tries
response = request.execute(**context)
if not self.async:
if isinstance(response.output, dict):
@ -125,13 +126,13 @@ class LoopProcedure(Procedure):
self.requests = requests
def execute(self, n_tries=1, async=None, **context):
def execute(self, async=None, **context):
iterable = Request.expand_value_from_context(self.iterable, **context)
response = Response()
for item in iterable:
context[self.iterator_name] = item
response = super().execute(n_tries, **context)
response = super().execute(**context)
return response

View file

@ -30,6 +30,9 @@ ouimeaux
google-assistant-sdk[samples]
google-assistant-library
# Google APIs general layer support
google-api-python-client
# Last.FM scrobbler plugin support
pylast

View file

@ -73,6 +73,7 @@ setup(
'Support for OMXPlayer plugin': ['omxplayer'],
'Support for YouTube in the OMXPlayer plugin': ['youtube-dl'],
'Support for Google Assistant': ['google-assistant-library'],
'Support for the Google APIs': ['google-api-python-client'],
'Support for most of the HTTP poll backends': ['python-dateutil'],
'Support for Last.FM scrobbler plugin': ['pylast'],
# 'Support for Flic buttons': ['git+ssh://git@github.com/50ButtonsEach/fliclib-linux-hci']