Added GMail actions support, solves #49
This commit is contained in:
parent
d629fc897f
commit
834b700d5f
7 changed files with 219 additions and 8 deletions
|
@ -113,14 +113,19 @@ class Request(Message):
|
|||
|
||||
if context_argname in context:
|
||||
try:
|
||||
context_value = eval("context['{}']{}".format(
|
||||
context_argname, path if path else ''))
|
||||
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:
|
||||
|
|
36
platypush/plugins/google/__init__.py
Normal file
36
platypush/plugins/google/__init__.py
Normal 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:
|
||||
|
82
platypush/plugins/google/credentials.py
Normal file
82
platypush/plugins/google/credentials.py
Normal 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:
|
||||
|
77
platypush/plugins/google/mail.py
Normal file
77
platypush/plugins/google/mail.py
Normal 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:
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
1
setup.py
1
setup.py
|
@ -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']
|
||||
|
|
Loading…
Reference in a new issue