Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/ClusterM/google-assistant-smart-home.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexey 'Cluster' Avdyukhin <clusterrr@clusterrr.com>2020-05-23 18:05:37 +0300
committerAlexey 'Cluster' Avdyukhin <clusterrr@clusterrr.com>2020-05-23 18:05:37 +0300
commit9670691c4cb6d5dff706d4fad946835cf8209923 (patch)
treebb088bf5c1535e39be0fe6fcea878542de00f4cf /google_home.py
initial commit
Diffstat (limited to 'google_home.py')
-rw-r--r--google_home.py202
1 files changed, 202 insertions, 0 deletions
diff --git a/google_home.py b/google_home.py
new file mode 100644
index 0000000..5fcd811
--- /dev/null
+++ b/google_home.py
@@ -0,0 +1,202 @@
+# coding: utf8
+
+import config
+from flask import Flask
+from flask import request
+from flask import render_template
+from flask import send_from_directory
+from flask import redirect
+import sys
+import os
+import requests
+import urllib
+import json
+import random
+import string
+from time import time
+import importlib
+import logging
+
+if hasattr(config, 'LOG_FILE'):
+ logging.basicConfig(level=config.LOG_LEVEL,
+ format=config.LOG_FORMAT,
+ datefmt=config.LOG_DATE_FORMAT,
+ filename=config.LOG_FILE,
+ filemode='a')
+logger = logging.getLogger()
+
+sys.path.insert(0, config.DEVICES_DIRECTORY)
+
+last_code = None
+last_code_user = None
+last_code_time = None
+
+app = Flask(__name__)
+
+logger.info("Started.", extra={'remote_addr': '-', 'user': '-'})
+
+def get_user(username):
+ filename = os.path.join(config.USERS_DIRECTORY, username + ".json")
+ if os.path.isfile(filename) and os.access(filename, os.R_OK):
+ with open(filename, mode='r') as f:
+ text = f.read()
+ data = json.loads(text)
+ return data
+ else:
+ logger.warning("user not found", extra={'remote_addr': request.remote_addr, 'user': username})
+ return None
+
+def get_token():
+ auth = request.headers.get('Authorization')
+ parts = auth.split(' ', 2)
+ if len(parts) == 2 and parts[0].lower() == 'bearer':
+ return parts[1]
+ else:
+ logger.warning("invalid token: %s", auth, extra={'remote_addr': request.remote_addr, 'user': '-'})
+ return None
+
+def check_token():
+ access_token = get_token()
+ access_token_file = os.path.join(config.TOKENS_DIRECTORY, access_token)
+ if os.path.isfile(access_token_file) and os.access(access_token_file, os.R_OK):
+ with open(access_token_file, mode='r') as f:
+ return f.read()
+ else:
+ return None
+
+def get_device(device_id):
+ filename = os.path.join(config.DEVICES_DIRECTORY, device_id + ".json")
+ if os.path.isfile(filename) and os.access(filename, os.R_OK):
+ with open(filename, mode='r') as f:
+ text = f.read()
+ data = json.loads(text)
+ data['id'] = device_id
+ return data
+ else:
+ return None
+
+def random_string(stringLength=8):
+ chars = string.ascii_letters + string.digits
+ return ''.join(random.choice(chars) for i in range(stringLength))
+
+@app.route('/css/<path:path>')
+def send_css(path):
+ return send_from_directory('css', path)
+
+@app.route('/auth/', methods=['GET', 'POST'])
+def auth():
+ global last_code, last_code_user, last_code_time
+ if request.method == 'GET':
+ return render_template('login.html')
+ elif request.method == 'POST':
+ if ("username" not in request.form
+ or "password" not in request.form
+ or "state" not in request.args
+ or "response_type" not in request.args
+ or request.args["response_type"] != "code"
+ or "client_id" not in request.args
+ or request.args["client_id"] != config.CLIENT_ID):
+ logger.warning("invalid auth request", extra={'remote_addr': request.remote_addr, 'user': request.form['username']})
+ return "Invalid request", 400
+ user = get_user(request.form["username"])
+ if user == None or user["password"] != request.form["password"]:
+ logger.warning("invalid password", extra={'remote_addr': request.remote_addr, 'user': request.form['username']})
+ return render_template('login.html', login_failed=True)
+
+ last_code = random_string(8)
+ last_code_user = request.form["username"]
+ last_code_time = time()
+
+ params = {'state': request.args['state'],
+ 'code': last_code,
+ 'client_id': config.CLIENT_ID}
+ logger.info("generated code", extra={'remote_addr': request.remote_addr, 'user': request.form['username']})
+ return redirect(request.args["redirect_uri"] + '?' + urllib.parse.urlencode(params))
+
+@app.route('/token/', methods=['POST'])
+def token():
+ global last_code, last_code_user, last_code_time
+ if ("client_secret" not in request.form
+ or request.form["client_secret"] != config.CLIENT_SECRET
+ or "client_id" not in request.form
+ or request.form["client_id"] != config.CLIENT_ID
+ or "code" not in request.form):
+ logger.warning("invalid token request", extra={'remote_addr': request.remote_addr, 'user': last_code_user})
+ return "Invalid request", 400
+ if request.form["code"] != last_code:
+ logger.warning("invalid code", extra={'remote_addr': request.remote_addr, 'user': last_code_user})
+ return "Invalid code", 403
+ if time() - last_code_time > 10:
+ logger.warning("code is too old", extra={'remote_addr': request.remote_addr, 'user': last_code_user})
+ return "Code is too old", 403
+ access_token = random_string(32)
+ access_token_file = os.path.join(config.TOKENS_DIRECTORY, access_token)
+ with open(access_token_file, mode='wb') as f:
+ f.write(last_code_user.encode('utf-8'))
+ logger.info("access granted", extra={'remote_addr': request.remote_addr, 'user': last_code_user})
+ return {'access_token': access_token}
+
+@app.route('/', methods=['GET', 'POST'])
+def fulfillment():
+ if request.method == 'GET': return "Your smart home is ready."
+
+ user_id = check_token()
+ if user_id == None:
+ return "Access denied", 403
+ r = request.get_json()
+ logger.debug("request: \r\n%s", json.dumps(r, indent=4), extra={'remote_addr': request.remote_addr, 'user': user_id})
+
+ result = {}
+ result['requestId'] = r['requestId']
+
+ inputs = r['inputs']
+ for i in inputs:
+ intent = i['intent']
+ if intent == "action.devices.SYNC":
+ result['payload'] = {"agentUserId": user_id, "devices": []}
+ user = get_user(user_id)
+ for device_id in user['devices']:
+ device = get_device(device_id)
+ result['payload']['devices'].append(device)
+
+ if intent == "action.devices.QUERY":
+ result['payload'] = {}
+ result['payload']['devices'] = {}
+ for device in i['payload']['devices']:
+ device_id = device['id']
+ if "customData" in device:
+ custom_data = device['customData']
+ else:
+ custom_data = None
+ device_info = get_device(device_id)
+ device_module = importlib.import_module(device_id)
+ query_method = getattr(device_module, device_id + "_query")
+ result['payload']['devices'][device_id] = query_method(custom_data)
+
+ if intent == "action.devices.EXECUTE":
+ result['payload'] = {}
+ result['payload']['commands'] = []
+ for command in i['payload']['commands']:
+ for device in command['devices']:
+ device_id = device['id']
+ custom_data = device.get("customData", None)
+ device_info = get_device(device_id)
+ device_module = importlib.import_module(device_id)
+ action_method = getattr(device_module, device_id + "_action")
+ for e in command['execution']:
+ command = e['command']
+ params = e.get("params", None)
+ action_result = action_method(custom_data, command, params)
+ action_result['ids'] = [device_id]
+ result['payload']['commands'].append(action_result)
+
+ if intent == "action.devices.DISCONNECT":
+ access_token = get_token()
+ access_token_file = os.path.join(config.TOKENS_DIRECTORY, access_token)
+ if os.path.isfile(access_token_file) and os.access(access_token_file, os.R_OK):
+ os.remove(access_token_file)
+ logger.debug("token %s revoked", access_token, extra={'remote_addr': request.remote_addr, 'user': user_id})
+ return {}
+
+ logger.debug("response: \r\n%s", json.dumps(result, indent=4), extra={'remote_addr': request.remote_addr, 'user': user_id})
+ return result