From 231cceacf6d7282852ae7e7266fb51096863e4bb Mon Sep 17 00:00:00 2001 From: Gustaf Holst Date: Mon, 9 Mar 2020 13:04:54 +0100 Subject: [PATCH 01/26] feat(package/math): create directory structure --- packages/math/__init__.py | 0 packages/math/calculator.py | 5 +++++ packages/math/config/.gitkeep | 0 packages/math/config/config.sample.json | 5 +++++ packages/math/data/.gitkeep | 0 packages/math/data/answers/.gitkeep | 0 packages/math/data/answers/en.json | 3 +++ packages/math/data/answers/fr.json | 3 +++ packages/math/data/db/.gitkeep | 0 packages/math/data/expressions/.gitkeep | 0 packages/math/data/expressions/en.json | 16 ++++++++++++++++ packages/math/data/expressions/fr.json | 16 ++++++++++++++++ packages/math/percentage.py | 5 +++++ 13 files changed, 53 insertions(+) create mode 100644 packages/math/__init__.py create mode 100644 packages/math/calculator.py create mode 100644 packages/math/config/.gitkeep create mode 100644 packages/math/config/config.sample.json create mode 100644 packages/math/data/.gitkeep create mode 100644 packages/math/data/answers/.gitkeep create mode 100644 packages/math/data/answers/en.json create mode 100644 packages/math/data/answers/fr.json create mode 100644 packages/math/data/db/.gitkeep create mode 100644 packages/math/data/expressions/.gitkeep create mode 100644 packages/math/data/expressions/en.json create mode 100644 packages/math/data/expressions/fr.json create mode 100644 packages/math/percentage.py diff --git a/packages/math/__init__.py b/packages/math/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/packages/math/calculator.py b/packages/math/calculator.py new file mode 100644 index 000000000..8d3d61a5e --- /dev/null +++ b/packages/math/calculator.py @@ -0,0 +1,5 @@ + +from bridges.python import utils + +def run(string, entities): + pass diff --git a/packages/math/config/.gitkeep b/packages/math/config/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/packages/math/config/config.sample.json b/packages/math/config/config.sample.json new file mode 100644 index 000000000..2db8332a6 --- /dev/null +++ b/packages/math/config/config.sample.json @@ -0,0 +1,5 @@ +{ + "calculation": { + "options": {} + } +} diff --git a/packages/math/data/.gitkeep b/packages/math/data/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/packages/math/data/answers/.gitkeep b/packages/math/data/answers/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/packages/math/data/answers/en.json b/packages/math/data/answers/en.json new file mode 100644 index 000000000..0db3279e4 --- /dev/null +++ b/packages/math/data/answers/en.json @@ -0,0 +1,3 @@ +{ + +} diff --git a/packages/math/data/answers/fr.json b/packages/math/data/answers/fr.json new file mode 100644 index 000000000..0db3279e4 --- /dev/null +++ b/packages/math/data/answers/fr.json @@ -0,0 +1,3 @@ +{ + +} diff --git a/packages/math/data/db/.gitkeep b/packages/math/data/db/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/packages/math/data/expressions/.gitkeep b/packages/math/data/expressions/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/packages/math/data/expressions/en.json b/packages/math/data/expressions/en.json new file mode 100644 index 000000000..dc1b32896 --- /dev/null +++ b/packages/math/data/expressions/en.json @@ -0,0 +1,16 @@ +{ + "calculator": { + "run": { + "expressions": [ + + ] + } + }, + "percentages": { + "run": { + "expressions": [ + + ] + } + } +} diff --git a/packages/math/data/expressions/fr.json b/packages/math/data/expressions/fr.json new file mode 100644 index 000000000..dc1b32896 --- /dev/null +++ b/packages/math/data/expressions/fr.json @@ -0,0 +1,16 @@ +{ + "calculator": { + "run": { + "expressions": [ + + ] + } + }, + "percentages": { + "run": { + "expressions": [ + + ] + } + } +} diff --git a/packages/math/percentage.py b/packages/math/percentage.py new file mode 100644 index 000000000..8d3d61a5e --- /dev/null +++ b/packages/math/percentage.py @@ -0,0 +1,5 @@ + +from bridges.python import utils + +def run(string, entities): + pass From c09aef9ef3ed37bc40c53551da09394603d7fcdd Mon Sep 17 00:00:00 2001 From: Gustaf Holst Date: Wed, 11 Mar 2020 13:15:41 +0100 Subject: [PATCH 02/26] feat(package/spotify): create directory structure --- bridges/python/Pipfile.lock | 34 +- bridges/python/main.py | 13 +- package-lock.json | 315 ++---------------- package.json | 1 - packages/checker/haveibeenpwned.py | 2 +- packages/checker/isitdown.py | 2 +- packages/leon/greeting.py | 35 +- packages/leon/joke.py | 2 +- packages/network/speedtest.py | 2 +- .../.gitkeep => packages/spotify/__init__.py | 0 packages/spotify/config/.gitkeep | 0 packages/spotify/config/config.sample.json | 5 + packages/spotify/data/answers/.gitkeep | 0 packages/spotify/data/db/.gitkeep | 0 packages/spotify/data/expressions/.gitkeep | 0 packages/spotify/spotify.py | 12 + server/src/query-object.sample.json | 6 +- 17 files changed, 100 insertions(+), 329 deletions(-) rename bridges/python/.venv/.gitkeep => packages/spotify/__init__.py (100%) create mode 100644 packages/spotify/config/.gitkeep create mode 100644 packages/spotify/config/config.sample.json create mode 100644 packages/spotify/data/answers/.gitkeep create mode 100644 packages/spotify/data/db/.gitkeep create mode 100644 packages/spotify/data/expressions/.gitkeep create mode 100644 packages/spotify/spotify.py diff --git a/bridges/python/Pipfile.lock b/bridges/python/Pipfile.lock index 54fc9aed1..8f7d0a554 100644 --- a/bridges/python/Pipfile.lock +++ b/bridges/python/Pipfile.lock @@ -1,12 +1,10 @@ { "_meta": { "hash": { - "sha256": "73bde89b379ffec7cec145fa30f1ebb49c349204b730a94108151fd9b71b31ec" + "sha256": "73708fd9c2db3c7b3cbbbc199ce68e5b21c9280c82cdb41877517fad550c40f5" }, "pipfile-spec": 6, - "requires": { - "python_version": "3.6" - }, + "requires": {}, "sources": [ { "name": "pypi", @@ -27,10 +25,10 @@ }, "certifi": { "hashes": [ - "sha256:59b7658e26ca9c7339e00f8f4636cdfe59d34fa37b9b04f6f9e9926b3cece1a5", - "sha256:b26104d6835d1f5e49452a26eb2ff87fe7090b89dfcaee5ea2212697e1e1d7ae" + "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3", + "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f" ], - "version": "==2019.3.9" + "version": "==2019.11.28" }, "chardet": { "hashes": [ @@ -62,12 +60,28 @@ "index": "pypi", "version": "==2.21.0" }, + "six": { + "hashes": [ + "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a", + "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c" + ], + "version": "==1.14.0" + }, "soupsieve": { "hashes": [ - "sha256:6898e82ecb03772a0d82bd0d0a10c0d6dcc342f77e0701d0ec4a8271be465ece", - "sha256:b20eff5e564529711544066d7dc0f7661df41232ae263619dede5059799cdfca" + "sha256:e914534802d7ffd233242b785229d5ba0766a7f487385e3f714446a07bf540ae", + "sha256:fcd71e08c0aee99aca1b73f45478549ee7e7fc006d51b37bec9e9def7dc22b69" ], - "version": "==1.9.1" + "version": "==2.0" + }, + "spotipy": { + "hashes": [ + "sha256:353490dccd681c643d3226f69046843be221a091cefec2e6cc1541b623cd4277", + "sha256:99d9bc15ca0e7e6b5c804b7527e2de3a3e3004cc0ca67d136347a5a6811c7f98", + "sha256:f4903379e4db045e83e24a89485b99236466e70a9ad6f9e3f7a8bfe9048b1020" + ], + "index": "pypi", + "version": "==2.9.0" }, "tinydb": { "hashes": [ diff --git a/bridges/python/main.py b/bridges/python/main.py index 0c48ad65e..dfe4598e8 100644 --- a/bridges/python/main.py +++ b/bridges/python/main.py @@ -6,15 +6,16 @@ from json import dumps, loads from importlib import import_module + def main(): - """Dynamically import modules related to the args and print the ouput""" + """Dynamically import modules related to the args and print the ouput""" - path.append('.') + path.append('./') - queryobj = utils.getqueryobj() - m = import_module('packages.' + queryobj['package'] + '.' + queryobj['module']) + queryobj = utils.getqueryobj() + m = import_module('packages.' + queryobj['package'] + '.' + queryobj['module']) - return getattr(m, queryobj['action'])(queryobj['query'], queryobj['entities']) + return getattr(m, queryobj['action'])(queryobj['query'], queryobj['entities']) if __name__ == '__main__': - main() + main() diff --git a/package-lock.json b/package-lock.json index 4448119da..128c6d810 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1531,7 +1531,8 @@ "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true }, "abort-controller": { "version": "3.0.0", @@ -1681,7 +1682,8 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true }, "ansi-styles": { "version": "3.2.1", @@ -1702,11 +1704,6 @@ "normalize-path": "^2.1.1" } }, - "aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" - }, "archiver": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/archiver/-/archiver-2.1.1.tgz", @@ -1735,19 +1732,11 @@ "readable-stream": "^2.0.0" } }, - "are-we-there-yet": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", - "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, "requires": { "sprintf-js": "~1.0.2" } @@ -3115,11 +3104,6 @@ } } }, - "chownr": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", - "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==" - }, "ci-info": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", @@ -3222,7 +3206,8 @@ "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true }, "codepage": { "version": "1.14.0", @@ -3481,11 +3466,6 @@ "date-now": "^0.1.4" } }, - "console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" - }, "constants-browserify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", @@ -3653,14 +3633,6 @@ "sha.js": "^2.4.8" } }, - "create-temp-file": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/create-temp-file/-/create-temp-file-1.0.0.tgz", - "integrity": "sha1-fFAr4RR6XPEmxaOZwH5QlbZzxds=", - "requires": { - "tempfile": "^1.1.1" - } - }, "crlf-normalize": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/crlf-normalize/-/crlf-normalize-1.0.3.tgz", @@ -3815,7 +3787,8 @@ "deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true }, "deep-is": { "version": "0.1.3", @@ -3835,18 +3808,6 @@ "is-mergeable-object": "1.1.0" } }, - "deepspeech": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/deepspeech/-/deepspeech-0.5.0.tgz", - "integrity": "sha512-ZqAwGiuS7pKPj2ALO9Gy7iC9Z8YgqWoWjA9Ei7CQTGQqUgja0Dj+C5HyfHp7Dp7OuP7Xt04UJvqQcWMcpT+6oQ==", - "requires": { - "argparse": "1.0.x", - "memory-stream": "0.0.3", - "node-pre-gyp": "0.13.x", - "node-wav": "0.0.2", - "sox-stream": "2.0.x" - } - }, "define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -3904,11 +3865,6 @@ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, - "delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" - }, "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", @@ -3953,11 +3909,6 @@ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" }, - "detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" - }, "detect-newline": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", @@ -4048,11 +3999,6 @@ "resolved": "https://registry.npmjs.org/doublearray/-/doublearray-0.0.2.tgz", "integrity": "sha1-Yxhv6NNEEydtNiH2qg7F954ifvk=" }, - "duplexer": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", - "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=" - }, "duplexer2": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", @@ -5404,14 +5350,6 @@ "universalify": "^0.1.0" } }, - "fs-minipass": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.6.tgz", - "integrity": "sha512-crhvyXcMejjv3Z5d2Fa9sf5xLYVCF5O1c71QxbVnbLsmYMBEvDAftewesN/HhY03YRoA7zOMxjNGrF5svGaaeQ==", - "requires": { - "minipass": "^2.2.1" - } - }, "fs-readdir-recursive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", @@ -5983,21 +5921,6 @@ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, - "gauge": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, "gaxios": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-2.0.1.tgz", @@ -6376,11 +6299,6 @@ "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", "dev": true }, - "has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" - }, "has-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", @@ -6420,11 +6338,6 @@ "safe-buffer": "^5.0.1" } }, - "hash-to-array": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hash-to-array/-/hash-to-array-1.0.1.tgz", - "integrity": "sha1-Kw4oTASjLDRYAkijMrli6xVeD28=" - }, "hash.js": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", @@ -6678,14 +6591,6 @@ "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=", "dev": true }, - "ignore-walk": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", - "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", - "requires": { - "minimatch": "^3.0.4" - } - }, "immutable": { "version": "3.8.2", "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.8.2.tgz", @@ -6754,7 +6659,8 @@ "ini": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "dev": true }, "inline-source-map": { "version": "0.6.2", @@ -7027,6 +6933,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, "requires": { "number-is-nan": "^1.0.0" } @@ -8742,37 +8649,6 @@ } } }, - "memory-stream": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/memory-stream/-/memory-stream-0.0.3.tgz", - "integrity": "sha1-6+jdHDuLw4wOeUHp3dWuvmtN6D8=", - "requires": { - "readable-stream": "~1.0.26-2" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - }, - "readable-stream": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" - } - } - }, "meow": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", @@ -8900,23 +8776,6 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" }, - "minipass": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz", - "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "minizlib": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.1.tgz", - "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", - "requires": { - "minipass": "^2.2.1" - } - }, "mitt": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/mitt/-/mitt-1.1.3.tgz", @@ -8946,6 +8805,7 @@ "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, "requires": { "minimist": "0.0.8" } @@ -9044,23 +8904,6 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, - "needle": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/needle/-/needle-2.4.0.tgz", - "integrity": "sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg==", - "requires": { - "debug": "^3.2.6", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - }, - "dependencies": { - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" - } - } - }, "negotiator": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", @@ -9135,23 +8978,6 @@ "which": "^1.3.0" } }, - "node-pre-gyp": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.13.0.tgz", - "integrity": "sha512-Md1D3xnEne8b/HGVQkZZwV27WUi1ZRuZBij24TNaZwUPU3ZAFtvT6xxJGaUVillfmMKnn5oD1HoGsp2Ftik7SQ==", - "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.1", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.2.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4" - } - }, "node-releases": { "version": "1.1.23", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.23.tgz", @@ -9184,15 +9010,6 @@ "update-notifier": "^2.5.0" } }, - "nopt": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", - "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", - "requires": { - "abbrev": "1", - "osenv": "^0.1.4" - } - }, "normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", @@ -9237,20 +9054,6 @@ "uni-string": "^1.1.2" } }, - "npm-bundled": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.6.tgz", - "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==" - }, - "npm-packlist": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.1.tgz", - "integrity": "sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw==", - "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1" - } - }, "npm-run-path": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", @@ -9259,17 +9062,6 @@ "path-key": "^2.0.0" } }, - "npmlog": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", @@ -9456,11 +9248,6 @@ "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", "dev": true }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" - }, "os-locale": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", @@ -9473,16 +9260,8 @@ "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" - }, - "osenv": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", - "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true }, "output-file-sync": { "version": "2.0.1", @@ -10103,6 +9882,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, "requires": { "deep-extend": "^0.6.0", "ini": "~1.3.0", @@ -10113,7 +9893,8 @@ "minimist": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true } } }, @@ -10610,6 +10391,7 @@ "version": "2.6.3", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, "requires": { "glob": "^7.1.3" } @@ -10890,7 +10672,8 @@ "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true }, "set-value": { "version": "2.0.0", @@ -11297,16 +11080,6 @@ "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=" }, - "sox-stream": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/sox-stream/-/sox-stream-2.0.3.tgz", - "integrity": "sha512-t21ZFMs+vm1JmODzSQhIIwEl/kLZdTNz6Zv7meEKaNROzwm9u8V7qAB3ti9IBsr5vxg4F8HNt/AC1QXjvErUnQ==", - "requires": { - "create-temp-file": "^1.0.0", - "duplexer": "^0.1.1", - "hash-to-array": "^1.0.0" - } - }, "spdx-correct": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", @@ -11366,7 +11139,8 @@ "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true }, "ssf": { "version": "0.10.2", @@ -11570,6 +11344,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -11588,6 +11363,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, "requires": { "ansi-regex": "^2.0.0" } @@ -11616,7 +11392,8 @@ "strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true }, "stubs": { "version": "3.0.0", @@ -11770,20 +11547,6 @@ } } }, - "tar": { - "version": "4.4.10", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.10.tgz", - "integrity": "sha512-g2SVs5QIxvo6OLp0GudTqEf05maawKUxXru104iaayWA09551tFCTI8f1Asb4lPfkBr91k07iL4c11XO3/b0tA==", - "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.3.5", - "minizlib": "^1.2.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.3" - } - }, "tar-stream": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", @@ -11808,22 +11571,6 @@ "uuid": "^3.3.2" } }, - "tempfile": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/tempfile/-/tempfile-1.1.1.tgz", - "integrity": "sha1-W8xOrsxKsscH2LwR2ZzMmiyyh/I=", - "requires": { - "os-tmpdir": "^1.0.0", - "uuid": "^2.0.1" - }, - "dependencies": { - "uuid": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz", - "integrity": "sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho=" - } - } - }, "term-size": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz", @@ -12709,14 +12456,6 @@ "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", "dev": true }, - "wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "requires": { - "string-width": "^1.0.2 || 2" - } - }, "widest-line": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz", diff --git a/package.json b/package.json index cad748c1e..091e8e35a 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,6 @@ "aws-sdk": "^2.382.0", "body-parser": "^1.17.2", "cross-env": "^5.2.0", - "deepspeech": "^0.5.0", "dotenv": "^4.0.0", "execa": "^0.10.0", "express": "^4.15.3", diff --git a/packages/checker/haveibeenpwned.py b/packages/checker/haveibeenpwned.py index 73ae623fe..3248e093d 100644 --- a/packages/checker/haveibeenpwned.py +++ b/packages/checker/haveibeenpwned.py @@ -27,7 +27,7 @@ def run(string, entities): isLastEmail = index == len(emails) - 1 breached = checkForBreach(email) data = { 'email': email } - + print("breached: ", breached) # Have I Been Pwned API returns a 403 when accessed by unauthorized/banned clients if breached == 403: return utils.output('end', 'blocked', utils.translate('blocked', { 'website_name': 'Have I Been Pwned' })) diff --git a/packages/checker/isitdown.py b/packages/checker/isitdown.py index 17f6828b6..0040112bb 100644 --- a/packages/checker/isitdown.py +++ b/packages/checker/isitdown.py @@ -2,7 +2,7 @@ # -*- coding:utf-8 -*- import requests -import utils +from bridges.python import utils def run(string, entities): """Check if a website is down or not""" diff --git a/packages/leon/greeting.py b/packages/leon/greeting.py index 812a7cffb..17ad9aab0 100644 --- a/packages/leon/greeting.py +++ b/packages/leon/greeting.py @@ -1,28 +1,29 @@ #!/usr/bin/env python # -*- coding:utf-8 -*- -import utils +from bridges.python import utils from datetime import datetime from random import randint + def run(string, entities): - """Leon greets you""" + """Leon greets you""" - time = datetime.time(datetime.now()) + time = datetime.time(datetime.now()) - # 1/2 chance to get deeper greetings - if randint(0, 1) != 0: - if time.hour >= 5 and time.hour <= 10: - return utils.output('end', 'morning_good_day', utils.translate('morning_good_day')) - if time.hour == 11: - return utils.output('end', 'morning', utils.translate('morning')) - if time.hour >= 12 and time.hour <= 17: - return utils.output('end', 'afternoon', utils.translate('afternoon')) - if time.hour >= 18 and time.hour <= 21: - return utils.output('end', 'evening', utils.translate('evening')) - if time.hour >= 22 and time.hour <= 23: - return utils.output('end', 'night', utils.translate('night')) + # 1/2 chance to get deeper greetings + if randint(0, 1) != 0: + if time.hour >= 5 and time.hour <= 10: + return utils.output('end', 'morning_good_day', utils.translate('morning_good_day')) + if time.hour == 11: + return utils.output('end', 'morning', utils.translate('morning')) + if time.hour >= 12 and time.hour <= 17: + return utils.output('end', 'afternoon', utils.translate('afternoon')) + if time.hour >= 18 and time.hour <= 21: + return utils.output('end', 'evening', utils.translate('evening')) + if time.hour >= 22 and time.hour <= 23: + return utils.output('end', 'night', utils.translate('night')) - return utils.output('end', 'too_late', utils.translate('too_late')) + return utils.output('end', 'too_late', utils.translate('too_late')) - return utils.output('end', 'default', utils.translate('default')) + return utils.output('end', 'default', utils.translate('default')) diff --git a/packages/leon/joke.py b/packages/leon/joke.py index a37deb6cf..02b2feb72 100644 --- a/packages/leon/joke.py +++ b/packages/leon/joke.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding:utf-8 -*- -import utils +from bridges.python import utils def run(string, entities): """Leon says some jokes""" diff --git a/packages/network/speedtest.py b/packages/network/speedtest.py index 8ed857503..817d136a9 100644 --- a/packages/network/speedtest.py +++ b/packages/network/speedtest.py @@ -6,7 +6,7 @@ # Date: 2019-03-09 # Based on the package https://github.com/sivel/speedtest-cli -import utils +from bridges.python import utils import os import sys import subprocess diff --git a/bridges/python/.venv/.gitkeep b/packages/spotify/__init__.py similarity index 100% rename from bridges/python/.venv/.gitkeep rename to packages/spotify/__init__.py diff --git a/packages/spotify/config/.gitkeep b/packages/spotify/config/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/packages/spotify/config/config.sample.json b/packages/spotify/config/config.sample.json new file mode 100644 index 000000000..2db8332a6 --- /dev/null +++ b/packages/spotify/config/config.sample.json @@ -0,0 +1,5 @@ +{ + "calculation": { + "options": {} + } +} diff --git a/packages/spotify/data/answers/.gitkeep b/packages/spotify/data/answers/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/packages/spotify/data/db/.gitkeep b/packages/spotify/data/db/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/packages/spotify/data/expressions/.gitkeep b/packages/spotify/data/expressions/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/packages/spotify/spotify.py b/packages/spotify/spotify.py new file mode 100644 index 000000000..76f8d8e01 --- /dev/null +++ b/packages/spotify/spotify.py @@ -0,0 +1,12 @@ + + +def play(string, entities): + + + + + + + + + diff --git a/server/src/query-object.sample.json b/server/src/query-object.sample.json index 113340bfc..1a3bd6778 100644 --- a/server/src/query-object.sample.json +++ b/server/src/query-object.sample.json @@ -1,9 +1,9 @@ { "lang": "en", - "package": "checker", - "module": "isitdown", + "package": "spotify", + "module": "spotify", "action": "run", - "query": "Check if github.com, mozilla.org and twitter.com are up", + "query": "show me info about the song derp", "entities": [ { "sourceText": "github.com", From b5a37d6e4ec49c72d12140ab5c6320b98899953f Mon Sep 17 00:00:00 2001 From: Gustaf Holst Date: Thu, 12 Mar 2020 00:11:43 +0100 Subject: [PATCH 03/26] feat(package/spotify): login, play track --- packages/spotify/authorize.py | 37 ++++ packages/spotify/data/answers/en.json | 30 +++ packages/spotify/data/answers/fr.json | 3 + packages/spotify/data/expressions/en.json | 92 +++++++++ packages/spotify/data/expressions/fr.json | 16 ++ packages/spotify/spotify.py | 215 ++++++++++++++++++++++ 6 files changed, 393 insertions(+) create mode 100644 packages/spotify/authorize.py create mode 100644 packages/spotify/data/answers/en.json create mode 100644 packages/spotify/data/answers/fr.json create mode 100644 packages/spotify/data/expressions/en.json create mode 100644 packages/spotify/data/expressions/fr.json diff --git a/packages/spotify/authorize.py b/packages/spotify/authorize.py new file mode 100644 index 000000000..239e23243 --- /dev/null +++ b/packages/spotify/authorize.py @@ -0,0 +1,37 @@ +import base64 +import time + +import requests + +from bridges.python import utils + +def run(string, entities): + db = utils.db()['db'] + + # parse url containing access code (pasted by user) + code = string.split('?code=')[1].split("&")[0] + + payload = {'redirect_uri': utils.config('redirect_uri'), + 'code': code, + 'grant_type': 'authorization_code', + 'scope': utils.config('scope')} + + data = utils.config('client_id') + ':' + utils.config('client_secret') + auth_header = base64.urlsafe_b64encode(data.encode('utf-8')) + headers = {'Authorization': 'Basic %s' % auth_header.decode('utf-8')} + + results = requests.post('https://accounts.spotify.com/api/token', data=payload, headers=headers) + + token = results.json() + token['expires_at'] = int(time.time()) + token['expires_in'] + token['client_id'] = utils.config('client_id') + token['client_secret'] = utils.config('client_secret') + token['url_base'] = utils.config('url_base') + token['scope'] = utils.config('scope') + + db.insert(token) + + utils.output('end', 'success', utils.translate('logged_in')) + + + diff --git a/packages/spotify/data/answers/en.json b/packages/spotify/data/answers/en.json new file mode 100644 index 000000000..eb05d7d08 --- /dev/null +++ b/packages/spotify/data/answers/en.json @@ -0,0 +1,30 @@ +{ + "spotify": { + "now_playing": [ + "now playing the %type% %name% by %artist%" + ], + "resume_playing": [ + "playing" + ], + "no_search_result": [ + "No search result", + "Didn't find anything on Spotify with that name" + ], + "no_device": [ + "Spotify is not running", + "Please open Spotify first" + ], + "not_logged_in": [ + "Please login to Spotify first", + "You are not logged in to Spotify" + ], + "login": [ + "Please sign in and then give me the whole URL to which you were redirected." + ] + }, + "authorize": { + "logged_in": [ + "You are logged in to Spotify!" + ] + } +} diff --git a/packages/spotify/data/answers/fr.json b/packages/spotify/data/answers/fr.json new file mode 100644 index 000000000..0db3279e4 --- /dev/null +++ b/packages/spotify/data/answers/fr.json @@ -0,0 +1,3 @@ +{ + +} diff --git a/packages/spotify/data/expressions/en.json b/packages/spotify/data/expressions/en.json new file mode 100644 index 000000000..1b6734289 --- /dev/null +++ b/packages/spotify/data/expressions/en.json @@ -0,0 +1,92 @@ +{ + "spotify": { + "play": { + "expressions": [ + "Play the song stairway to heaven by led zeppelin", + "Play the track lucy in the sky with diamonds by the beatles", + "Play me the song roxanne by the police", + "Play the artist Prince" + ], + "entities": [ + { + "type": "trim", + "name": "track", + "conditions": [ + { + "type": "after", + "from": "song" + }, + { + "type": "between", + "from": "song", + "to": "by" + }, + { + "type": "between", + "from": "track", + "to": "by" + } + ] + }, + { + "type": "trim", + "name": "artist", + "conditions": [ + { + "type": "after", + "from": "artist" + }, + { + "type": "after", + "from": "group" + }, + { + "type": "after", + "from": "by" + } + ] + }, + { + "type": "trim", + "name": "playlist", + "conditions": [ + { + "type": "after", + "from": "playlist" + } + ] + }, + { + "type": "trim", + "name": "album", + "conditions": [ + { + "type": "after", + "from": "album" + } + ] + } + ] + }, + "login" : { + "expressions": [ + "spotify login", + "login to spotify" + ] + } + }, + "authorize" : { + "run" : { + "expressions": [ + "http://localhost:1337/callback?code= " + ], + "entities": [ + { + "type": "regex", + "name": "auth_url", + "regex": "^http://localhost:1337/callback?code=" + } + ] + } + } +} diff --git a/packages/spotify/data/expressions/fr.json b/packages/spotify/data/expressions/fr.json new file mode 100644 index 000000000..dc1b32896 --- /dev/null +++ b/packages/spotify/data/expressions/fr.json @@ -0,0 +1,16 @@ +{ + "calculator": { + "run": { + "expressions": [ + + ] + } + }, + "percentages": { + "run": { + "expressions": [ + + ] + } + } +} diff --git a/packages/spotify/spotify.py b/packages/spotify/spotify.py index 76f8d8e01..c55cfdde2 100644 --- a/packages/spotify/spotify.py +++ b/packages/spotify/spotify.py @@ -1,12 +1,227 @@ +import base64 +import json +import socket +import time +import webbrowser +from urllib.parse import urlencode +from bridges.python import utils +import requests + +auth_base = 'https://accounts.spotify.com/authorize?' + +def logged_in(): + db = utils.db()['db'].all() + if len(db) > 0: + db = get_database_content() + now = int(time.time()) + return int(db['expires_at']) > now + + return False + +def get_database_content(): + return utils.db()['db'].all()[0] + +def login(string, entities): + config = { + 'redirect_uri': utils.config('redirect_uri'), + 'response_type': 'code', + 'client_id': utils.config('client_id'), + 'scope': utils.config('scope') + } + + url = auth_base + urlencode(config) + + webbrowser.open(url) + + utils.output('end', 'success', utils.translate('login')) def play(string, entities): + if not logged_in(): + return utils.output('end', 'error', utils.translate('not_logged_in')) + + device = get_device() + if not device: + return utils.output('end', 'error', utils.translate('no_device')) + + track='' + album='' + artist='' + playlist='' + + # which search parameters are specified + for item in entities: + if item['entity'] == 'track': + track=item['sourceText'] + if item['entity'] == 'artist': + artist = item['sourceText'] + if item['entity'] == 'album': + album = item['sourceText'] + if item['entity'] == 'playlist': + playlist = item['sourceText'] + + if device: + if album: + play_album(device, album, artist) + elif playlist: + play_playlist(device, playlist) + elif track: + play_track(device, track, artist) + elif artist: + play_artist(device, artist) + else: + play_current_track(device) + + +def play_current_track(device): + spotify_request('PUT', 'me/player/play', {'device_id': device}) + utils.output('end', 'success', utils.translate('resume_playing')) + + +def play_track(device_id, track_name, artist=None): + params = { + 'q': track_name, + 'type': 'track' + } + + if artist: + params['artist'] = artist + + results = spotify_request('GET', 'search', params) + + file = open("mylog.txt", 'w') + file.write(json.dumps(results)) + file.close() + + if not results['tracks']['total'] > 0: + return utils.output('end', 'info', utils.translate('no_search_result')) + + chosen_track = None + if artist: + for t in results['tracks']['items']: + for a in t['artists']: + if a['name'].lower() == artist.lower(): + chosen_track = t + break + + if not chosen_track: + chosen_track = results['tracks']['items'][0] # choose first match + + artists = [] + + for artist in chosen_track['artists']: + artists.append(artist['name']) + + data = {} + data["uris"] = [chosen_track["uri"]] + + file = open("chosentrack_uri.txt", 'w') + file.write(chosen_track["uri"]) + file.close() + + spotify_request('PUT', 'me/player/play', {'device_id': device_id}, json.dumps(data)) + + info = { + "name": chosen_track['name'], + "artist": ', '.join(artists), + "type": 'track' + } + + utils.output('end', 'success', utils.translate('now_playing', info)) + + +def play_album(device, album, artist=None): + pass + +def play_artist(device, artist): + pass + +def play_playlist(device, playlist): + pass + +def token_expired(expires_at): + now = int(time.time()) + return expires_at - now < 60 + +def get_access_token(): + data = get_database_content() + access_token = data['access_token'] + expires_at = data['expires_at'] + refresh_token = data['refresh_token'] + + if token_expired(expires_at): + access_token = refresh_access_token(refresh_token) + + return access_token + + +def refresh_access_token(refresh_token): + payload = {'refresh_token': refresh_token, 'grant_type': 'refresh_token'} + + db_content = get_database_content() + client_id = db_content['client_id'] + client_secret= db_content['client_secret'] + + data = client_id + ':' + client_secret + auth_header = base64.urlsafe_b64encode(data.encode('utf-8')) + headers = {'Authorization': 'Basic %s' % auth_header.decode('utf-8')} + + response = requests.post('https://accounts.spotify.com/api/token', data=payload, headers=headers) + + token_info = response.json() + + db = utils.db()['db'] + db.update({"access_token": token_info['access_token']}) + return token_info['access_token'] + + +def spotify_request(method, endpoint, query_params, body_params={}): + access_token = get_access_token() + url_base = get_database_content()['url_base'] + url = url_base + endpoint + '?' + urlencode(query_params) + result = requests.request(method=method, url=url, headers={'Authorization': 'Bearer {0}'.format( + access_token), 'Content-Type': 'application/json'}, data=body_params) + + file = open("putresult.txt", 'w') + file.write(result.text) + file.close() + + if result.text and len(result.text) > 0 and result.text != 'null': + return result.json() + else: + return None + + +def get_device(): + access_token = get_access_token() + url_base = get_database_content()['url_base'] + url = url_base + 'me/player/devices' + devices = requests.get( + url=url, headers={'Authorization': 'Bearer {0}'.format(access_token)}).json() + current_device_name = socket.gethostname() + file = open('logfile.txt', 'w') + file.write(json.dumps(devices)) + file.close() + device_id = False + for device in devices['devices']: + if (device['name'].lower() == current_device_name): + device_id = device['id'] + return device_id +def get_current_track(): + data = {} + currently_playing = spotify_request('GET', 'me/player/currently-playing', {})['item'] + data['name'] = currently_playing['name'] + artists = [] + for artist in currently_playing['artists']: + artists.append(artist['name']) + data['artists'] = ', '.join(artists) + return data From b8b690a1a90fa354d78abe065a3f51b5fda055ea Mon Sep 17 00:00:00 2001 From: Gustaf Holst Date: Thu, 12 Mar 2020 11:09:17 +0100 Subject: [PATCH 04/26] feat(package/spotify): refactor expressions map to individual functions --- packages/spotify/authorize.py | 7 +++ packages/spotify/data/answers/en.json | 3 ++ packages/spotify/data/expressions/en.json | 65 ++++++++++++++++++++--- packages/spotify/spotify.py | 59 +++++++++++++++----- 4 files changed, 113 insertions(+), 21 deletions(-) diff --git a/packages/spotify/authorize.py b/packages/spotify/authorize.py index 239e23243..528d401e9 100644 --- a/packages/spotify/authorize.py +++ b/packages/spotify/authorize.py @@ -1,4 +1,5 @@ import base64 +import json import time import requests @@ -23,6 +24,12 @@ def run(string, entities): results = requests.post('https://accounts.spotify.com/api/token', data=payload, headers=headers) token = results.json() + + file = open("access_token.txt", 'w') + file.write(json.dumps(token)) + file.close() + + # add info fields to token object token['expires_at'] = int(time.time()) + token['expires_in'] token['client_id'] = utils.config('client_id') token['client_secret'] = utils.config('client_secret') diff --git a/packages/spotify/data/answers/en.json b/packages/spotify/data/answers/en.json index eb05d7d08..509c12b1a 100644 --- a/packages/spotify/data/answers/en.json +++ b/packages/spotify/data/answers/en.json @@ -18,6 +18,9 @@ "Please login to Spotify first", "You are not logged in to Spotify" ], + "already_logged_in": [ + "You are already logged in to Spotify" + ], "login": [ "Please sign in and then give me the whole URL to which you were redirected." ] diff --git a/packages/spotify/data/expressions/en.json b/packages/spotify/data/expressions/en.json index 1b6734289..5734d9510 100644 --- a/packages/spotify/data/expressions/en.json +++ b/packages/spotify/data/expressions/en.json @@ -1,21 +1,36 @@ { "spotify": { - "play": { + "play_current_track": { "expressions": [ - "Play the song stairway to heaven by led zeppelin", + "Play", + "Resume", + "Resume playing" + ] + }, + "play_track": { + "expressions": [ + "Play smells like teen spirit", + "Play the song stairway to heaven", "Play the track lucy in the sky with diamonds by the beatles", - "Play me the song roxanne by the police", - "Play the artist Prince" + "Play me the song roxanne by the police" ], "entities": [ { "type": "trim", "name": "track", "conditions": [ + { + "type": "after", + "from": "play" + }, { "type": "after", "from": "song" }, + { + "type": "after", + "from": "track" + }, { "type": "between", "from": "song", @@ -45,10 +60,18 @@ "from": "by" } ] - }, + } + ] + }, + "play_album": { + "expressions": [ + "Play album foo", + "Play the album derp by foo fighters" + ], + "entities": [ { "type": "trim", - "name": "playlist", + "name": "album", "conditions": [ { "type": "after", @@ -56,13 +79,39 @@ } ] }, + { + "type": "trim", + "name": "artist", + "conditions": [ + { + "type": "after", + "from": "artist" + }, + { + "type": "after", + "from": "group" + }, + { + "type": "after", + "from": "by" + } + ] + } + ] + }, + "play_playlist": { + "expressions": [ + "Play playlist foo", + "Play the playlist derp" + ], + "entities": [ { "type": "trim", - "name": "album", + "name": "playlist", "conditions": [ { "type": "after", - "from": "album" + "from": "playlist" } ] } diff --git a/packages/spotify/spotify.py b/packages/spotify/spotify.py index c55cfdde2..4428fd662 100644 --- a/packages/spotify/spotify.py +++ b/packages/spotify/spotify.py @@ -13,9 +13,12 @@ def logged_in(): db = utils.db()['db'].all() if len(db) > 0: - db = get_database_content() + db = get_database_content() # token info now = int(time.time()) - return int(db['expires_at']) > now + file = open("isloggedin.txt", 'w') + file.write("Expires at: " + str(db['expires_at']) + '\t' + "Now: " + str(now)) + file.close() + return now < db['expires_at'] return False @@ -23,6 +26,9 @@ def get_database_content(): return utils.db()['db'].all()[0] def login(string, entities): + if logged_in(): + return utils.output('end', 'message', utils.translate('already_logged_in')) + config = { 'redirect_uri': utils.config('redirect_uri'), 'response_type': 'code', @@ -36,13 +42,21 @@ def login(string, entities): utils.output('end', 'success', utils.translate('login')) -def play(string, entities): +def can_play(device): if not logged_in(): - return utils.output('end', 'error', utils.translate('not_logged_in')) + utils.output('end', 'error', utils.translate('not_logged_in')) + return False - device = get_device() if not device: - return utils.output('end', 'error', utils.translate('no_device')) + utils.output('end', 'error', utils.translate('no_device')) + return False + + return True + +def play(string, entities): + device = get_device() + if not can_play(device): + return track='' album='' @@ -73,19 +87,38 @@ def play(string, entities): play_current_track(device) -def play_current_track(device): - spotify_request('PUT', 'me/player/play', {'device_id': device}) +def play_current_track(string, entities): + device_id = get_device() + if not can_play(device_id): + return + spotify_request('PUT', 'me/player/play', {'device_id': device_id}) utils.output('end', 'success', utils.translate('resume_playing')) -def play_track(device_id, track_name, artist=None): +def play_track(string, entities): + device_id = get_device() + if not can_play(device_id): + return + + track_name = '' + artist_name = '' + + for item in entities: + if item['entity'] == 'track': + track_name=item['sourceText'] + if item['entity'] == 'artist': + artist_name = item['sourceText'] + + if not track_name: + return utils.output('end', 'info', "no track name....faaan!") + params = { 'q': track_name, 'type': 'track' } - if artist: - params['artist'] = artist + if artist_name: + params['artist'] = artist_name results = spotify_request('GET', 'search', params) @@ -97,10 +130,10 @@ def play_track(device_id, track_name, artist=None): return utils.output('end', 'info', utils.translate('no_search_result')) chosen_track = None - if artist: + if artist_name: for t in results['tracks']['items']: for a in t['artists']: - if a['name'].lower() == artist.lower(): + if a['name'].lower() == artist_name.lower(): chosen_track = t break From 457d41ddc94d869d55bae8460ec808ee0918fe2a Mon Sep 17 00:00:00 2001 From: Gustaf Holst Date: Thu, 12 Mar 2020 12:09:32 +0100 Subject: [PATCH 05/26] feat(package/spotify): fixed bug logging in after token expired --- packages/spotify/authorize.py | 7 ++++--- packages/spotify/data/expressions/en.json | 8 ++------ packages/spotify/spotify.py | 7 ++----- 3 files changed, 8 insertions(+), 14 deletions(-) diff --git a/packages/spotify/authorize.py b/packages/spotify/authorize.py index 528d401e9..f46d1d71a 100644 --- a/packages/spotify/authorize.py +++ b/packages/spotify/authorize.py @@ -36,9 +36,10 @@ def run(string, entities): token['url_base'] = utils.config('url_base') token['scope'] = utils.config('scope') + # in case new login because of expired token + if len(db.all()) > 0: + db.purge() + db.insert(token) utils.output('end', 'success', utils.translate('logged_in')) - - - diff --git a/packages/spotify/data/expressions/en.json b/packages/spotify/data/expressions/en.json index 5734d9510..1f71e6f46 100644 --- a/packages/spotify/data/expressions/en.json +++ b/packages/spotify/data/expressions/en.json @@ -1,14 +1,10 @@ { "spotify": { - "play_current_track": { + "play_track": { "expressions": [ "Play", "Resume", - "Resume playing" - ] - }, - "play_track": { - "expressions": [ + "Play maiden voayage", "Play smells like teen spirit", "Play the song stairway to heaven", "Play the track lucy in the sky with diamonds by the beatles", diff --git a/packages/spotify/spotify.py b/packages/spotify/spotify.py index 4428fd662..aae069937 100644 --- a/packages/spotify/spotify.py +++ b/packages/spotify/spotify.py @@ -87,10 +87,7 @@ def play(string, entities): play_current_track(device) -def play_current_track(string, entities): - device_id = get_device() - if not can_play(device_id): - return +def play_current_track(device_id): spotify_request('PUT', 'me/player/play', {'device_id': device_id}) utils.output('end', 'success', utils.translate('resume_playing')) @@ -110,7 +107,7 @@ def play_track(string, entities): artist_name = item['sourceText'] if not track_name: - return utils.output('end', 'info', "no track name....faaan!") + play_current_track(device_id) params = { 'q': track_name, From 0952449849d86aff4249f777bb95616fcef0cda3 Mon Sep 17 00:00:00 2001 From: Gustaf Holst Date: Thu, 12 Mar 2020 13:57:34 +0100 Subject: [PATCH 06/26] feat(package/spotify): play album --- packages/spotify/data/answers/en.json | 2 +- packages/spotify/data/expressions/en.json | 19 ++++++- packages/spotify/spotify.py | 69 ++++++++++++++++++++++- 3 files changed, 85 insertions(+), 5 deletions(-) diff --git a/packages/spotify/data/answers/en.json b/packages/spotify/data/answers/en.json index 509c12b1a..2fae6de6c 100644 --- a/packages/spotify/data/answers/en.json +++ b/packages/spotify/data/answers/en.json @@ -1,7 +1,7 @@ { "spotify": { "now_playing": [ - "now playing the %type% %name% by %artist%" + "now playing the %type% \"%name%\" by %artist%" ], "resume_playing": [ "playing" diff --git a/packages/spotify/data/expressions/en.json b/packages/spotify/data/expressions/en.json index 1f71e6f46..3a62b4ad3 100644 --- a/packages/spotify/data/expressions/en.json +++ b/packages/spotify/data/expressions/en.json @@ -8,7 +8,8 @@ "Play smells like teen spirit", "Play the song stairway to heaven", "Play the track lucy in the sky with diamonds by the beatles", - "Play me the song roxanne by the police" + "Play me the song roxanne by the police", + "Play the tune fly me to the moon by Frank Sinatra" ], "entities": [ { @@ -27,6 +28,10 @@ "type": "after", "from": "track" }, + { + "type": "after", + "from": "tune" + }, { "type": "between", "from": "song", @@ -36,6 +41,11 @@ "type": "between", "from": "track", "to": "by" + }, + { + "type": "between", + "from": "tune", + "to": "by" } ] }, @@ -69,9 +79,14 @@ "type": "trim", "name": "album", "conditions": [ + { + "type": "between", + "from": "album", + "to": "by" + }, { "type": "after", - "from": "playlist" + "from": "album" } ] }, diff --git a/packages/spotify/spotify.py b/packages/spotify/spotify.py index aae069937..24869896f 100644 --- a/packages/spotify/spotify.py +++ b/packages/spotify/spotify.py @@ -160,19 +160,84 @@ def play_track(string, entities): utils.output('end', 'success', utils.translate('now_playing', info)) -def play_album(device, album, artist=None): - pass +def play_album(string, entities): + device_id = get_device() + if not can_play(device_id): + return + + album_name = '' + artist_name = '' + + for item in entities: + if item['entity'] == 'album': + album_name = item['sourceText'] + if item['entity'] == 'artist': + artist_name = item['sourceText'] + + if not album_name: + play_current_track(device_id) + + params = { + 'q': album_name, + 'type': 'album' + } + + if artist_name: + params['artist'] = artist_name + + results = spotify_request('GET', 'search', params) + + file = open("play_album.txt", 'w') + file.write(json.dumps(results)) + file.close() + + if not results['albums']['total'] > 0: + return utils.output('end', 'info', utils.translate('no_search_result')) + + chosen_album = None + if artist_name: + for alb in results['albums']['items']: + if alb['name'].lower() == album_name.lower(): + for art in alb['artists']: + if art['name'].lower() == artist_name.lower(): + chosen_album= alb + break + + if not chosen_album: + chosen_album = results['albums']['items'][0] # choose first match + + artists = [] + + for artist in chosen_album['artists']: + artists.append(artist['name']) + + data = {} + data["context_uri"] = chosen_album["uri"] + + spotify_request('PUT', 'me/player/play', {'device_id': device_id}, json.dumps(data)) + + info = { + "name": chosen_album['name'], + "artist": ', '.join(artists), + "type": 'album' + } + + utils.output('end', 'success', utils.translate('now_playing', info)) + def play_artist(device, artist): pass + def play_playlist(device, playlist): pass + def token_expired(expires_at): now = int(time.time()) return expires_at - now < 60 + def get_access_token(): data = get_database_content() access_token = data['access_token'] From d119f196d02f0fd971fba285f75ed4df2be628d0 Mon Sep 17 00:00:00 2001 From: Gustaf Holst Date: Thu, 12 Mar 2020 15:48:47 +0100 Subject: [PATCH 07/26] feat(package/spotify): play artist --- packages/spotify/data/answers/en.json | 3 + packages/spotify/data/expressions/en.json | 15 ++-- packages/spotify/spotify.py | 101 +++++++++++++--------- 3 files changed, 74 insertions(+), 45 deletions(-) diff --git a/packages/spotify/data/answers/en.json b/packages/spotify/data/answers/en.json index 2fae6de6c..89a432dd9 100644 --- a/packages/spotify/data/answers/en.json +++ b/packages/spotify/data/answers/en.json @@ -3,6 +3,9 @@ "now_playing": [ "now playing the %type% \"%name%\" by %artist%" ], + "now_playing_artist": [ + "now playing the %type% \"%name%\"" + ], "resume_playing": [ "playing" ], diff --git a/packages/spotify/data/expressions/en.json b/packages/spotify/data/expressions/en.json index 3a62b4ad3..b6a5104c4 100644 --- a/packages/spotify/data/expressions/en.json +++ b/packages/spotify/data/expressions/en.json @@ -110,19 +110,24 @@ } ] }, - "play_playlist": { + "play_artist": { "expressions": [ - "Play playlist foo", - "Play the playlist derp" + "Play artist Prince", + "Play the artist Foo Fighters", + "Play something by the group Weezer" ], "entities": [ { "type": "trim", - "name": "playlist", + "name": "artist", "conditions": [ { "type": "after", - "from": "playlist" + "from": "artist" + }, + { + "type": "after", + "from": "group" } ] } diff --git a/packages/spotify/spotify.py b/packages/spotify/spotify.py index 24869896f..07e4e7ca7 100644 --- a/packages/spotify/spotify.py +++ b/packages/spotify/spotify.py @@ -53,39 +53,6 @@ def can_play(device): return True -def play(string, entities): - device = get_device() - if not can_play(device): - return - - track='' - album='' - artist='' - playlist='' - - # which search parameters are specified - for item in entities: - if item['entity'] == 'track': - track=item['sourceText'] - if item['entity'] == 'artist': - artist = item['sourceText'] - if item['entity'] == 'album': - album = item['sourceText'] - if item['entity'] == 'playlist': - playlist = item['sourceText'] - - if device: - if album: - play_album(device, album, artist) - elif playlist: - play_playlist(device, playlist) - elif track: - play_track(device, track, artist) - elif artist: - play_artist(device, artist) - else: - play_current_track(device) - def play_current_track(device_id): spotify_request('PUT', 'me/player/play', {'device_id': device_id}) @@ -106,8 +73,11 @@ def play_track(string, entities): if item['entity'] == 'artist': artist_name = item['sourceText'] - if not track_name: - play_current_track(device_id) + # in case the nlu has misunderstood + if not track_name and not artist_name: + return play_current_track(device_id) + if artist_name and not track_name: + return play_artist(string, entities) params = { 'q': track_name, @@ -183,6 +153,7 @@ def play_album(string, entities): } if artist_name: + params['q'] += ' artist:' + artist_name params['artist'] = artist_name results = spotify_request('GET', 'search', params) @@ -194,14 +165,17 @@ def play_album(string, entities): if not results['albums']['total'] > 0: return utils.output('end', 'info', utils.translate('no_search_result')) + file = open("play_album.txt", 'a') chosen_album = None if artist_name: for alb in results['albums']['items']: if alb['name'].lower() == album_name.lower(): for art in alb['artists']: + file.write("\nAlbum: " + alb['name'] + '\t' + "Artist: " + art['name'] + '\n') if art['name'].lower() == artist_name.lower(): - chosen_album= alb + chosen_album = alb break + file.close() if not chosen_album: chosen_album = results['albums']['items'][0] # choose first match @@ -225,12 +199,59 @@ def play_album(string, entities): utils.output('end', 'success', utils.translate('now_playing', info)) -def play_artist(device, artist): - pass +def play_artist(string, entities): + device_id = get_device() + if not can_play(device_id): + return + + artist_name = '' + + for item in entities: + if item['entity'] == 'artist': + artist_name = item['sourceText'] + + if not artist_name: + play_current_track(device_id) + + params = { + 'q': artist_name, + 'type': 'artist' + } + + results = spotify_request('GET', 'search', params) + + file = open("play_artist.txt", 'w') + file.write(json.dumps(results)) + file.close() + + if not results['artists']['total'] > 0: + return utils.output('end', 'info', utils.translate('no_search_result')) + + file = open("play_album.txt", 'a') + chosen_artist = None + for art in results['artists']['items']: + file.write("\nArtist: " + art['name'] + '\n') + if art['name'].lower() == artist_name.lower(): + chosen_artist = art + break + + file.close() + + if not chosen_artist: + chosen_artist = results['artists']['items'][0] # choose first match + + data = {} + data["context_uri"] = chosen_artist["uri"] + + spotify_request('PUT', 'me/player/play', {'device_id': device_id}, json.dumps(data)) + + info = { + "name": chosen_artist['name'], + "type": 'artist' + } -def play_playlist(device, playlist): - pass + utils.output('end', 'success', utils.translate('now_playing_artist', info)) def token_expired(expires_at): From d68df29e02f48c4aa5b21e36cd6b3da3c0228874 Mon Sep 17 00:00:00 2001 From: Gustaf Holst Date: Thu, 12 Mar 2020 17:22:57 +0100 Subject: [PATCH 08/26] feat(package/spotify): fixed bug preventing album names containing the word "album" etc --- packages/spotify/data/answers/en.json | 15 ++++++++----- packages/spotify/data/expressions/en.json | 26 +++++++++++------------ 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/packages/spotify/data/answers/en.json b/packages/spotify/data/answers/en.json index 89a432dd9..b3326988f 100644 --- a/packages/spotify/data/answers/en.json +++ b/packages/spotify/data/answers/en.json @@ -1,13 +1,13 @@ { "spotify": { "now_playing": [ - "now playing the %type% \"%name%\" by %artist%" + "Now playing the %type% \"%name%\" by %artist%" ], "now_playing_artist": [ - "now playing the %type% \"%name%\"" + "Now playing the %type% \"%name%\"" ], "resume_playing": [ - "playing" + "Playing" ], "no_search_result": [ "No search result", @@ -25,12 +25,17 @@ "You are already logged in to Spotify" ], "login": [ - "Please sign in and then give me the whole URL to which you were redirected." + "Please sign in and then give me the whole URL to which you were redirected.", + "Please log in using the opened tab in your browser and then paste the whole URL to which you are redirected." ] }, "authorize": { "logged_in": [ - "You are logged in to Spotify!" + "Great, you are now logged in to Spotify!", + "Great, you are now logged in to Spotify!\nTry playing a track!", + "Great, you are now logged in to Spotify!\nTry playing an album!", + "Great, you are now logged in to Spotify!\nTry playing an artist!", + "You have successfully logged in to Spotify!" ] } } diff --git a/packages/spotify/data/expressions/en.json b/packages/spotify/data/expressions/en.json index b6a5104c4..819befcc3 100644 --- a/packages/spotify/data/expressions/en.json +++ b/packages/spotify/data/expressions/en.json @@ -17,19 +17,19 @@ "name": "track", "conditions": [ { - "type": "after", + "type": "after_first", "from": "play" }, { - "type": "after", + "type": "after_first", "from": "song" }, { - "type": "after", + "type": "after_first", "from": "track" }, { - "type": "after", + "type": "after_first", "from": "tune" }, { @@ -54,15 +54,15 @@ "name": "artist", "conditions": [ { - "type": "after", + "type": "after_first", "from": "artist" }, { - "type": "after", + "type": "after_first", "from": "group" }, { - "type": "after", + "type": "after_first", "from": "by" } ] @@ -85,7 +85,7 @@ "to": "by" }, { - "type": "after", + "type": "after_first", "from": "album" } ] @@ -95,15 +95,15 @@ "name": "artist", "conditions": [ { - "type": "after", + "type": "after_first", "from": "artist" }, { - "type": "after", + "type": "after_first", "from": "group" }, { - "type": "after", + "type": "after_first", "from": "by" } ] @@ -122,11 +122,11 @@ "name": "artist", "conditions": [ { - "type": "after", + "type": "after_first", "from": "artist" }, { - "type": "after", + "type": "after_first", "from": "group" } ] From c6916e6b4d796136ace825d5f9e2e9b93faea0a5 Mon Sep 17 00:00:00 2001 From: Gustaf Holst Date: Thu, 12 Mar 2020 18:40:52 +0100 Subject: [PATCH 09/26] feat(package/spotify): add readme with instructions --- packages/spotify/README.md | 33 ++++++++++++++++++++++ packages/spotify/config/config.sample.json | 19 +++++++++++-- 2 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 packages/spotify/README.md diff --git a/packages/spotify/README.md b/packages/spotify/README.md new file mode 100644 index 000000000..d51c41ad7 --- /dev/null +++ b/packages/spotify/README.md @@ -0,0 +1,33 @@ +# Spotify Package + +The Spotify package contains modules which lets Leon interface with Spotify. + +## Modules + +### Spotify + +Use Leon to start|stop|search tracks|albums|artists on Spotify. + +#### Usage + +This package requires a Spotify Premium (:moneybag:) account. Once you have [signed up](https://www.spotify.com) please +follow these steps in order to start using Spotify via Leon +1. Login at the [Spotify web api for developers](https://developer.spotify.com/dashboard/login) +2. Create a client id (suitable app name: "Leon") +3. Make sure you have + - a client id + - a client secret +4. Paste these into the package config file (packages/spotify/config/config.json) +5. Add the `redirect_uri` you find in (packages/spotify/config/config.sample.json)to redirect URIs at the Spotify Web + API Dashboard (-> edit settings) + +``` +(en-US) "Play track Smells like teen spirit" + "Play album the white album by The Beatles" + "Play artist Led Zeppelin" + "Pause" + "Resume" + +(fr-FR) "" +... +``` diff --git a/packages/spotify/config/config.sample.json b/packages/spotify/config/config.sample.json index 2db8332a6..611b68efc 100644 --- a/packages/spotify/config/config.sample.json +++ b/packages/spotify/config/config.sample.json @@ -1,5 +1,18 @@ { - "calculation": { - "options": {} - } + "spotify": { + "client_id": "PASTE CLIENT ID HERE", + "client_secret": "PASTE CLIENT SECRET HERE", + "url_base" : "https://api.spotify.com/v1/", + "redirect_uri": "http://localhost:1337/callback", + "scope": "user-read-playback-state,user-modify-playback-state", + "options": {} + }, + "authorize": { + "client_id": "PASTE CLIENT ID HERE", + "client_secret": "PASTE CLIENT SECRET HERE", + "url_base" : "https://api.spotify.com/v1/", + "redirect_uri": "http://localhost:1337/callback", + "scope": "user-read-playback-state,user-modify-playback-state", + "options": {} + } } From cade1d895ae00e404f8cd7af22fe8d0011f14bc6 Mon Sep 17 00:00:00 2001 From: Gustaf Holst Date: Thu, 12 Mar 2020 18:53:47 +0100 Subject: [PATCH 10/26] feat(package/spotify): pause functionality --- packages/spotify/data/answers/en.json | 5 ++++- packages/spotify/data/expressions/en.json | 8 ++++++++ packages/spotify/spotify.py | 11 ++++++++++- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/packages/spotify/data/answers/en.json b/packages/spotify/data/answers/en.json index b3326988f..43bb78b61 100644 --- a/packages/spotify/data/answers/en.json +++ b/packages/spotify/data/answers/en.json @@ -6,7 +6,10 @@ "now_playing_artist": [ "Now playing the %type% \"%name%\"" ], - "resume_playing": [ + "playing_paused": [ + "Paused" + ], + "playing_resumed": [ "Playing" ], "no_search_result": [ diff --git a/packages/spotify/data/expressions/en.json b/packages/spotify/data/expressions/en.json index 819befcc3..ce5534b4b 100644 --- a/packages/spotify/data/expressions/en.json +++ b/packages/spotify/data/expressions/en.json @@ -133,6 +133,14 @@ } ] }, + "pause": { + "expressions": [ + "pause", + "stop", + "pause track", + "pause music" + ] + }, "login" : { "expressions": [ "spotify login", diff --git a/packages/spotify/spotify.py b/packages/spotify/spotify.py index 07e4e7ca7..7754ecfb1 100644 --- a/packages/spotify/spotify.py +++ b/packages/spotify/spotify.py @@ -54,9 +54,18 @@ def can_play(device): return True +def pause(string, entities): + device_id = get_device() + if not can_play(device_id): + return + + spotify_request('PUT', 'me/player/pause', {'device_id': device_id}) + utils.output('end', 'success', utils.translate('playing_paused')) + + def play_current_track(device_id): spotify_request('PUT', 'me/player/play', {'device_id': device_id}) - utils.output('end', 'success', utils.translate('resume_playing')) + utils.output('end', 'success', utils.translate('playing_resumed')) def play_track(string, entities): From 28e4cb2f6552f0177880c604220b56f93df89427 Mon Sep 17 00:00:00 2001 From: Gustaf Holst Date: Fri, 13 Mar 2020 09:54:22 +0100 Subject: [PATCH 11/26] feat(package/spotify): DRY refactor --- packages/spotify/data/expressions/en.json | 62 ++------ packages/spotify/spotify.py | 175 ++++++++++++---------- 2 files changed, 108 insertions(+), 129 deletions(-) diff --git a/packages/spotify/data/expressions/en.json b/packages/spotify/data/expressions/en.json index ce5534b4b..c28a3123d 100644 --- a/packages/spotify/data/expressions/en.json +++ b/packages/spotify/data/expressions/en.json @@ -1,6 +1,6 @@ { "spotify": { - "play_track": { + "play": { "expressions": [ "Play", "Resume", @@ -9,7 +9,12 @@ "Play the song stairway to heaven", "Play the track lucy in the sky with diamonds by the beatles", "Play me the song roxanne by the police", - "Play the tune fly me to the moon by Frank Sinatra" + "Play the tune fly me to the moon by Frank Sinatra", + "Play album foo", + "Play the album derp by foo fighters", + "Play artist Prince", + "Play the artist Foo Fighters", + "Play something by the group Weezer" ], "entities": [ { @@ -18,7 +23,7 @@ "conditions": [ { "type": "after_first", - "from": "play" + "from": "music" }, { "type": "after_first", @@ -66,15 +71,7 @@ "from": "by" } ] - } - ] - }, - "play_album": { - "expressions": [ - "Play album foo", - "Play the album derp by foo fighters" - ], - "entities": [ + }, { "type": "trim", "name": "album", @@ -89,47 +86,6 @@ "from": "album" } ] - }, - { - "type": "trim", - "name": "artist", - "conditions": [ - { - "type": "after_first", - "from": "artist" - }, - { - "type": "after_first", - "from": "group" - }, - { - "type": "after_first", - "from": "by" - } - ] - } - ] - }, - "play_artist": { - "expressions": [ - "Play artist Prince", - "Play the artist Foo Fighters", - "Play something by the group Weezer" - ], - "entities": [ - { - "type": "trim", - "name": "artist", - "conditions": [ - { - "type": "after_first", - "from": "artist" - }, - { - "type": "after_first", - "from": "group" - } - ] } ] }, diff --git a/packages/spotify/spotify.py b/packages/spotify/spotify.py index 7754ecfb1..a46209722 100644 --- a/packages/spotify/spotify.py +++ b/packages/spotify/spotify.py @@ -10,6 +10,51 @@ auth_base = 'https://accounts.spotify.com/authorize?' +track = 'track' +artist = 'artist' +album = 'album' +playlist = 'playlist' + +class SearchQuery: + def __init__(self, track=None, artist=None, album=None, playlist=None): + self.track = track + self.artist = artist + self.album = album + self.playlist = playlist + + def get_type(self): + if self.track: + return track + if self.album: + return album + if self.artist: + return artist + if self.playlist: + return playlist + return None + + def get_query_string(self): + if self.track and self.artist: + return '{} artist:{}'.format(self.track, self.artist) + if self.album and self.artist: + return '{} album:{}'.format(self.album, self.artist) + if self.track: + return self.track + if self.album: + return self.album + if self.artist: + return self.artist + if self.playlist: + return self.playlist + return '' + + def get_parameters(self): + return { + 'q': self.get_query_string(), + 'type': self.get_type() + } + + def logged_in(): db = utils.db()['db'].all() if len(db) > 0: @@ -22,9 +67,11 @@ def logged_in(): return False + def get_database_content(): return utils.db()['db'].all()[0] + def login(string, entities): if logged_in(): return utils.output('end', 'message', utils.translate('already_logged_in')) @@ -42,6 +89,7 @@ def login(string, entities): utils.output('end', 'success', utils.translate('login')) + def can_play(device): if not logged_in(): utils.output('end', 'error', utils.translate('not_logged_in')) @@ -54,6 +102,41 @@ def can_play(device): return True +def parse_entities(entities): + query = SearchQuery() + for item in entities: + if item['entity'] == track: + query.track = item['sourceText'] + if item['entity'] == artist: + query.artist = item['sourceText'] + if item['entity'] == album: + query.album = item['sourceText'] + if item['entity'] == playlist: + query.playlist = item['sourceText'] + + return query + +def play(string, entities): + device_id = get_device() + # is spotify running and user logged in? + if not can_play(device_id): + return + + # create search query object from entities retrieved from the nlu + search_query = parse_entities(entities) + + if search_query.get_type() == track: + play_track(device_id, search_query) + elif search_query.get_type() == album: + play_album(device_id, search_query) + elif search_query.get_type() == artist: + play_artist(device_id, search_query) + elif search_query.get_type() == playlist: + play_playlist(device_id, search_query) + else: + play_current_track(device_id) + + def pause(string, entities): device_id = get_device() if not can_play(device_id): @@ -68,33 +151,8 @@ def play_current_track(device_id): utils.output('end', 'success', utils.translate('playing_resumed')) -def play_track(string, entities): - device_id = get_device() - if not can_play(device_id): - return - - track_name = '' - artist_name = '' - - for item in entities: - if item['entity'] == 'track': - track_name=item['sourceText'] - if item['entity'] == 'artist': - artist_name = item['sourceText'] - - # in case the nlu has misunderstood - if not track_name and not artist_name: - return play_current_track(device_id) - if artist_name and not track_name: - return play_artist(string, entities) - - params = { - 'q': track_name, - 'type': 'track' - } - - if artist_name: - params['artist'] = artist_name +def play_track(device_id, search_query): + params = search_query.get_parameters() results = spotify_request('GET', 'search', params) @@ -106,10 +164,10 @@ def play_track(string, entities): return utils.output('end', 'info', utils.translate('no_search_result')) chosen_track = None - if artist_name: + if search_query.artist: for t in results['tracks']['items']: for a in t['artists']: - if a['name'].lower() == artist_name.lower(): + if a['name'].lower() == search_query.artist.lower(): chosen_track = t break @@ -139,31 +197,8 @@ def play_track(string, entities): utils.output('end', 'success', utils.translate('now_playing', info)) -def play_album(string, entities): - device_id = get_device() - if not can_play(device_id): - return - - album_name = '' - artist_name = '' - - for item in entities: - if item['entity'] == 'album': - album_name = item['sourceText'] - if item['entity'] == 'artist': - artist_name = item['sourceText'] - - if not album_name: - play_current_track(device_id) - - params = { - 'q': album_name, - 'type': 'album' - } - - if artist_name: - params['q'] += ' artist:' + artist_name - params['artist'] = artist_name +def play_album(device_id, search_query): + params = search_query.get_parameters() results = spotify_request('GET', 'search', params) @@ -176,12 +211,12 @@ def play_album(string, entities): file = open("play_album.txt", 'a') chosen_album = None - if artist_name: + if search_query.artist: for alb in results['albums']['items']: - if alb['name'].lower() == album_name.lower(): + if alb['name'].lower() == search_query.album.lower(): for art in alb['artists']: file.write("\nAlbum: " + alb['name'] + '\t' + "Artist: " + art['name'] + '\n') - if art['name'].lower() == artist_name.lower(): + if art['name'].lower() == search_query.artist.lower(): chosen_album = alb break file.close() @@ -208,24 +243,8 @@ def play_album(string, entities): utils.output('end', 'success', utils.translate('now_playing', info)) -def play_artist(string, entities): - device_id = get_device() - if not can_play(device_id): - return - - artist_name = '' - - for item in entities: - if item['entity'] == 'artist': - artist_name = item['sourceText'] - - if not artist_name: - play_current_track(device_id) - - params = { - 'q': artist_name, - 'type': 'artist' - } +def play_artist(device_id, search_query): + params = search_query.get_parameters() results = spotify_request('GET', 'search', params) @@ -241,7 +260,7 @@ def play_artist(string, entities): chosen_artist = None for art in results['artists']['items']: file.write("\nArtist: " + art['name'] + '\n') - if art['name'].lower() == artist_name.lower(): + if art['name'].lower() == search_query.artist.lower(): chosen_artist = art break @@ -263,6 +282,10 @@ def play_artist(string, entities): utils.output('end', 'success', utils.translate('now_playing_artist', info)) +def play_playlist(device_id, search_query): + pass + + def token_expired(expires_at): now = int(time.time()) return expires_at - now < 60 From 0ade7890c8e376cfd9892412034ec1a53bdd3d0c Mon Sep 17 00:00:00 2001 From: Gustaf Holst Date: Fri, 13 Mar 2020 10:40:44 +0100 Subject: [PATCH 12/26] feat(package/spotify): bugfix + more DRY --- packages/spotify/spotify.py | 64 ++++--------------------------------- 1 file changed, 7 insertions(+), 57 deletions(-) diff --git a/packages/spotify/spotify.py b/packages/spotify/spotify.py index a46209722..a37d760d5 100644 --- a/packages/spotify/spotify.py +++ b/packages/spotify/spotify.py @@ -37,7 +37,7 @@ def get_query_string(self): if self.track and self.artist: return '{} artist:{}'.format(self.track, self.artist) if self.album and self.artist: - return '{} album:{}'.format(self.album, self.artist) + return '{} artist:{}'.format(self.album, self.artist) if self.track: return self.track if self.album: @@ -156,23 +156,10 @@ def play_track(device_id, search_query): results = spotify_request('GET', 'search', params) - file = open("mylog.txt", 'w') - file.write(json.dumps(results)) - file.close() - if not results['tracks']['total'] > 0: return utils.output('end', 'info', utils.translate('no_search_result')) - chosen_track = None - if search_query.artist: - for t in results['tracks']['items']: - for a in t['artists']: - if a['name'].lower() == search_query.artist.lower(): - chosen_track = t - break - - if not chosen_track: - chosen_track = results['tracks']['items'][0] # choose first match + chosen_track = results['tracks']['items'][0] # choose first match artists = [] @@ -182,16 +169,12 @@ def play_track(device_id, search_query): data = {} data["uris"] = [chosen_track["uri"]] - file = open("chosentrack_uri.txt", 'w') - file.write(chosen_track["uri"]) - file.close() - spotify_request('PUT', 'me/player/play', {'device_id': device_id}, json.dumps(data)) info = { "name": chosen_track['name'], "artist": ', '.join(artists), - "type": 'track' + "type": track } utils.output('end', 'success', utils.translate('now_playing', info)) @@ -202,27 +185,10 @@ def play_album(device_id, search_query): results = spotify_request('GET', 'search', params) - file = open("play_album.txt", 'w') - file.write(json.dumps(results)) - file.close() - if not results['albums']['total'] > 0: return utils.output('end', 'info', utils.translate('no_search_result')) - file = open("play_album.txt", 'a') - chosen_album = None - if search_query.artist: - for alb in results['albums']['items']: - if alb['name'].lower() == search_query.album.lower(): - for art in alb['artists']: - file.write("\nAlbum: " + alb['name'] + '\t' + "Artist: " + art['name'] + '\n') - if art['name'].lower() == search_query.artist.lower(): - chosen_album = alb - break - file.close() - - if not chosen_album: - chosen_album = results['albums']['items'][0] # choose first match + chosen_album = results['albums']['items'][0] # choose first match artists = [] @@ -237,7 +203,7 @@ def play_album(device_id, search_query): info = { "name": chosen_album['name'], "artist": ', '.join(artists), - "type": 'album' + "type": album } utils.output('end', 'success', utils.translate('now_playing', info)) @@ -248,26 +214,10 @@ def play_artist(device_id, search_query): results = spotify_request('GET', 'search', params) - file = open("play_artist.txt", 'w') - file.write(json.dumps(results)) - file.close() - if not results['artists']['total'] > 0: return utils.output('end', 'info', utils.translate('no_search_result')) - file = open("play_album.txt", 'a') - - chosen_artist = None - for art in results['artists']['items']: - file.write("\nArtist: " + art['name'] + '\n') - if art['name'].lower() == search_query.artist.lower(): - chosen_artist = art - break - - file.close() - - if not chosen_artist: - chosen_artist = results['artists']['items'][0] # choose first match + chosen_artist = results['artists']['items'][0] # choose first match data = {} data["context_uri"] = chosen_artist["uri"] @@ -276,7 +226,7 @@ def play_artist(device_id, search_query): info = { "name": chosen_artist['name'], - "type": 'artist' + "type": artist } utils.output('end', 'success', utils.translate('now_playing_artist', info)) From 6d61883f497308ef2074657c32998487d5ab6094 Mon Sep 17 00:00:00 2001 From: Gustaf Holst Date: Fri, 13 Mar 2020 11:01:16 +0100 Subject: [PATCH 13/26] feat(package/spotify): play playlist --- packages/spotify/data/answers/en.json | 4 +-- packages/spotify/data/expressions/en.json | 10 ++++++ packages/spotify/spotify.py | 44 ++++++++++++++--------- 3 files changed, 40 insertions(+), 18 deletions(-) diff --git a/packages/spotify/data/answers/en.json b/packages/spotify/data/answers/en.json index 43bb78b61..bdb06eecd 100644 --- a/packages/spotify/data/answers/en.json +++ b/packages/spotify/data/answers/en.json @@ -1,9 +1,9 @@ { "spotify": { - "now_playing": [ + "now_playing_by": [ "Now playing the %type% \"%name%\" by %artist%" ], - "now_playing_artist": [ + "now_playing": [ "Now playing the %type% \"%name%\"" ], "playing_paused": [ diff --git a/packages/spotify/data/expressions/en.json b/packages/spotify/data/expressions/en.json index c28a3123d..3d33927ad 100644 --- a/packages/spotify/data/expressions/en.json +++ b/packages/spotify/data/expressions/en.json @@ -86,6 +86,16 @@ "from": "album" } ] + }, + { + "type": "trim", + "name": "playlist", + "conditions": [ + { + "type": "after_first", + "from": "playlist" + } + ] } ] }, diff --git a/packages/spotify/spotify.py b/packages/spotify/spotify.py index a37d760d5..7b5b0e137 100644 --- a/packages/spotify/spotify.py +++ b/packages/spotify/spotify.py @@ -48,7 +48,7 @@ def get_query_string(self): return self.playlist return '' - def get_parameters(self): + def get_search_parameters(self): return { 'q': self.get_query_string(), 'type': self.get_type() @@ -116,6 +116,7 @@ def parse_entities(entities): return query + def play(string, entities): device_id = get_device() # is spotify running and user logged in? @@ -152,7 +153,7 @@ def play_current_track(device_id): def play_track(device_id, search_query): - params = search_query.get_parameters() + params = search_query.get_search_parameters() results = spotify_request('GET', 'search', params) @@ -177,11 +178,11 @@ def play_track(device_id, search_query): "type": track } - utils.output('end', 'success', utils.translate('now_playing', info)) + utils.output('end', 'success', utils.translate('now_playing_by', info)) def play_album(device_id, search_query): - params = search_query.get_parameters() + params = search_query.get_search_parameters() results = spotify_request('GET', 'search', params) @@ -206,11 +207,11 @@ def play_album(device_id, search_query): "type": album } - utils.output('end', 'success', utils.translate('now_playing', info)) + utils.output('end', 'success', utils.translate('now_playing_by', info)) def play_artist(device_id, search_query): - params = search_query.get_parameters() + params = search_query.get_search_parameters() results = spotify_request('GET', 'search', params) @@ -229,11 +230,30 @@ def play_artist(device_id, search_query): "type": artist } - utils.output('end', 'success', utils.translate('now_playing_artist', info)) + utils.output('end', 'success', utils.translate('now_playing', info)) def play_playlist(device_id, search_query): - pass + params = search_query.get_search_parameters() + + results = spotify_request('GET', 'search', params) + + if not results['playlists']['total'] > 0: + return utils.output('end', 'info', utils.translate('no_search_result')) + + chosen_artist = results['playlists']['items'][0] # choose first match + + data = {} + data["context_uri"] = chosen_artist["uri"] + + spotify_request('PUT', 'me/player/play', {'device_id': device_id}, json.dumps(data)) + + info = { + "name": chosen_artist['name'], + "type": playlist + } + + utils.output('end', 'success', utils.translate('now_playing', info)) def token_expired(expires_at): @@ -280,10 +300,6 @@ def spotify_request(method, endpoint, query_params, body_params={}): result = requests.request(method=method, url=url, headers={'Authorization': 'Bearer {0}'.format( access_token), 'Content-Type': 'application/json'}, data=body_params) - file = open("putresult.txt", 'w') - file.write(result.text) - file.close() - if result.text and len(result.text) > 0 and result.text != 'null': return result.json() else: @@ -298,10 +314,6 @@ def get_device(): url=url, headers={'Authorization': 'Bearer {0}'.format(access_token)}).json() current_device_name = socket.gethostname() - file = open('logfile.txt', 'w') - file.write(json.dumps(devices)) - file.close() - device_id = False for device in devices['devices']: if (device['name'].lower() == current_device_name): From 702aba064eeb3f3e6117249d7038d1ce0325f3b9 Mon Sep 17 00:00:00 2001 From: Gustaf Holst Date: Fri, 13 Mar 2020 12:06:33 +0100 Subject: [PATCH 14/26] feat(package/spotify): another DRY refactor --- packages/spotify/spotify.py | 132 +++++++----------------------------- 1 file changed, 26 insertions(+), 106 deletions(-) diff --git a/packages/spotify/spotify.py b/packages/spotify/spotify.py index 7b5b0e137..469a489d9 100644 --- a/packages/spotify/spotify.py +++ b/packages/spotify/spotify.py @@ -126,134 +126,54 @@ def play(string, entities): # create search query object from entities retrieved from the nlu search_query = parse_entities(entities) - if search_query.get_type() == track: - play_track(device_id, search_query) - elif search_query.get_type() == album: - play_album(device_id, search_query) - elif search_query.get_type() == artist: - play_artist(device_id, search_query) - elif search_query.get_type() == playlist: - play_playlist(device_id, search_query) - else: - play_current_track(device_id) - - -def pause(string, entities): - device_id = get_device() - if not can_play(device_id): - return - - spotify_request('PUT', 'me/player/pause', {'device_id': device_id}) - utils.output('end', 'success', utils.translate('playing_paused')) + if not search_query.get_type(): + return play_current_track(device_id) - -def play_current_track(device_id): - spotify_request('PUT', 'me/player/play', {'device_id': device_id}) - utils.output('end', 'success', utils.translate('playing_resumed')) - - -def play_track(device_id, search_query): params = search_query.get_search_parameters() results = spotify_request('GET', 'search', params) - if not results['tracks']['total'] > 0: + result_type = search_query.get_type() + 's' + if not results[result_type]['total'] > 0: return utils.output('end', 'info', utils.translate('no_search_result')) - chosen_track = results['tracks']['items'][0] # choose first match - - artists = [] - - for artist in chosen_track['artists']: - artists.append(artist['name']) + first_result_item= results[result_type]['items'][0] # choose first match data = {} - data["uris"] = [chosen_track["uri"]] - - spotify_request('PUT', 'me/player/play', {'device_id': device_id}, json.dumps(data)) - - info = { - "name": chosen_track['name'], - "artist": ', '.join(artists), - "type": track - } - - utils.output('end', 'success', utils.translate('now_playing_by', info)) - - -def play_album(device_id, search_query): - params = search_query.get_search_parameters() - - results = spotify_request('GET', 'search', params) - - if not results['albums']['total'] > 0: - return utils.output('end', 'info', utils.translate('no_search_result')) - - chosen_album = results['albums']['items'][0] # choose first match - - artists = [] - - for artist in chosen_album['artists']: - artists.append(artist['name']) - - data = {} - data["context_uri"] = chosen_album["uri"] + if (search_query.get_type() == track): + data["uris"] = [first_result_item["uri"]] + else: + data["context_uri"] = first_result_item["uri"] spotify_request('PUT', 'me/player/play', {'device_id': device_id}, json.dumps(data)) info = { - "name": chosen_album['name'], - "artist": ', '.join(artists), - "type": album + "name": first_result_item['name'], + "type": search_query.get_type() } - utils.output('end', 'success', utils.translate('now_playing_by', info)) - - -def play_artist(device_id, search_query): - params = search_query.get_search_parameters() - - results = spotify_request('GET', 'search', params) + if search_query.get_type() == track or search_query.get_type() == album: + artists = [] + for artist in first_result_item['artists']: + artists.append(artist['name']) + info["artist"] = ', '.join(artists) - if not results['artists']['total'] > 0: - return utils.output('end', 'info', utils.translate('no_search_result')) - - chosen_artist = results['artists']['items'][0] # choose first match - - data = {} - data["context_uri"] = chosen_artist["uri"] - - spotify_request('PUT', 'me/player/play', {'device_id': device_id}, json.dumps(data)) - - info = { - "name": chosen_artist['name'], - "type": artist - } + return utils.output('end', 'success', utils.translate('now_playing_by', info)) utils.output('end', 'success', utils.translate('now_playing', info)) +def pause(string, entities): + device_id = get_device() + if not can_play(device_id): + return -def play_playlist(device_id, search_query): - params = search_query.get_search_parameters() - - results = spotify_request('GET', 'search', params) - - if not results['playlists']['total'] > 0: - return utils.output('end', 'info', utils.translate('no_search_result')) - - chosen_artist = results['playlists']['items'][0] # choose first match - - data = {} - data["context_uri"] = chosen_artist["uri"] - - spotify_request('PUT', 'me/player/play', {'device_id': device_id}, json.dumps(data)) + spotify_request('PUT', 'me/player/pause', {'device_id': device_id}) + utils.output('end', 'success', utils.translate('playing_paused')) - info = { - "name": chosen_artist['name'], - "type": playlist - } - utils.output('end', 'success', utils.translate('now_playing', info)) +def play_current_track(device_id): + spotify_request('PUT', 'me/player/play', {'device_id': device_id}) + utils.output('end', 'success', utils.translate('playing_resumed')) def token_expired(expires_at): From dcb6fd8e8d8c745b921886d22d6d9eac67466d56 Mon Sep 17 00:00:00 2001 From: Gustaf Holst Date: Fri, 13 Mar 2020 18:31:40 +0100 Subject: [PATCH 15/26] feat(package/spotify): show track/album --- packages/spotify/data/answers/en.json | 13 ++ packages/spotify/data/expressions/en.json | 97 ++++++++ packages/spotify/spotify.py | 198 ++++------------- packages/spotify/spotify_utils.py | 258 ++++++++++++++++++++++ 4 files changed, 405 insertions(+), 161 deletions(-) create mode 100644 packages/spotify/spotify_utils.py diff --git a/packages/spotify/data/answers/en.json b/packages/spotify/data/answers/en.json index bdb06eecd..ca49ac76d 100644 --- a/packages/spotify/data/answers/en.json +++ b/packages/spotify/data/answers/en.json @@ -20,6 +20,15 @@ "Spotify is not running", "Please open Spotify first" ], + "no_playlists": [ + "You do not have any playlists" + ], + "show_my_playlists": [ + "You currently have %num_playlists% playlists%playlists%" + ], + "display_info": [ + "%info%" + ], "not_logged_in": [ "Please login to Spotify first", "You are not logged in to Spotify" @@ -30,6 +39,10 @@ "login": [ "Please sign in and then give me the whole URL to which you were redirected.", "Please log in using the opened tab in your browser and then paste the whole URL to which you are redirected." + ], + "contacting_spotify": [ + "Checking with spotify...", + "Let me check with Spotify" ] }, "authorize": { diff --git a/packages/spotify/data/expressions/en.json b/packages/spotify/data/expressions/en.json index 3d33927ad..0cd97b48c 100644 --- a/packages/spotify/data/expressions/en.json +++ b/packages/spotify/data/expressions/en.json @@ -107,6 +107,103 @@ "pause music" ] }, + "show_my_playlists": { + "expressions": [ + "Show my playlists", + "Display my playlists" + ] + }, + "display_info": { + "expressions": [ + "Show album the white album", + "Show track Everlong by the Foo Fighters", + "Show artist Cardi B", + "Show popular playlists", + "Show playlist Discover Weekly" + ], + "entities": [ + { + "type": "trim", + "name": "track", + "conditions": [ + { + "type": "after_first", + "from": "music" + }, + { + "type": "after_first", + "from": "song" + }, + { + "type": "after_first", + "from": "track" + }, + { + "type": "after_first", + "from": "tune" + }, + { + "type": "between", + "from": "song", + "to": "by" + }, + { + "type": "between", + "from": "track", + "to": "by" + }, + { + "type": "between", + "from": "tune", + "to": "by" + } + ] + }, + { + "type": "trim", + "name": "artist", + "conditions": [ + { + "type": "after_first", + "from": "artist" + }, + { + "type": "after_first", + "from": "group" + }, + { + "type": "after_first", + "from": "by" + } + ] + }, + { + "type": "trim", + "name": "album", + "conditions": [ + { + "type": "between", + "from": "album", + "to": "by" + }, + { + "type": "after_first", + "from": "album" + } + ] + }, + { + "type": "trim", + "name": "playlist", + "conditions": [ + { + "type": "after_first", + "from": "playlist" + } + ] + } + ] + }, "login" : { "expressions": [ "spotify login", diff --git a/packages/spotify/spotify.py b/packages/spotify/spotify.py index 469a489d9..a83fc249b 100644 --- a/packages/spotify/spotify.py +++ b/packages/spotify/spotify.py @@ -1,75 +1,12 @@ -import base64 import json -import socket -import time import webbrowser from urllib.parse import urlencode - from bridges.python import utils -import requests - -auth_base = 'https://accounts.spotify.com/authorize?' - -track = 'track' -artist = 'artist' -album = 'album' -playlist = 'playlist' - -class SearchQuery: - def __init__(self, track=None, artist=None, album=None, playlist=None): - self.track = track - self.artist = artist - self.album = album - self.playlist = playlist - - def get_type(self): - if self.track: - return track - if self.album: - return album - if self.artist: - return artist - if self.playlist: - return playlist - return None - - def get_query_string(self): - if self.track and self.artist: - return '{} artist:{}'.format(self.track, self.artist) - if self.album and self.artist: - return '{} artist:{}'.format(self.album, self.artist) - if self.track: - return self.track - if self.album: - return self.album - if self.artist: - return self.artist - if self.playlist: - return self.playlist - return '' - - def get_search_parameters(self): - return { - 'q': self.get_query_string(), - 'type': self.get_type() - } - - -def logged_in(): - db = utils.db()['db'].all() - if len(db) > 0: - db = get_database_content() # token info - now = int(time.time()) - file = open("isloggedin.txt", 'w') - file.write("Expires at: " + str(db['expires_at']) + '\t' + "Now: " + str(now)) - file.close() - return now < db['expires_at'] - - return False - - -def get_database_content(): - return utils.db()['db'].all()[0] + +from packages.spotify.spotify_utils import parse_entities, get_device, spotify_request, logged_in, can_play, \ + display_track, display_album + +from packages.spotify.spotify_utils import track, album, artist, playlist def login(string, entities): @@ -83,40 +20,13 @@ def login(string, entities): 'scope': utils.config('scope') } - url = auth_base + urlencode(config) + url = utils.config('auth_base') + urlencode(config) webbrowser.open(url) utils.output('end', 'success', utils.translate('login')) -def can_play(device): - if not logged_in(): - utils.output('end', 'error', utils.translate('not_logged_in')) - return False - - if not device: - utils.output('end', 'error', utils.translate('no_device')) - return False - - return True - - -def parse_entities(entities): - query = SearchQuery() - for item in entities: - if item['entity'] == track: - query.track = item['sourceText'] - if item['entity'] == artist: - query.artist = item['sourceText'] - if item['entity'] == album: - query.album = item['sourceText'] - if item['entity'] == playlist: - query.playlist = item['sourceText'] - - return query - - def play(string, entities): device_id = get_device() # is spotify running and user logged in? @@ -137,7 +47,7 @@ def play(string, entities): if not results[result_type]['total'] > 0: return utils.output('end', 'info', utils.translate('no_search_result')) - first_result_item= results[result_type]['items'][0] # choose first match + first_result_item = results[result_type]['items'][0] # choose first match data = {} if (search_query.get_type() == track): @@ -162,6 +72,7 @@ def play(string, entities): utils.output('end', 'success', utils.translate('now_playing', info)) + def pause(string, entities): device_id = get_device() if not can_play(device_id): @@ -176,82 +87,47 @@ def play_current_track(device_id): utils.output('end', 'success', utils.translate('playing_resumed')) -def token_expired(expires_at): - now = int(time.time()) - return expires_at - now < 60 - - -def get_access_token(): - data = get_database_content() - access_token = data['access_token'] - expires_at = data['expires_at'] - refresh_token = data['refresh_token'] - - if token_expired(expires_at): - access_token = refresh_access_token(refresh_token) - - return access_token +def display_playlist(playlist): + pass -def refresh_access_token(refresh_token): - payload = {'refresh_token': refresh_token, 'grant_type': 'refresh_token'} - - db_content = get_database_content() - client_id = db_content['client_id'] - client_secret= db_content['client_secret'] - - data = client_id + ':' + client_secret - auth_header = base64.urlsafe_b64encode(data.encode('utf-8')) - headers = {'Authorization': 'Basic %s' % auth_header.decode('utf-8')} - - response = requests.post('https://accounts.spotify.com/api/token', data=payload, headers=headers) - - token_info = response.json() - - db = utils.db()['db'] - db.update({"access_token": token_info['access_token']}) - return token_info['access_token'] +def show_my_playlists(string, entities): + if not logged_in(): + return utils.output('end', 'error', utils.translate('not_logged_in')) + utils.output('inter', 'message', utils.translate('contacting_spotify')) -def spotify_request(method, endpoint, query_params, body_params={}): - access_token = get_access_token() - url_base = get_database_content()['url_base'] - url = url_base + endpoint + '?' + urlencode(query_params) - result = requests.request(method=method, url=url, headers={'Authorization': 'Bearer {0}'.format( - access_token), 'Content-Type': 'application/json'}, data=body_params) + results = spotify_request('GET', 'me/playlists', {}) - if result.text and len(result.text) > 0 and result.text != 'null': - return result.json() - else: - return None + if not results['total'] > 0: + return utils.output('end', 'message', utils.translate('no_playlists')) + playlist_list = '
    ' + for playlist in results['items']: + playlist_list += '
  • ' + playlist['name'] + '
  • ' -def get_device(): - access_token = get_access_token() - url_base = get_database_content()['url_base'] - url = url_base + 'me/player/devices' - devices = requests.get( - url=url, headers={'Authorization': 'Bearer {0}'.format(access_token)}).json() - current_device_name = socket.gethostname() + info = { + "playlists": playlist_list, + "num_playlists": results['total'] + } - device_id = False - for device in devices['devices']: - if (device['name'].lower() == current_device_name): - device_id = device['id'] - return device_id + utils.output('inter', 'success', utils.translate('show_my_playlists', info)) -def get_current_track(): - data = {} - currently_playing = spotify_request('GET', 'me/player/currently-playing', {})['item'] +def display_info(string, entities): + search_query = parse_entities(entities) - data['name'] = currently_playing['name'] + params = search_query.get_search_parameters() - artists = [] + results = spotify_request('GET', 'search', params) - for artist in currently_playing['artists']: - artists.append(artist['name']) + result_type = search_query.get_type() + 's' + if not results[result_type]['total'] > 0: + return utils.output('end', 'info', utils.translate('no_search_result')) - data['artists'] = ', '.join(artists) + first_result_item = results[result_type]['items'][0] - return data + if search_query.get_type() == track: + display_track(first_result_item) + if search_query.get_type() == album: + display_album(first_result_item) diff --git a/packages/spotify/spotify_utils.py b/packages/spotify/spotify_utils.py new file mode 100644 index 000000000..f22e509f0 --- /dev/null +++ b/packages/spotify/spotify_utils.py @@ -0,0 +1,258 @@ +import base64 +import socket +import time +from urllib.parse import urlencode + +import requests + +from bridges.python import utils + +track = 'track' +artist = 'artist' +album = 'album' +playlist = 'playlist' + + +class SearchQuery: + def __init__(self, track=None, artist=None, album=None, playlist=None): + self.track = track + self.artist = artist + self.album = album + self.playlist = playlist + + def get_type(self): + if self.track: + return track + if self.album: + return album + if self.artist: + return artist + if self.playlist: + return playlist + return None + + def get_query_string(self): + if self.track and self.artist: + return '{} artist:{}'.format(self.track, self.artist) + if self.album and self.artist: + return '{} artist:{}'.format(self.album, self.artist) + if self.track: + return self.track + if self.album: + return self.album + if self.artist: + return self.artist + if self.playlist: + return self.playlist + return '' + + def get_search_parameters(self): + return { + 'q': self.get_query_string(), + 'type': self.get_type() + } + + +def format_time(millis): + seconds = int((millis / 1000)) % 60 + minutes = int((millis / (1000 * 60))) % 60 + hours = int((millis / (1000 * 60 * 60))) % 24 + if (hours == 0): + return '{:02d}:{:02d}'.format(minutes, seconds) + + return '{:02d}:{:02d}:{:02d}'.format(hours, minutes, seconds) + + +def parse_entities(entities): + query = SearchQuery() + for item in entities: + if item['entity'] == track: + query.track = item['sourceText'] + if item['entity'] == artist: + query.artist = item['sourceText'] + if item['entity'] == album: + query.album = item['sourceText'] + if item['entity'] == playlist: + query.playlist = item['sourceText'] + + return query + + +def track_to_html_list(info): + return "

    {}

    " \ + "

      " \ + "
    • from album {}
    • " \ + "
    • by \'{}\'
    • " \ + "
    • popularity: {}%
    • " \ + "
    • playing time: {}
    ".format(info['name'], info['album'], info['artist'], info['popularity'], info['duration']) + + +def display_track(track): + info = { + "name": track['name'], + "duration": format_time(track['duration_ms']), + "album": track['album']['name'], + "popularity": track['popularity'] + } + + artists = [] + for artist in track['artists']: + artists.append(artist['name']) + + info["artist"] = ', '.join(artists) + + utils.output('end', 'success', utils.translate('display_info', {"info": track_to_html_list(info)})) + + +def album_to_html_table(album): + tracks_table = '' + + for track in album['tracks']: + tracks_table += ''.format(track['name'], track['duration']) + + tracks_table += '
    {}{}
    ' + + return "

    {}

    " \ + "

      " \ + "
    • by \'{}\'
    • " \ + "" \ + "

      Tracks

      {}" \ + .format(album['name'], album['artist'], album['image_url'], tracks_table) + + +def display_album(album): + info = { + "name": album['name'], + "type": album['type'], + "image_url": album['images'][0]['url'] + } + + result = spotify_request('GET', 'albums/' + album['id'] + '/tracks', {}) + + file = open("album_tracks.txt", 'w') + file.write(json.dumps(result)) + file.close() + + tracks = [] + for tr in result['items']: + tracks.append({"name": tr['name'], "duration": format_time(tr['duration_ms'])}) + + info['tracks'] = tracks + + artists = [] + for art in album['artists']: + artists.append(art['name']) + + info["artist"] = ', '.join(artists) + + utils.output('end', 'success', utils.translate('display_info', {"info": album_to_html_table(info)})) + + +def get_database_content(): + return utils.db()['db'].all()[0] + + +def token_expired(expires_at): + now = int(time.time()) + return expires_at - now < 60 + + +def logged_in(): + db = utils.db()['db'].all() + if len(db) > 0: + db = get_database_content() # token info + now = int(time.time()) + file = open("isloggedin.txt", 'w') + file.write("Expires at: " + str(db['expires_at']) + '\t' + "Now: " + str(now)) + file.close() + return now < db['expires_at'] + + return False + + +def can_play(device): + if not logged_in(): + utils.output('end', 'error', utils.translate('not_logged_in')) + return False + + if not device: + utils.output('end', 'error', utils.translate('no_device')) + return False + + return True + + +def get_access_token(): + data = get_database_content() + access_token = data['access_token'] + expires_at = data['expires_at'] + refresh_token = data['refresh_token'] + + if token_expired(expires_at): + access_token = refresh_access_token(refresh_token) + + return access_token + + +def refresh_access_token(refresh_token): + payload = {'refresh_token': refresh_token, 'grant_type': 'refresh_token'} + + db_content = get_database_content() + client_id = db_content['client_id'] + client_secret= db_content['client_secret'] + + data = client_id + ':' + client_secret + auth_header = base64.urlsafe_b64encode(data.encode('utf-8')) + headers = {'Authorization': 'Basic %s' % auth_header.decode('utf-8')} + + response = requests.post('https://accounts.spotify.com/api/token', data=payload, headers=headers) + + token_info = response.json() + + db = utils.db()['db'] + db.update({"access_token": token_info['access_token']}) + return token_info['access_token'] + + +def spotify_request(method, endpoint, query_params, body_params={}): + access_token = get_access_token() + url_base = get_database_content()['url_base'] + url = url_base + endpoint + '?' + urlencode(query_params) + result = requests.request(method=method, url=url, headers={'Authorization': 'Bearer {0}'.format( + access_token), 'Content-Type': 'application/json'}, data=body_params) + + if result.text and len(result.text) > 0 and result.text != 'null': + return result.json() + else: + return None + + +def get_device(): + access_token = get_access_token() + url_base = get_database_content()['url_base'] + url = url_base + 'me/player/devices' + devices = requests.get( + url=url, headers={'Authorization': 'Bearer {0}'.format(access_token)}).json() + current_device_name = socket.gethostname() + + device_id = False + for device in devices['devices']: + if (device['name'].lower() == current_device_name): + device_id = device['id'] + return device_id + + +def get_current_track(): + data = {} + currently_playing = spotify_request('GET', 'me/player/currently-playing', {})['item'] + + data['name'] = currently_playing['name'] + + artists = [] + + for artist in currently_playing['artists']: + artists.append(artist['name']) + + data['artists'] = ', '.join(artists) + + return data From 388f1ffbf1db0c92d3eb6934693f5e0fcca685ed Mon Sep 17 00:00:00 2001 From: Gustaf Holst Date: Fri, 13 Mar 2020 18:33:05 +0100 Subject: [PATCH 16/26] feat(package/spotify): add spotify authorization endpoint to server.js --- app/spotify_auth.html | 28 ++++++++++++++++++++++++++++ server/src/core/server.js | 3 +++ 2 files changed, 31 insertions(+) create mode 100644 app/spotify_auth.html diff --git a/app/spotify_auth.html b/app/spotify_auth.html new file mode 100644 index 000000000..7f8d3dcec --- /dev/null +++ b/app/spotify_auth.html @@ -0,0 +1,28 @@ + + + + + + + Leon - Spotify authorization + + +
      +

      Spotify authorization

      +

      Copy and paste the url of this tab into Leon and then you can close this tab

      +
      + + + + + diff --git a/server/src/core/server.js b/server/src/core/server.js index 7ab7f1d91..170434011 100644 --- a/server/src/core/server.js +++ b/server/src/core/server.js @@ -75,6 +75,9 @@ class Server { app.get('/', (req, res) => { res.sendFile(path.resolve(`${__dirname}/../../../app/index.html`)) }) + app.get('/spotify_auth', (req, res) => { + res.sendFile(path.resolve(`${__dirname}/../../../app/spotify_auth.html`)) + }) app.use(`/${apiVersion}/info`, infoRouter) app.use(`/${apiVersion}/downloads`, downloadRouter) From 79ab60c22763ba76e8f682d7ccf9063c591576b8 Mon Sep 17 00:00:00 2001 From: Gustaf Holst Date: Fri, 13 Mar 2020 19:30:26 +0100 Subject: [PATCH 17/26] feat(package/spotify): show playlist --- packages/spotify/data/expressions/en.json | 6 ++++- packages/spotify/spotify.py | 8 +++--- packages/spotify/spotify_utils.py | 33 +++++++++++++++++++++++ 3 files changed, 41 insertions(+), 6 deletions(-) diff --git a/packages/spotify/data/expressions/en.json b/packages/spotify/data/expressions/en.json index 0cd97b48c..dd16d5907 100644 --- a/packages/spotify/data/expressions/en.json +++ b/packages/spotify/data/expressions/en.json @@ -119,7 +119,11 @@ "Show track Everlong by the Foo Fighters", "Show artist Cardi B", "Show popular playlists", - "Show playlist Discover Weekly" + "Show playlist Discover Weekly", + "Display album rubber soul", + "Display track War Pigs by Black Sabbath", + "Display artist Joni Mitchell", + "Display playlist Discover Weekly" ], "entities": [ { diff --git a/packages/spotify/spotify.py b/packages/spotify/spotify.py index a83fc249b..1b7f0e267 100644 --- a/packages/spotify/spotify.py +++ b/packages/spotify/spotify.py @@ -4,7 +4,7 @@ from bridges.python import utils from packages.spotify.spotify_utils import parse_entities, get_device, spotify_request, logged_in, can_play, \ - display_track, display_album + display_track, display_album, format_time, album_to_html_table from packages.spotify.spotify_utils import track, album, artist, playlist @@ -87,10 +87,6 @@ def play_current_track(device_id): utils.output('end', 'success', utils.translate('playing_resumed')) -def display_playlist(playlist): - pass - - def show_my_playlists(string, entities): if not logged_in(): return utils.output('end', 'error', utils.translate('not_logged_in')) @@ -131,3 +127,5 @@ def display_info(string, entities): display_track(first_result_item) if search_query.get_type() == album: display_album(first_result_item) + if search_query.get_type() == playlist: + display_playlist(first_result_item) diff --git a/packages/spotify/spotify_utils.py b/packages/spotify/spotify_utils.py index f22e509f0..8943ebc5d 100644 --- a/packages/spotify/spotify_utils.py +++ b/packages/spotify/spotify_utils.py @@ -1,4 +1,5 @@ import base64 +import json import socket import time from urllib.parse import urlencode @@ -148,6 +149,38 @@ def display_album(album): utils.output('end', 'success', utils.translate('display_info', {"info": album_to_html_table(info)})) +def playlist_to_html_table(playlist): + tracks_table = '' + for track in playlist['tracks']: + tracks_table += ''.format(track['name'], track['artist'], track['duration']) + tracks_table += '
      Track nameArtistPlaying time
      {}{}{}
      ' + + return "

      {}

      " \ + "

        " \ + "{}" \ + .format(playlist['name'], tracks_table) + + +def display_playlist(playlist): + info = { + "name": playlist['name'] + } + + result = spotify_request('GET', 'playlists/' + playlist['id'] + '/tracks', {}) + + file = open("playlist_tracks.txt", 'w') + file.write(json.dumps(result)) + file.close() + + tracks = [] + for tr in result['items']: + tracks.append({"name": tr['track']['name'], "duration": format_time(tr['track']['duration_ms']), "artist": tr['track']['artists'][0]['name']}) + + info['tracks'] = tracks + + utils.output('end', 'success', utils.translate('display_info', {"info": playlist_to_html_table(info)})) + + def get_database_content(): return utils.db()['db'].all()[0] From e69a12fd20fcbf77c672d470b8ed08cb5e8cc3dc Mon Sep 17 00:00:00 2001 From: Gustaf Holst Date: Fri, 13 Mar 2020 20:34:08 +0100 Subject: [PATCH 18/26] feat(package/spotify): bugfix deleted track in playlist --- packages/spotify/authorize.py | 4 ---- packages/spotify/data/answers/en.json | 3 +++ packages/spotify/spotify.py | 22 ++++++++++++------ packages/spotify/spotify_utils.py | 33 ++++++++++----------------- 4 files changed, 30 insertions(+), 32 deletions(-) diff --git a/packages/spotify/authorize.py b/packages/spotify/authorize.py index f46d1d71a..7462effca 100644 --- a/packages/spotify/authorize.py +++ b/packages/spotify/authorize.py @@ -25,10 +25,6 @@ def run(string, entities): token = results.json() - file = open("access_token.txt", 'w') - file.write(json.dumps(token)) - file.close() - # add info fields to token object token['expires_at'] = int(time.time()) + token['expires_in'] token['client_id'] = utils.config('client_id') diff --git a/packages/spotify/data/answers/en.json b/packages/spotify/data/answers/en.json index ca49ac76d..8c2b81295 100644 --- a/packages/spotify/data/answers/en.json +++ b/packages/spotify/data/answers/en.json @@ -33,6 +33,9 @@ "Please login to Spotify first", "You are not logged in to Spotify" ], + "no_search_term": [ + "Please provide a search term" + ], "already_logged_in": [ "You are already logged in to Spotify" ], diff --git a/packages/spotify/spotify.py b/packages/spotify/spotify.py index 1b7f0e267..3cdcf28f6 100644 --- a/packages/spotify/spotify.py +++ b/packages/spotify/spotify.py @@ -4,7 +4,7 @@ from bridges.python import utils from packages.spotify.spotify_utils import parse_entities, get_device, spotify_request, logged_in, can_play, \ - display_track, display_album, format_time, album_to_html_table + display_track, display_album, display_playlist from packages.spotify.spotify_utils import track, album, artist, playlist @@ -24,7 +24,7 @@ def login(string, entities): webbrowser.open(url) - utils.output('end', 'success', utils.translate('login')) + return utils.output('end', 'success', utils.translate('login')) def play(string, entities): @@ -70,7 +70,7 @@ def play(string, entities): return utils.output('end', 'success', utils.translate('now_playing_by', info)) - utils.output('end', 'success', utils.translate('now_playing', info)) + return utils.output('end', 'success', utils.translate('now_playing', info)) def pause(string, entities): @@ -84,7 +84,7 @@ def pause(string, entities): def play_current_track(device_id): spotify_request('PUT', 'me/player/play', {'device_id': device_id}) - utils.output('end', 'success', utils.translate('playing_resumed')) + return utils.output('end', 'success', utils.translate('playing_resumed')) def show_my_playlists(string, entities): @@ -107,16 +107,22 @@ def show_my_playlists(string, entities): "num_playlists": results['total'] } - utils.output('inter', 'success', utils.translate('show_my_playlists', info)) + return utils.output('inter', 'success', utils.translate('show_my_playlists', info)) def display_info(string, entities): search_query = parse_entities(entities) + if not search_query.get_type(): + return utils.output('end', 'message', utils.translate('no_search_term')) + params = search_query.get_search_parameters() + params['limit'] = 1 results = spotify_request('GET', 'search', params) + utils.output('inter', 'message', utils.translate('contacting_spotify')) + result_type = search_query.get_type() + 's' if not results[result_type]['total'] > 0: return utils.output('end', 'info', utils.translate('no_search_result')) @@ -125,7 +131,9 @@ def display_info(string, entities): if search_query.get_type() == track: display_track(first_result_item) - if search_query.get_type() == album: + elif search_query.get_type() == album: display_album(first_result_item) - if search_query.get_type() == playlist: + elif search_query.get_type() == playlist: display_playlist(first_result_item) + + return diff --git a/packages/spotify/spotify_utils.py b/packages/spotify/spotify_utils.py index 8943ebc5d..473436eea 100644 --- a/packages/spotify/spotify_utils.py +++ b/packages/spotify/spotify_utils.py @@ -1,5 +1,4 @@ import base64 -import json import socket import time from urllib.parse import urlencode @@ -102,7 +101,7 @@ def display_track(track): info["artist"] = ', '.join(artists) - utils.output('end', 'success', utils.translate('display_info', {"info": track_to_html_list(info)})) + return utils.output('end', 'success', utils.translate('display_info', {"info": track_to_html_list(info)})) def album_to_html_table(album): @@ -130,10 +129,6 @@ def display_album(album): result = spotify_request('GET', 'albums/' + album['id'] + '/tracks', {}) - file = open("album_tracks.txt", 'w') - file.write(json.dumps(result)) - file.close() - tracks = [] for tr in result['items']: tracks.append({"name": tr['name'], "duration": format_time(tr['duration_ms'])}) @@ -146,7 +141,7 @@ def display_album(album): info["artist"] = ', '.join(artists) - utils.output('end', 'success', utils.translate('display_info', {"info": album_to_html_table(info)})) + return utils.output('end', 'success', utils.translate('display_info', {"info": album_to_html_table(info)})) def playlist_to_html_table(playlist): @@ -156,29 +151,29 @@ def playlist_to_html_table(playlist): tracks_table += '' return "

        {}

        " \ + "

        {}

        " \ "

          " \ "{}" \ - .format(playlist['name'], tracks_table) + .format(playlist['name'], playlist['description'], tracks_table) def display_playlist(playlist): info = { - "name": playlist['name'] + "name": playlist['name'], + "description": playlist['description'] } - result = spotify_request('GET', 'playlists/' + playlist['id'] + '/tracks', {}) - - file = open("playlist_tracks.txt", 'w') - file.write(json.dumps(result)) - file.close() + result = spotify_request('GET', 'playlists/' + playlist['id'] + '/tracks', {"limit": 20}) tracks = [] for tr in result['items']: - tracks.append({"name": tr['track']['name'], "duration": format_time(tr['track']['duration_ms']), "artist": tr['track']['artists'][0]['name']}) + # tracks may be null if not available anymore + if tr: + tracks.append({"name": tr['track']['name'], "duration": format_time(tr['track']['duration_ms']), "artist": tr['track']['artists'][0]['name']}) info['tracks'] = tracks - utils.output('end', 'success', utils.translate('display_info', {"info": playlist_to_html_table(info)})) + return utils.output('end', 'success', utils.translate('display_info', {"info": playlist_to_html_table(info)})) def get_database_content(): @@ -195,9 +190,6 @@ def logged_in(): if len(db) > 0: db = get_database_content() # token info now = int(time.time()) - file = open("isloggedin.txt", 'w') - file.write("Expires at: " + str(db['expires_at']) + '\t' + "Now: " + str(now)) - file.close() return now < db['expires_at'] return False @@ -264,8 +256,7 @@ def get_device(): access_token = get_access_token() url_base = get_database_content()['url_base'] url = url_base + 'me/player/devices' - devices = requests.get( - url=url, headers={'Authorization': 'Bearer {0}'.format(access_token)}).json() + devices = requests.get(url=url, headers={'Authorization': 'Bearer {0}'.format(access_token)}).json() current_device_name = socket.gethostname() device_id = False From a2261dd7298649beb1933cb25e60fe476721c4b1 Mon Sep 17 00:00:00 2001 From: Gustaf Holst Date: Fri, 13 Mar 2020 22:29:31 +0100 Subject: [PATCH 19/26] feat(package/spotify): show artist --- packages/spotify/authorize.py | 41 --------------- packages/spotify/config/config.sample.json | 8 --- packages/spotify/data/answers/en.json | 4 +- packages/spotify/data/expressions/en.json | 8 ++- packages/spotify/spotify.py | 44 +++++++++++++++- packages/spotify/spotify_utils.py | 61 ++++++++++++++++++++-- 6 files changed, 105 insertions(+), 61 deletions(-) delete mode 100644 packages/spotify/authorize.py diff --git a/packages/spotify/authorize.py b/packages/spotify/authorize.py deleted file mode 100644 index 7462effca..000000000 --- a/packages/spotify/authorize.py +++ /dev/null @@ -1,41 +0,0 @@ -import base64 -import json -import time - -import requests - -from bridges.python import utils - -def run(string, entities): - db = utils.db()['db'] - - # parse url containing access code (pasted by user) - code = string.split('?code=')[1].split("&")[0] - - payload = {'redirect_uri': utils.config('redirect_uri'), - 'code': code, - 'grant_type': 'authorization_code', - 'scope': utils.config('scope')} - - data = utils.config('client_id') + ':' + utils.config('client_secret') - auth_header = base64.urlsafe_b64encode(data.encode('utf-8')) - headers = {'Authorization': 'Basic %s' % auth_header.decode('utf-8')} - - results = requests.post('https://accounts.spotify.com/api/token', data=payload, headers=headers) - - token = results.json() - - # add info fields to token object - token['expires_at'] = int(time.time()) + token['expires_in'] - token['client_id'] = utils.config('client_id') - token['client_secret'] = utils.config('client_secret') - token['url_base'] = utils.config('url_base') - token['scope'] = utils.config('scope') - - # in case new login because of expired token - if len(db.all()) > 0: - db.purge() - - db.insert(token) - - utils.output('end', 'success', utils.translate('logged_in')) diff --git a/packages/spotify/config/config.sample.json b/packages/spotify/config/config.sample.json index 611b68efc..05e6d6936 100644 --- a/packages/spotify/config/config.sample.json +++ b/packages/spotify/config/config.sample.json @@ -5,14 +5,6 @@ "url_base" : "https://api.spotify.com/v1/", "redirect_uri": "http://localhost:1337/callback", "scope": "user-read-playback-state,user-modify-playback-state", - "options": {} - }, - "authorize": { - "client_id": "PASTE CLIENT ID HERE", - "client_secret": "PASTE CLIENT SECRET HERE", - "url_base" : "https://api.spotify.com/v1/", - "redirect_uri": "http://localhost:1337/callback", - "scope": "user-read-playback-state,user-modify-playback-state", "options": {} } } diff --git a/packages/spotify/data/answers/en.json b/packages/spotify/data/answers/en.json index 8c2b81295..6c451f4a6 100644 --- a/packages/spotify/data/answers/en.json +++ b/packages/spotify/data/answers/en.json @@ -46,9 +46,7 @@ "contacting_spotify": [ "Checking with spotify...", "Let me check with Spotify" - ] - }, - "authorize": { + ], "logged_in": [ "Great, you are now logged in to Spotify!", "Great, you are now logged in to Spotify!\nTry playing a track!", diff --git a/packages/spotify/data/expressions/en.json b/packages/spotify/data/expressions/en.json index dd16d5907..9ea006610 100644 --- a/packages/spotify/data/expressions/en.json +++ b/packages/spotify/data/expressions/en.json @@ -1,6 +1,6 @@ { "spotify": { - "play": { + "play": { "expressions": [ "Play", "Resume", @@ -213,10 +213,8 @@ "spotify login", "login to spotify" ] - } - }, - "authorize" : { - "run" : { + }, + "authorize" : { "expressions": [ "http://localhost:1337/callback?code= " ], diff --git a/packages/spotify/spotify.py b/packages/spotify/spotify.py index 3cdcf28f6..f3ac56088 100644 --- a/packages/spotify/spotify.py +++ b/packages/spotify/spotify.py @@ -1,10 +1,15 @@ +import base64 import json +import time import webbrowser from urllib.parse import urlencode + +import requests + from bridges.python import utils from packages.spotify.spotify_utils import parse_entities, get_device, spotify_request, logged_in, can_play, \ - display_track, display_album, display_playlist + display_track, display_album, display_playlist, display_artist from packages.spotify.spotify_utils import track, album, artist, playlist @@ -27,6 +32,41 @@ def login(string, entities): return utils.output('end', 'success', utils.translate('login')) +def authorize(string, entities): + db = utils.db()['db'] + + # parse url containing access code (pasted by user) + code = string.split('?code=')[1].split("&")[0] + + payload = {'redirect_uri': utils.config('redirect_uri'), + 'code': code, + 'grant_type': 'authorization_code', + 'scope': utils.config('scope')} + + data = utils.config('client_id') + ':' + utils.config('client_secret') + auth_header = base64.urlsafe_b64encode(data.encode('utf-8')) + headers = {'Authorization': 'Basic %s' % auth_header.decode('utf-8')} + + results = requests.post('https://accounts.spotify.com/api/token', data=payload, headers=headers) + + token = results.json() + + # add info fields to token object + token['expires_at'] = int(time.time()) + token['expires_in'] + token['client_id'] = utils.config('client_id') + token['client_secret'] = utils.config('client_secret') + token['url_base'] = utils.config('url_base') + token['scope'] = utils.config('scope') + + # in case new login because of expired token + if len(db.all()) > 0: + db.purge() + + db.insert(token) + + utils.output('end', 'success', utils.translate('logged_in')) + + def play(string, entities): device_id = get_device() # is spotify running and user logged in? @@ -135,5 +175,7 @@ def display_info(string, entities): display_album(first_result_item) elif search_query.get_type() == playlist: display_playlist(first_result_item) + elif search_query.get_type() == artist: + display_artist(first_result_item) return diff --git a/packages/spotify/spotify_utils.py b/packages/spotify/spotify_utils.py index 473436eea..f2aa96d80 100644 --- a/packages/spotify/spotify_utils.py +++ b/packages/spotify/spotify_utils.py @@ -1,4 +1,5 @@ import base64 +import json import socket import time from urllib.parse import urlencode @@ -144,11 +145,18 @@ def display_album(album): return utils.output('end', 'success', utils.translate('display_info', {"info": album_to_html_table(info)})) -def playlist_to_html_table(playlist): +def create_tracks_table(tracks): tracks_table = '' - for track in playlist['tracks']: - tracks_table += ''.format(track['name'], track['artist'], track['duration']) + for track in tracks: + tracks_table += ''.format(track['name'], + track['artist'], + track['duration']) tracks_table += '
          Track nameArtistPlaying time
          {}{}{}
          {}{}{}
          ' + return tracks_table + + +def playlist_to_html_table(playlist): + tracks_table = create_tracks_table(playlist['tracks']) return "

          {}

          " \ "

          {}

          " \ @@ -176,6 +184,53 @@ def display_playlist(playlist): return utils.output('end', 'success', utils.translate('display_info', {"info": playlist_to_html_table(info)})) +def artist_to_html_list(info): + return "

          {}

          " \ + "

            " \ + "
          • popularity: {}%
          • " \ + "
          • followers: {}
          • " \ + "
          • genres: {}
          " \ + "

          Top tracks

          {}".format(info['name'], info['popularity'], info['num_followers'], info['genres'], info['top_tracks']) + + +def display_artist(artist): + info = { + "name": artist['name'], + "num_followers": artist['followers']['total'], + "popularity": artist['popularity'] + } + + user_info = spotify_request('GET', 'me', {}) + + file = open("me.txt", 'w') + file.write(json.dumps(user_info)) + file.close() + + result = spotify_request('GET', 'artists/' + artist['id'] + '/top-tracks', {'country': user_info['country']}) + + file = open("toptracks.txt", 'w') + file.write(json.dumps(result)) + file.close() + + top_tracks = [] + for tr in result['tracks']: + top_tracks.append({"name": tr['name'], "duration": format_time(tr['duration_ms']), "artist": artist['name']}) + + info['tracks'] = top_tracks + + top_tracks_table = create_tracks_table(top_tracks) + + info['top_tracks'] = top_tracks_table + + genres = [] + for genre in artist['genres']: + genres.append(genre) + + info["genres"] = ', '.join(genres) + + return utils.output('end', 'success', utils.translate('display_info', {"info": artist_to_html_list(info)})) + + def get_database_content(): return utils.db()['db'].all()[0] From eb50ecef56cf93cd560c2555aa99b781448d7b29 Mon Sep 17 00:00:00 2001 From: Gustaf Holst Date: Sat, 14 Mar 2020 15:32:59 +0100 Subject: [PATCH 20/26] feat(package/spotify): various fixes --- app/spotify_auth.html | 8 ++++- packages/spotify/data/answers/en.json | 3 ++ packages/spotify/spotify.py | 47 +++++++++++++++--------- packages/spotify/spotify_utils.py | 51 ++++++++++++++------------- 4 files changed, 67 insertions(+), 42 deletions(-) diff --git a/app/spotify_auth.html b/app/spotify_auth.html index 7f8d3dcec..3bcf574cd 100644 --- a/app/spotify_auth.html +++ b/app/spotify_auth.html @@ -9,7 +9,13 @@

          Spotify authorization

          -

          Copy and paste the url of this tab into Leon and then you can close this tab

          +

          Please follow these steps:

          +
            +
          1. Copy the complete url of this tab
          2. +
          3. Paste url into Leon
          4. +
          5. Close this tab
          6. +
          +
          diff --git a/packages/spotify/data/answers/en.json b/packages/spotify/data/answers/en.json index 6c451f4a6..010d7c254 100644 --- a/packages/spotify/data/answers/en.json +++ b/packages/spotify/data/answers/en.json @@ -20,6 +20,9 @@ "Spotify is not running", "Please open Spotify first" ], + "device_not_active": [ + "Spotify is not active, please activate it first by playing a track" + ], "no_playlists": [ "You do not have any playlists" ], diff --git a/packages/spotify/spotify.py b/packages/spotify/spotify.py index f3ac56088..e5be08eb0 100644 --- a/packages/spotify/spotify.py +++ b/packages/spotify/spotify.py @@ -2,16 +2,13 @@ import json import time import webbrowser -from urllib.parse import urlencode - import requests +from urllib.parse import urlencode from bridges.python import utils - from packages.spotify.spotify_utils import parse_entities, get_device, spotify_request, logged_in, can_play, \ display_track, display_album, display_playlist, display_artist - -from packages.spotify.spotify_utils import track, album, artist, playlist +from packages.spotify.spotify_utils import track_str, album_str, artist_str, playlist_str def login(string, entities): @@ -45,12 +42,20 @@ def authorize(string, entities): data = utils.config('client_id') + ':' + utils.config('client_secret') auth_header = base64.urlsafe_b64encode(data.encode('utf-8')) - headers = {'Authorization': 'Basic %s' % auth_header.decode('utf-8')} + headers = {'Authorization': 'Basic %s' % str(auth_header, 'utf-8')} + + file = open("headers.txt", 'w') + file.write(json.dumps(payload)) + file.close() results = requests.post('https://accounts.spotify.com/api/token', data=payload, headers=headers) token = results.json() + file = open("token.txt", 'w') + file.write(json.dumps(token)) + file.close() + # add info fields to token object token['expires_at'] = int(time.time()) + token['expires_in'] token['client_id'] = utils.config('client_id') @@ -68,11 +73,16 @@ def authorize(string, entities): def play(string, entities): - device_id = get_device() + if not logged_in(): + return utils.output('end', 'error', utils.translate('not_logged_in')) + + device = get_device() # is spotify running and user logged in? - if not can_play(device_id): + if not can_play(device): return + device_id = device['id'] + # create search query object from entities retrieved from the nlu search_query = parse_entities(entities) @@ -90,7 +100,7 @@ def play(string, entities): first_result_item = results[result_type]['items'][0] # choose first match data = {} - if (search_query.get_type() == track): + if (search_query.get_type() == track_str): data["uris"] = [first_result_item["uri"]] else: data["context_uri"] = first_result_item["uri"] @@ -102,7 +112,7 @@ def play(string, entities): "type": search_query.get_type() } - if search_query.get_type() == track or search_query.get_type() == album: + if search_query.get_type() == track_str or search_query.get_type() == album_str: artists = [] for artist in first_result_item['artists']: artists.append(artist['name']) @@ -114,10 +124,13 @@ def play(string, entities): def pause(string, entities): - device_id = get_device() - if not can_play(device_id): - return + if not logged_in(): + return utils.output('end', 'error', utils.translate('not_logged_in')) + device = get_device() + if not can_play(device): + return + device_id = device['id'] spotify_request('PUT', 'me/player/pause', {'device_id': device_id}) utils.output('end', 'success', utils.translate('playing_paused')) @@ -169,13 +182,13 @@ def display_info(string, entities): first_result_item = results[result_type]['items'][0] - if search_query.get_type() == track: + if search_query.get_type() == track_str: display_track(first_result_item) - elif search_query.get_type() == album: + elif search_query.get_type() == album_str: display_album(first_result_item) - elif search_query.get_type() == playlist: + elif search_query.get_type() == playlist_str: display_playlist(first_result_item) - elif search_query.get_type() == artist: + elif search_query.get_type() == artist_str: display_artist(first_result_item) return diff --git a/packages/spotify/spotify_utils.py b/packages/spotify/spotify_utils.py index f2aa96d80..e75a9e55e 100644 --- a/packages/spotify/spotify_utils.py +++ b/packages/spotify/spotify_utils.py @@ -2,16 +2,15 @@ import json import socket import time -from urllib.parse import urlencode - import requests +from urllib.parse import urlencode from bridges.python import utils -track = 'track' -artist = 'artist' -album = 'album' -playlist = 'playlist' +track_str = 'track' +artist_str = 'artist' +album_str = 'album' +playlist_str = 'playlist' class SearchQuery: @@ -23,13 +22,13 @@ def __init__(self, track=None, artist=None, album=None, playlist=None): def get_type(self): if self.track: - return track + return track_str if self.album: - return album + return album_str if self.artist: - return artist + return artist_str if self.playlist: - return playlist + return playlist_str return None def get_query_string(self): @@ -67,13 +66,13 @@ def format_time(millis): def parse_entities(entities): query = SearchQuery() for item in entities: - if item['entity'] == track: + if item['entity'] == track_str: query.track = item['sourceText'] - if item['entity'] == artist: + if item['entity'] == artist_str: query.artist = item['sourceText'] - if item['entity'] == album: + if item['entity'] == album_str: query.album = item['sourceText'] - if item['entity'] == playlist: + if item['entity'] == playlist_str: query.playlist = item['sourceText'] return query @@ -159,7 +158,7 @@ def playlist_to_html_table(playlist): tracks_table = create_tracks_table(playlist['tracks']) return "

          {}

          " \ - "

          {}

          " \ + "

          {}

          " \ "

            " \ "{}" \ .format(playlist['name'], playlist['description'], tracks_table) @@ -251,14 +250,14 @@ def logged_in(): def can_play(device): - if not logged_in(): - utils.output('end', 'error', utils.translate('not_logged_in')) - return False - if not device: utils.output('end', 'error', utils.translate('no_device')) return False + if device and not device['is_active']: + utils.output('end', 'error', utils.translate('device_not_active')) + return False + return True @@ -314,11 +313,15 @@ def get_device(): devices = requests.get(url=url, headers={'Authorization': 'Bearer {0}'.format(access_token)}).json() current_device_name = socket.gethostname() - device_id = False - for device in devices['devices']: - if (device['name'].lower() == current_device_name): - device_id = device['id'] - return device_id + file = open("devices.txt", 'w') + file.write(json.dumps(devices)) + file.close() + + device = None + for dev in devices['devices']: + if (dev['name'].lower() == current_device_name): + device = dev + return device def get_current_track(): From 6a58d7e4b5d58ddd6d3c050bef4ba3b18fc23419 Mon Sep 17 00:00:00 2001 From: Gustaf Holst Date: Sat, 14 Mar 2020 15:55:17 +0100 Subject: [PATCH 21/26] feat(package/spotify): update readme --- packages/spotify/README.md | 9 +++++++-- packages/spotify/data/expressions/en.json | 1 - packages/spotify/spotify.py | 8 -------- packages/spotify/spotify_utils.py | 12 ------------ 4 files changed, 7 insertions(+), 23 deletions(-) diff --git a/packages/spotify/README.md b/packages/spotify/README.md index d51c41ad7..b030d13d1 100644 --- a/packages/spotify/README.md +++ b/packages/spotify/README.md @@ -12,8 +12,8 @@ Use Leon to start|stop|search tracks|albums|artists on Spotify. This package requires a Spotify Premium (:moneybag:) account. Once you have [signed up](https://www.spotify.com) please follow these steps in order to start using Spotify via Leon -1. Login at the [Spotify web api for developers](https://developer.spotify.com/dashboard/login) -2. Create a client id (suitable app name: "Leon") +1. Login to [Spotify web api for developers](https://developer.spotify.com/dashboard/login) +2. Create a new client id (suitable app name: "Leon") 3. Make sure you have - a client id - a client secret @@ -27,6 +27,11 @@ follow these steps in order to start using Spotify via Leon "Play artist Led Zeppelin" "Pause" "Resume" + "Spotify Login" + "Show artist Cardi B", + "Show playlist Discover Weekly", + "Display album Rubber Soul", + "Display track War Pigs by Black Sabbath", (fr-FR) "" ... diff --git a/packages/spotify/data/expressions/en.json b/packages/spotify/data/expressions/en.json index 9ea006610..9819ebf1d 100644 --- a/packages/spotify/data/expressions/en.json +++ b/packages/spotify/data/expressions/en.json @@ -118,7 +118,6 @@ "Show album the white album", "Show track Everlong by the Foo Fighters", "Show artist Cardi B", - "Show popular playlists", "Show playlist Discover Weekly", "Display album rubber soul", "Display track War Pigs by Black Sabbath", diff --git a/packages/spotify/spotify.py b/packages/spotify/spotify.py index e5be08eb0..ff2daa3c3 100644 --- a/packages/spotify/spotify.py +++ b/packages/spotify/spotify.py @@ -44,18 +44,10 @@ def authorize(string, entities): auth_header = base64.urlsafe_b64encode(data.encode('utf-8')) headers = {'Authorization': 'Basic %s' % str(auth_header, 'utf-8')} - file = open("headers.txt", 'w') - file.write(json.dumps(payload)) - file.close() - results = requests.post('https://accounts.spotify.com/api/token', data=payload, headers=headers) token = results.json() - file = open("token.txt", 'w') - file.write(json.dumps(token)) - file.close() - # add info fields to token object token['expires_at'] = int(time.time()) + token['expires_in'] token['client_id'] = utils.config('client_id') diff --git a/packages/spotify/spotify_utils.py b/packages/spotify/spotify_utils.py index e75a9e55e..97c8804fc 100644 --- a/packages/spotify/spotify_utils.py +++ b/packages/spotify/spotify_utils.py @@ -201,16 +201,8 @@ def display_artist(artist): user_info = spotify_request('GET', 'me', {}) - file = open("me.txt", 'w') - file.write(json.dumps(user_info)) - file.close() - result = spotify_request('GET', 'artists/' + artist['id'] + '/top-tracks', {'country': user_info['country']}) - file = open("toptracks.txt", 'w') - file.write(json.dumps(result)) - file.close() - top_tracks = [] for tr in result['tracks']: top_tracks.append({"name": tr['name'], "duration": format_time(tr['duration_ms']), "artist": artist['name']}) @@ -313,10 +305,6 @@ def get_device(): devices = requests.get(url=url, headers={'Authorization': 'Bearer {0}'.format(access_token)}).json() current_device_name = socket.gethostname() - file = open("devices.txt", 'w') - file.write(json.dumps(devices)) - file.close() - device = None for dev in devices['devices']: if (dev['name'].lower() == current_device_name): From f5c9bc14e612acdeaf084f2c99a87657f87f76d7 Mon Sep 17 00:00:00 2001 From: Gustaf Holst Date: Mon, 16 Mar 2020 11:55:14 +0100 Subject: [PATCH 22/26] feat(package/spotify): add shebangs --- packages/spotify/config/config.sample.json | 2 +- packages/spotify/spotify.py | 3 +++ packages/spotify/spotify_utils.py | 4 +++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/spotify/config/config.sample.json b/packages/spotify/config/config.sample.json index 05e6d6936..8ef612971 100644 --- a/packages/spotify/config/config.sample.json +++ b/packages/spotify/config/config.sample.json @@ -3,7 +3,7 @@ "client_id": "PASTE CLIENT ID HERE", "client_secret": "PASTE CLIENT SECRET HERE", "url_base" : "https://api.spotify.com/v1/", - "redirect_uri": "http://localhost:1337/callback", + "redirect_uri": "http://localhost:1337/spotify_auth", "scope": "user-read-playback-state,user-modify-playback-state", "options": {} } diff --git a/packages/spotify/spotify.py b/packages/spotify/spotify.py index ff2daa3c3..3ecde3e1b 100644 --- a/packages/spotify/spotify.py +++ b/packages/spotify/spotify.py @@ -1,3 +1,6 @@ +#!/usr/bin/env python +# -*- coding:utf-8 -*- + import base64 import json import time diff --git a/packages/spotify/spotify_utils.py b/packages/spotify/spotify_utils.py index 97c8804fc..910fedca7 100644 --- a/packages/spotify/spotify_utils.py +++ b/packages/spotify/spotify_utils.py @@ -1,5 +1,7 @@ +#!/usr/bin/env python +# -*- coding:utf-8 -*- + import base64 -import json import socket import time import requests From e9e8d9188723363873b1183b42c471525f546157 Mon Sep 17 00:00:00 2001 From: Gustaf Holst Date: Tue, 17 Mar 2020 19:12:41 +0100 Subject: [PATCH 23/26] feat(package/spotify): correct config.sample.json --- packages/math/__init__.py | 0 packages/math/calculator.py | 5 ----- packages/math/config/.gitkeep | 0 packages/math/config/config.sample.json | 5 ----- packages/math/data/.gitkeep | 0 packages/math/data/answers/.gitkeep | 0 packages/math/data/answers/en.json | 3 --- packages/math/data/answers/fr.json | 3 --- packages/math/data/db/.gitkeep | 0 packages/math/data/expressions/.gitkeep | 0 packages/math/data/expressions/en.json | 16 ---------------- packages/math/data/expressions/fr.json | 16 ---------------- packages/math/percentage.py | 5 ----- packages/spotify/config/config.sample.json | 1 + 14 files changed, 1 insertion(+), 53 deletions(-) delete mode 100644 packages/math/__init__.py delete mode 100644 packages/math/calculator.py delete mode 100644 packages/math/config/.gitkeep delete mode 100644 packages/math/config/config.sample.json delete mode 100644 packages/math/data/.gitkeep delete mode 100644 packages/math/data/answers/.gitkeep delete mode 100644 packages/math/data/answers/en.json delete mode 100644 packages/math/data/answers/fr.json delete mode 100644 packages/math/data/db/.gitkeep delete mode 100644 packages/math/data/expressions/.gitkeep delete mode 100644 packages/math/data/expressions/en.json delete mode 100644 packages/math/data/expressions/fr.json delete mode 100644 packages/math/percentage.py diff --git a/packages/math/__init__.py b/packages/math/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/math/calculator.py b/packages/math/calculator.py deleted file mode 100644 index 8d3d61a5e..000000000 --- a/packages/math/calculator.py +++ /dev/null @@ -1,5 +0,0 @@ - -from bridges.python import utils - -def run(string, entities): - pass diff --git a/packages/math/config/.gitkeep b/packages/math/config/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/math/config/config.sample.json b/packages/math/config/config.sample.json deleted file mode 100644 index 2db8332a6..000000000 --- a/packages/math/config/config.sample.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "calculation": { - "options": {} - } -} diff --git a/packages/math/data/.gitkeep b/packages/math/data/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/math/data/answers/.gitkeep b/packages/math/data/answers/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/math/data/answers/en.json b/packages/math/data/answers/en.json deleted file mode 100644 index 0db3279e4..000000000 --- a/packages/math/data/answers/en.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - -} diff --git a/packages/math/data/answers/fr.json b/packages/math/data/answers/fr.json deleted file mode 100644 index 0db3279e4..000000000 --- a/packages/math/data/answers/fr.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - -} diff --git a/packages/math/data/db/.gitkeep b/packages/math/data/db/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/math/data/expressions/.gitkeep b/packages/math/data/expressions/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/math/data/expressions/en.json b/packages/math/data/expressions/en.json deleted file mode 100644 index dc1b32896..000000000 --- a/packages/math/data/expressions/en.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "calculator": { - "run": { - "expressions": [ - - ] - } - }, - "percentages": { - "run": { - "expressions": [ - - ] - } - } -} diff --git a/packages/math/data/expressions/fr.json b/packages/math/data/expressions/fr.json deleted file mode 100644 index dc1b32896..000000000 --- a/packages/math/data/expressions/fr.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "calculator": { - "run": { - "expressions": [ - - ] - } - }, - "percentages": { - "run": { - "expressions": [ - - ] - } - } -} diff --git a/packages/math/percentage.py b/packages/math/percentage.py deleted file mode 100644 index 8d3d61a5e..000000000 --- a/packages/math/percentage.py +++ /dev/null @@ -1,5 +0,0 @@ - -from bridges.python import utils - -def run(string, entities): - pass diff --git a/packages/spotify/config/config.sample.json b/packages/spotify/config/config.sample.json index 8ef612971..6a4d32ca7 100644 --- a/packages/spotify/config/config.sample.json +++ b/packages/spotify/config/config.sample.json @@ -3,6 +3,7 @@ "client_id": "PASTE CLIENT ID HERE", "client_secret": "PASTE CLIENT SECRET HERE", "url_base" : "https://api.spotify.com/v1/", + "auth_base": "https://accounts.spotify.com/authorize?", "redirect_uri": "http://localhost:1337/spotify_auth", "scope": "user-read-playback-state,user-modify-playback-state", "options": {} From 162089f735a540556409ba63685256cda449a25f Mon Sep 17 00:00:00 2001 From: Gustaf Holst Date: Wed, 18 Mar 2020 12:50:03 +0100 Subject: [PATCH 24/26] feat(package/spotify): added scope to config.sample.json --- packages/spotify/config/config.sample.json | 2 +- packages/spotify/spotify_utils.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/spotify/config/config.sample.json b/packages/spotify/config/config.sample.json index 6a4d32ca7..867bf4f14 100644 --- a/packages/spotify/config/config.sample.json +++ b/packages/spotify/config/config.sample.json @@ -5,7 +5,7 @@ "url_base" : "https://api.spotify.com/v1/", "auth_base": "https://accounts.spotify.com/authorize?", "redirect_uri": "http://localhost:1337/spotify_auth", - "scope": "user-read-playback-state,user-modify-playback-state", + "scope": "user-read-playback-state,user-modify-playback-state,user-read-private", "options": {} } } diff --git a/packages/spotify/spotify_utils.py b/packages/spotify/spotify_utils.py index 910fedca7..8f607388d 100644 --- a/packages/spotify/spotify_utils.py +++ b/packages/spotify/spotify_utils.py @@ -2,6 +2,7 @@ # -*- coding:utf-8 -*- import base64 +import json import socket import time import requests From b6c4895a09db340cd6aefc9cebe261dbe907cae3 Mon Sep 17 00:00:00 2001 From: Gustaf Holst Date: Wed, 18 Mar 2020 16:29:38 +0100 Subject: [PATCH 25/26] feat(package/spotify): minor fixes --- packages/spotify/spotify.py | 6 +++--- packages/spotify/spotify_utils.py | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/spotify/spotify.py b/packages/spotify/spotify.py index 3ecde3e1b..0448221a0 100644 --- a/packages/spotify/spotify.py +++ b/packages/spotify/spotify.py @@ -44,8 +44,8 @@ def authorize(string, entities): 'scope': utils.config('scope')} data = utils.config('client_id') + ':' + utils.config('client_secret') - auth_header = base64.urlsafe_b64encode(data.encode('utf-8')) - headers = {'Authorization': 'Basic %s' % str(auth_header, 'utf-8')} + encoded_data = base64.urlsafe_b64encode(data.encode('utf-8')) + headers = {'Authorization': 'Basic {}'.format(str(encoded_data, 'utf-8'))} results = requests.post('https://accounts.spotify.com/api/token', data=payload, headers=headers) @@ -64,7 +64,7 @@ def authorize(string, entities): db.insert(token) - utils.output('end', 'success', utils.translate('logged_in')) + return utils.output('end', 'success', utils.translate('logged_in')) def play(string, entities): diff --git a/packages/spotify/spotify_utils.py b/packages/spotify/spotify_utils.py index 8f607388d..910fedca7 100644 --- a/packages/spotify/spotify_utils.py +++ b/packages/spotify/spotify_utils.py @@ -2,7 +2,6 @@ # -*- coding:utf-8 -*- import base64 -import json import socket import time import requests From e76d8584ed70a7f7a96b7d191999aee06340d0f2 Mon Sep 17 00:00:00 2001 From: Gustaf Holst Date: Sun, 22 Mar 2020 18:35:14 +0100 Subject: [PATCH 26/26] feat(package/spotify): add workaround for authorization bug --- packages/spotify/data/answers/en.json | 3 +++ packages/spotify/spotify.py | 6 +++++- packages/spotify/spotify_utils.py | 21 +++++++++++++++++---- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/packages/spotify/data/answers/en.json b/packages/spotify/data/answers/en.json index 010d7c254..b7ca6cf5a 100644 --- a/packages/spotify/data/answers/en.json +++ b/packages/spotify/data/answers/en.json @@ -46,6 +46,9 @@ "Please sign in and then give me the whole URL to which you were redirected.", "Please log in using the opened tab in your browser and then paste the whole URL to which you are redirected." ], + "try_again_workaround": [ + "Oops, something went wrong during the authorization process, please try logging in again!" + ], "contacting_spotify": [ "Checking with spotify...", "Let me check with Spotify" diff --git a/packages/spotify/spotify.py b/packages/spotify/spotify.py index 0448221a0..cc951fbef 100644 --- a/packages/spotify/spotify.py +++ b/packages/spotify/spotify.py @@ -25,7 +25,7 @@ def login(string, entities): 'scope': utils.config('scope') } - url = utils.config('auth_base') + urlencode(config) + url = "{}{}".format(utils.config('auth_base'), urlencode(config)) webbrowser.open(url) @@ -38,6 +38,10 @@ def authorize(string, entities): # parse url containing access code (pasted by user) code = string.split('?code=')[1].split("&")[0] + # workaround for what I can only assume is an error in the spotify accounts service + if code.endswith("#_=_"): + return utils.output('end', 'error', utils.translate('try_again_workaround')) + payload = {'redirect_uri': utils.config('redirect_uri'), 'code': code, 'grant_type': 'authorization_code', diff --git a/packages/spotify/spotify_utils.py b/packages/spotify/spotify_utils.py index 910fedca7..b18f05161 100644 --- a/packages/spotify/spotify_utils.py +++ b/packages/spotify/spotify_utils.py @@ -86,7 +86,11 @@ def track_to_html_list(info): "
          • from album {}
          • " \ "
          • by \'{}\'
          • " \ "
          • popularity: {}%
          • " \ - "
          • playing time: {}
          ".format(info['name'], info['album'], info['artist'], info['popularity'], info['duration']) + "
        • playing time: {}
        ".format(info['name'], + info['album'], + info['artist'], + info['popularity'], + info['duration']) def display_track(track): @@ -147,7 +151,10 @@ def display_album(album): def create_tracks_table(tracks): - tracks_table = '' + tracks_table = '
        Track nameArtistPlaying time
        ' \ + '' \ + '' \ + '' for track in tracks: tracks_table += ''.format(track['name'], track['artist'], @@ -178,7 +185,9 @@ def display_playlist(playlist): for tr in result['items']: # tracks may be null if not available anymore if tr: - tracks.append({"name": tr['track']['name'], "duration": format_time(tr['track']['duration_ms']), "artist": tr['track']['artists'][0]['name']}) + tracks.append({"name": tr['track']['name'], + "duration": format_time(tr['track']['duration_ms']), + "artist": tr['track']['artists'][0]['name']}) info['tracks'] = tracks @@ -191,7 +200,11 @@ def artist_to_html_list(info): "
      • popularity: {}%
      • " \ "
      • followers: {}
      • " \ "
      • genres: {}
      • " \ - "

        Top tracks

        {}".format(info['name'], info['popularity'], info['num_followers'], info['genres'], info['top_tracks']) + "

        Top tracks

        {}".format(info['name'], + info['popularity'], + info['num_followers'], + info['genres'], + info['top_tracks']) def display_artist(artist):
        Track nameArtistPlaying time
        {}{}{}