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 0000000000..e69de29bb2 diff --git a/packages/math/calculator.py b/packages/math/calculator.py new file mode 100644 index 0000000000..8d3d61a5e5 --- /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 0000000000..e69de29bb2 diff --git a/packages/math/config/config.sample.json b/packages/math/config/config.sample.json new file mode 100644 index 0000000000..2db8332a66 --- /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 0000000000..e69de29bb2 diff --git a/packages/math/data/answers/.gitkeep b/packages/math/data/answers/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/math/data/answers/en.json b/packages/math/data/answers/en.json new file mode 100644 index 0000000000..0db3279e44 --- /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 0000000000..0db3279e44 --- /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 0000000000..e69de29bb2 diff --git a/packages/math/data/expressions/.gitkeep b/packages/math/data/expressions/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/math/data/expressions/en.json b/packages/math/data/expressions/en.json new file mode 100644 index 0000000000..dc1b328968 --- /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 0000000000..dc1b328968 --- /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 0000000000..8d3d61a5e5 --- /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 54fc9aed1c..8f7d0a5544 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 0c48ad65e2..dfe4598e87 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 4448119da6..128c6d8103 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 cad748c1ec..091e8e35ad 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 73ae623fec..3248e093d6 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 17f6828b65..0040112bb1 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 812a7cffb5..17ad9aab09 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 a37deb6cff..02b2feb72a 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 8ed8575032..817d136a98 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 0000000000..e69de29bb2 diff --git a/packages/spotify/config/config.sample.json b/packages/spotify/config/config.sample.json new file mode 100644 index 0000000000..2db8332a66 --- /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 0000000000..e69de29bb2 diff --git a/packages/spotify/data/db/.gitkeep b/packages/spotify/data/db/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/spotify/data/expressions/.gitkeep b/packages/spotify/data/expressions/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/spotify/spotify.py b/packages/spotify/spotify.py new file mode 100644 index 0000000000..76f8d8e010 --- /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 113340bfc6..1a3bd6778c 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 0000000000..239e232435 --- /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 0000000000..eb05d7d082 --- /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 0000000000..0db3279e44 --- /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 0000000000..1b67342898 --- /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 0000000000..dc1b328968 --- /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 76f8d8e010..c55cfdde26 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 239e232435..528d401e98 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 eb05d7d082..509c12b1a0 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 1b67342898..5734d95104 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 c55cfdde26..4428fd6625 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 528d401e98..f46d1d71aa 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 5734d95104..1f71e6f465 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 4428fd6625..aae0699375 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 509c12b1a0..2fae6de6c9 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 1f71e6f465..3a62b4ad35 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 aae0699375..24869896fd 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 2fae6de6c9..89a432dd91 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 3a62b4ad35..b6a5104c4e 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 24869896fd..07e4e7ca78 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 89a432dd91..b3326988f9 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 b6a5104c4e..819befcc38 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 0000000000..d51c41ad7a --- /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 2db8332a66..611b68efc6 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 b3326988f9..43bb78b61c 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 819befcc38..ce5534b4bf 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 07e4e7ca78..7754ecfb1c 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 ce5534b4bf..c28a3123df 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 7754ecfb1c..a46209722a 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 a46209722a..a37d760d5e 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 43bb78b61c..bdb06eecdc 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 c28a3123df..3d33927ad4 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 a37d760d5e..7b5b0e1379 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 7b5b0e1379..469a489d9d 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 bdb06eecdc..ca49ac76de 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 3d33927ad4..0cd97b48ca 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 469a489d9d..a83fc249b8 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 0000000000..f22e509f09 --- /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 0000000000..7f8d3dcece --- /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 7ab7f1d911..1704340115 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 0cd97b48ca..dd16d59075 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 a83fc249b8..1b7f0e267b 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 f22e509f09..8943ebc5d1 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 f46d1d71aa..7462effca3 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 ca49ac76de..8c2b812953 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 1b7f0e267b..3cdcf28f67 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 8943ebc5d1..473436eea6 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 7462effca3..0000000000 --- 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 611b68efc6..05e6d6936a 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 8c2b812953..6c451f4a66 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 dd16d59075..9ea006610b 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 3cdcf28f67..f3ac56088b 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 473436eea6..f2aa96d809 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 7f8d3dcece..3bcf574cde 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 6c451f4a66..010d7c254a 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 f3ac56088b..e5be08eb04 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 f2aa96d809..e75a9e55ee 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 d51c41ad7a..b030d13d19 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 9ea006610b..9819ebf1d6 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 e5be08eb04..ff2daa3c37 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 e75a9e55ee..97c8804fc7 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 05e6d6936a..8ef612971d 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 ff2daa3c37..3ecde3e1bd 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 97c8804fc7..910fedca7b 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 e69de29bb2..0000000000 diff --git a/packages/math/calculator.py b/packages/math/calculator.py deleted file mode 100644 index 8d3d61a5e5..0000000000 --- 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 e69de29bb2..0000000000 diff --git a/packages/math/config/config.sample.json b/packages/math/config/config.sample.json deleted file mode 100644 index 2db8332a66..0000000000 --- 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 e69de29bb2..0000000000 diff --git a/packages/math/data/answers/.gitkeep b/packages/math/data/answers/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/math/data/answers/en.json b/packages/math/data/answers/en.json deleted file mode 100644 index 0db3279e44..0000000000 --- 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 0db3279e44..0000000000 --- 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 e69de29bb2..0000000000 diff --git a/packages/math/data/expressions/.gitkeep b/packages/math/data/expressions/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/math/data/expressions/en.json b/packages/math/data/expressions/en.json deleted file mode 100644 index dc1b328968..0000000000 --- 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 dc1b328968..0000000000 --- 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 8d3d61a5e5..0000000000 --- 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 8ef612971d..6a4d32ca76 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 6a4d32ca76..867bf4f145 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 910fedca7b..8f607388d6 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 3ecde3e1bd..0448221a00 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 8f607388d6..910fedca7b 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 010d7c254a..b7ca6cf5a9 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 0448221a00..cc951fbef3 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 910fedca7b..b18f05161c 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
        {}{}{}