From 0a72b4678f93e8ecfe1c88fd79a3314a06fe7621 Mon Sep 17 00:00:00 2001 From: aj Date: Mon, 30 Sep 2019 14:19:00 +0100 Subject: [PATCH] added player blueprint, fixed decorators --- package-lock.json | 403 +++++++++++------- package.json | 12 +- spotify/api/__init__.py | 3 +- spotify/api/api.py | 178 +------- spotify/api/decorators.py | 111 +++++ spotify/api/player.py | 142 ++++++ spotify/spotify.py | 3 +- .../spotify.py => tasks/create_playlist.py} | 0 8 files changed, 509 insertions(+), 343 deletions(-) create mode 100644 spotify/api/decorators.py create mode 100644 spotify/api/player.py rename spotify/{api/spotify.py => tasks/create_playlist.py} (100%) diff --git a/package-lock.json b/package-lock.json index a3f9fdc..2420730 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,12 +5,12 @@ "requires": true, "dependencies": { "@babel/cli": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.5.5.tgz", - "integrity": "sha512-UHI+7pHv/tk9g6WXQKYz+kmXTI77YtuY3vqC59KIqcoWEjsJJSG6rAxKaLsgj3LDyadsPrCB929gVOKM6Hui0w==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.6.2.tgz", + "integrity": "sha512-JDZ+T/br9pPfT2lmAMJypJDTTTHM9ePD/ED10TRjRzJVdEVy+JB3iRlhzYmTt5YkNgHvxWGlUVnLtdv6ruiDrQ==", "dev": true, "requires": { - "chokidar": "^2.0.4", + "chokidar": "^2.1.8", "commander": "^2.8.1", "convert-source-map": "^1.1.0", "fs-readdir-recursive": "^1.1.0", @@ -20,6 +20,29 @@ "output-file-sync": "^2.0.0", "slash": "^2.0.0", "source-map": "^0.5.0" + }, + "dependencies": { + "chokidar": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "dev": true, + "optional": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + } + } } }, "@babel/code-frame": { @@ -32,18 +55,18 @@ } }, "@babel/core": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.5.5.tgz", - "integrity": "sha512-i4qoSr2KTtce0DmkuuQBV4AuQgGPUcPXMr9L5MyYAtk06z068lQ10a4O009fe5OB/DfNV+h+qqT7ddNV8UnRjg==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.6.2.tgz", + "integrity": "sha512-l8zto/fuoZIbncm+01p8zPSDZu/VuuJhAfA7d/AbzM09WR7iVhavvfNDYCNpo1VvLk6E6xgAoP9P+/EMJHuRkQ==", "dev": true, "requires": { "@babel/code-frame": "^7.5.5", - "@babel/generator": "^7.5.5", - "@babel/helpers": "^7.5.5", - "@babel/parser": "^7.5.5", - "@babel/template": "^7.4.4", - "@babel/traverse": "^7.5.5", - "@babel/types": "^7.5.5", + "@babel/generator": "^7.6.2", + "@babel/helpers": "^7.6.2", + "@babel/parser": "^7.6.2", + "@babel/template": "^7.6.0", + "@babel/traverse": "^7.6.2", + "@babel/types": "^7.6.0", "convert-source-map": "^1.1.0", "debug": "^4.1.0", "json5": "^2.1.0", @@ -53,6 +76,17 @@ "source-map": "^0.5.0" }, "dependencies": { + "@babel/types": { + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.1.tgz", + "integrity": "sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, "debug": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", @@ -71,16 +105,28 @@ } }, "@babel/generator": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.5.5.tgz", - "integrity": "sha512-ETI/4vyTSxTzGnU2c49XHv2zhExkv9JHLTwDAFz85kmcwuShvYG2H08FwgIguQf4JC75CBnXAUM5PqeF4fj0nQ==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.6.2.tgz", + "integrity": "sha512-j8iHaIW4gGPnViaIHI7e9t/Hl8qLjERI6DcV9kEpAIDJsAOrcnXqRS7t+QbhL76pwbtqP+QCQLL0z1CyVmtjjQ==", "dev": true, "requires": { - "@babel/types": "^7.5.5", + "@babel/types": "^7.6.0", "jsesc": "^2.5.1", "lodash": "^4.17.13", - "source-map": "^0.5.0", - "trim-right": "^1.0.1" + "source-map": "^0.5.0" + }, + "dependencies": { + "@babel/types": { + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.1.tgz", + "integrity": "sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/helper-annotate-as-pure": { @@ -286,14 +332,27 @@ } }, "@babel/helpers": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.5.5.tgz", - "integrity": "sha512-nRq2BUhxZFnfEn/ciJuhklHvFOqjJUD5wpx+1bxUF2axL9C+v4DE/dmp5sT2dKnpOs4orZWzpAZqlCy8QqE/7g==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.6.2.tgz", + "integrity": "sha512-3/bAUL8zZxYs1cdX2ilEE0WobqbCmKWr/889lf2SS0PpDcpEIY8pb1CCyz0pEcX3pEb+MCbks1jIokz2xLtGTA==", "dev": true, "requires": { - "@babel/template": "^7.4.4", - "@babel/traverse": "^7.5.5", - "@babel/types": "^7.5.5" + "@babel/template": "^7.6.0", + "@babel/traverse": "^7.6.2", + "@babel/types": "^7.6.0" + }, + "dependencies": { + "@babel/types": { + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.1.tgz", + "integrity": "sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/highlight": { @@ -308,9 +367,9 @@ } }, "@babel/parser": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.5.5.tgz", - "integrity": "sha512-E5BN68cqR7dhKan1SfqgPGhQ178bkVKpXTPEXnFJBrEt8/DKRZlybmy+IgYLTeN7tp1R5Ccmbm2rBk17sHYU3g==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.6.2.tgz", + "integrity": "sha512-mdFqWrSPCmikBoaBYMuBulzTIKuXVPtEISFbRRVNwMWpCms/hmE2kRq0bblUHaNRKrjRlmVbx1sDHmjmRgD2Xg==", "dev": true }, "@babel/plugin-proposal-async-generator-functions": { @@ -345,9 +404,9 @@ } }, "@babel/plugin-proposal-object-rest-spread": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.5.5.tgz", - "integrity": "sha512-F2DxJJSQ7f64FyTVl5cw/9MWn6naXGdk3Q3UhDbFEEHv+EilCPoeRD3Zh/Utx1CJz4uyKlQ4uH+bJPbEhMV7Zw==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.6.2.tgz", + "integrity": "sha512-LDBXlmADCsMZV1Y9OQwMc0MyGZ8Ta/zlD9N67BfQT8uYwkRswiu2hU6nJKrjrt/58aH/vqfQlR/9yId/7A2gWw==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", @@ -365,14 +424,14 @@ } }, "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.4.4.tgz", - "integrity": "sha512-j1NwnOqMG9mFUOH58JTFsA/+ZYzQLUZ/drqWUqxCYLGeu2JFZL8YrNC9hBxKmWtAuOCHPcRpgv7fhap09Fb4kA==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.6.2.tgz", + "integrity": "sha512-NxHETdmpeSCtiatMRYWVJo7266rrvAC3DTeG5exQBIH/fMIUK7ejDNznBbn3HQl/o9peymRRg7Yqkx6PdUXmMw==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", "@babel/helper-regex": "^7.4.4", - "regexpu-core": "^4.5.4" + "regexpu-core": "^4.6.0" } }, "@babel/plugin-syntax-async-generators": { @@ -459,9 +518,9 @@ } }, "@babel/plugin-transform-block-scoping": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.5.5.tgz", - "integrity": "sha512-82A3CLRRdYubkG85lKwhZB0WZoHxLGsJdux/cOVaJCJpvYFl1LVzAIFyRsa7CvXqW8rBM4Zf3Bfn8PHt5DP0Sg==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.6.2.tgz", + "integrity": "sha512-zZT8ivau9LOQQaOGC7bQLQOT4XPkPXgN2ERfUgk1X8ql+mVkLc4E8eKk+FO3o0154kxzqenWCorfmEXpEZcrSQ==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", @@ -494,23 +553,23 @@ } }, "@babel/plugin-transform-destructuring": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.5.0.tgz", - "integrity": "sha512-YbYgbd3TryYYLGyC7ZR+Tq8H/+bCmwoaxHfJHupom5ECstzbRLTch6gOQbhEY9Z4hiCNHEURgq06ykFv9JZ/QQ==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.6.0.tgz", + "integrity": "sha512-2bGIS5P1v4+sWTCnKNDZDxbGvEqi0ijeqM/YqHtVGrvG2y0ySgnEEhXErvE9dA0bnIzY9bIzdFK0jFA46ASIIQ==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-transform-dotall-regex": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.4.4.tgz", - "integrity": "sha512-P05YEhRc2h53lZDjRPk/OektxCVevFzZs2Gfjd545Wde3k+yFDbXORgl2e0xpbq8mLcKJ7Idss4fAg0zORN/zg==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.6.2.tgz", + "integrity": "sha512-KGKT9aqKV+9YMZSkowzYoYEiHqgaDhGmPNZlZxX6UeHC4z30nC1J9IrZuGqbYFB1jaIGdv91ujpze0exiVK8bA==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", "@babel/helper-regex": "^7.4.4", - "regexpu-core": "^4.5.4" + "regexpu-core": "^4.6.0" } }, "@babel/plugin-transform-duplicate-keys": { @@ -581,9 +640,9 @@ } }, "@babel/plugin-transform-modules-commonjs": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.5.0.tgz", - "integrity": "sha512-xmHq0B+ytyrWJvQTc5OWAC4ii6Dhr0s22STOoydokG51JjWhyYo5mRPXoi+ZmtHQhZZwuXNN+GG5jy5UZZJxIQ==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.6.0.tgz", + "integrity": "sha512-Ma93Ix95PNSEngqomy5LSBMAQvYKVe3dy+JlVJSHEXZR5ASL9lQBedMiCyVtmTLraIDVRE3ZjTZvmXXD2Ozw3g==", "dev": true, "requires": { "@babel/helper-module-transforms": "^7.4.4", @@ -614,12 +673,12 @@ } }, "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.4.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.4.5.tgz", - "integrity": "sha512-z7+2IsWafTBbjNsOxU/Iv5CvTJlr5w4+HGu1HovKYTtgJ362f7kBcQglkfmlspKKZ3bgrbSGvLfNx++ZJgCWsg==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.6.2.tgz", + "integrity": "sha512-xBdB+XOs+lgbZc2/4F5BVDVcDNS4tcSKQc96KmlqLEAwz6tpYPEvPdmDfvVG0Ssn8lAhronaRs6Z6KSexIpK5g==", "dev": true, "requires": { - "regexp-tree": "^0.1.6" + "regexpu-core": "^4.6.0" } }, "@babel/plugin-transform-new-target": { @@ -729,9 +788,9 @@ } }, "@babel/plugin-transform-spread": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.2.2.tgz", - "integrity": "sha512-KWfky/58vubwtS0hLqEnrWJjsMGaOeSBn90Ezn5Jeg9Z8KKHmELbP1yGylMlm5N6TPKeY9A2+UaSYLdxahg01w==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.6.2.tgz", + "integrity": "sha512-DpSvPFryKdK1x+EDJYCy28nmAaIMdxmhot62jAXF/o99iA33Zj2Lmcp3vDmz+MUh0LNYVPvfj5iC3feb3/+PFg==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" @@ -767,20 +826,20 @@ } }, "@babel/plugin-transform-unicode-regex": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.4.4.tgz", - "integrity": "sha512-il+/XdNw01i93+M9J9u4T7/e/Ue/vWfNZE4IRUQjplu2Mqb/AFTDimkw2tdEdSH50wuQXZAbXSql0UphQke+vA==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.6.2.tgz", + "integrity": "sha512-orZI6cWlR3nk2YmYdb0gImrgCUwb5cBUwjf6Ks6dvNVvXERkwtJWOQaEOjPiu0Gu1Tq6Yq/hruCZZOOi9F34Dw==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", "@babel/helper-regex": "^7.4.4", - "regexpu-core": "^4.5.4" + "regexpu-core": "^4.6.0" } }, "@babel/preset-env": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.5.5.tgz", - "integrity": "sha512-GMZQka/+INwsMz1A5UEql8tG015h5j/qjptpKY2gJ7giy8ohzU710YciJB5rcKsWGWHiW3RUnHib0E5/m3Tp3A==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.6.2.tgz", + "integrity": "sha512-Ru7+mfzy9M1/YTEtlDS8CD45jd22ngb9tXnn64DvQK3ooyqSw9K4K9DUWmYknTTVk4TqygL9dqCrZgm1HMea/Q==", "dev": true, "requires": { "@babel/helper-module-imports": "^7.0.0", @@ -788,9 +847,9 @@ "@babel/plugin-proposal-async-generator-functions": "^7.2.0", "@babel/plugin-proposal-dynamic-import": "^7.5.0", "@babel/plugin-proposal-json-strings": "^7.2.0", - "@babel/plugin-proposal-object-rest-spread": "^7.5.5", + "@babel/plugin-proposal-object-rest-spread": "^7.6.2", "@babel/plugin-proposal-optional-catch-binding": "^7.2.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-proposal-unicode-property-regex": "^7.6.2", "@babel/plugin-syntax-async-generators": "^7.2.0", "@babel/plugin-syntax-dynamic-import": "^7.2.0", "@babel/plugin-syntax-json-strings": "^7.2.0", @@ -799,11 +858,11 @@ "@babel/plugin-transform-arrow-functions": "^7.2.0", "@babel/plugin-transform-async-to-generator": "^7.5.0", "@babel/plugin-transform-block-scoped-functions": "^7.2.0", - "@babel/plugin-transform-block-scoping": "^7.5.5", + "@babel/plugin-transform-block-scoping": "^7.6.2", "@babel/plugin-transform-classes": "^7.5.5", "@babel/plugin-transform-computed-properties": "^7.2.0", - "@babel/plugin-transform-destructuring": "^7.5.0", - "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/plugin-transform-destructuring": "^7.6.0", + "@babel/plugin-transform-dotall-regex": "^7.6.2", "@babel/plugin-transform-duplicate-keys": "^7.5.0", "@babel/plugin-transform-exponentiation-operator": "^7.2.0", "@babel/plugin-transform-for-of": "^7.4.4", @@ -811,10 +870,10 @@ "@babel/plugin-transform-literals": "^7.2.0", "@babel/plugin-transform-member-expression-literals": "^7.2.0", "@babel/plugin-transform-modules-amd": "^7.5.0", - "@babel/plugin-transform-modules-commonjs": "^7.5.0", + "@babel/plugin-transform-modules-commonjs": "^7.6.0", "@babel/plugin-transform-modules-systemjs": "^7.5.0", "@babel/plugin-transform-modules-umd": "^7.2.0", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.4.5", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.6.2", "@babel/plugin-transform-new-target": "^7.4.4", "@babel/plugin-transform-object-super": "^7.5.5", "@babel/plugin-transform-parameters": "^7.4.4", @@ -822,17 +881,30 @@ "@babel/plugin-transform-regenerator": "^7.4.5", "@babel/plugin-transform-reserved-words": "^7.2.0", "@babel/plugin-transform-shorthand-properties": "^7.2.0", - "@babel/plugin-transform-spread": "^7.2.0", + "@babel/plugin-transform-spread": "^7.6.2", "@babel/plugin-transform-sticky-regex": "^7.2.0", "@babel/plugin-transform-template-literals": "^7.4.4", "@babel/plugin-transform-typeof-symbol": "^7.2.0", - "@babel/plugin-transform-unicode-regex": "^7.4.4", - "@babel/types": "^7.5.5", + "@babel/plugin-transform-unicode-regex": "^7.6.2", + "@babel/types": "^7.6.0", "browserslist": "^4.6.0", "core-js-compat": "^3.1.1", "invariant": "^2.2.2", "js-levenshtein": "^1.1.3", "semver": "^5.5.0" + }, + "dependencies": { + "@babel/types": { + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.1.tgz", + "integrity": "sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/preset-react": { @@ -849,41 +921,65 @@ } }, "@babel/runtime": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.5.5.tgz", - "integrity": "sha512-28QvEGyQyNkB0/m2B4FU7IEZGK2NUrcMtT6BZEFALTguLk+AUT6ofsHtPk5QyjAdUkpMJ+/Em+quwz4HOt30AQ==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.6.2.tgz", + "integrity": "sha512-EXxN64agfUqqIGeEjI5dL5z0Sw0ZwWo1mLTi4mQowCZ42O59b7DRpZAnTC6OqdF28wMBMFKNb/4uFGrVaigSpg==", "requires": { "regenerator-runtime": "^0.13.2" } }, "@babel/template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.4.4.tgz", - "integrity": "sha512-CiGzLN9KgAvgZsnivND7rkA+AeJ9JB0ciPOD4U59GKbQP2iQl+olF1l76kJOupqidozfZ32ghwBEJDhnk9MEcw==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.6.0.tgz", + "integrity": "sha512-5AEH2EXD8euCk446b7edmgFdub/qfH1SN6Nii3+fyXP807QRx9Q73A2N5hNwRRslC2H9sNzaFhsPubkS4L8oNQ==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.4.4", - "@babel/types": "^7.4.4" + "@babel/parser": "^7.6.0", + "@babel/types": "^7.6.0" + }, + "dependencies": { + "@babel/types": { + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.1.tgz", + "integrity": "sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/traverse": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.5.5.tgz", - "integrity": "sha512-MqB0782whsfffYfSjH4TM+LMjrJnhCNEDMDIjeTpl+ASaUvxcjoiVCo/sM1GhS1pHOXYfWVCYneLjMckuUxDaQ==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.6.2.tgz", + "integrity": "sha512-8fRE76xNwNttVEF2TwxJDGBLWthUkHWSldmfuBzVRmEDWOtu4XdINTgN7TDWzuLg4bbeIMLvfMFD9we5YcWkRQ==", "dev": true, "requires": { "@babel/code-frame": "^7.5.5", - "@babel/generator": "^7.5.5", + "@babel/generator": "^7.6.2", "@babel/helper-function-name": "^7.1.0", "@babel/helper-split-export-declaration": "^7.4.4", - "@babel/parser": "^7.5.5", - "@babel/types": "^7.5.5", + "@babel/parser": "^7.6.2", + "@babel/types": "^7.6.0", "debug": "^4.1.0", "globals": "^11.1.0", "lodash": "^4.17.13" }, "dependencies": { + "@babel/types": { + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.1.tgz", + "integrity": "sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, "debug": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", @@ -1591,14 +1687,14 @@ } }, "browserslist": { - "version": "4.6.6", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.6.6.tgz", - "integrity": "sha512-D2Nk3W9JL9Fp/gIcWei8LrERCS+eXu9AM5cfXA8WEZ84lFks+ARnZ0q/R69m2SV3Wjma83QDDPxsNKXUwdIsyA==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.7.0.tgz", + "integrity": "sha512-9rGNDtnj+HaahxiVV38Gn8n8Lr8REKsel68v1sPFfIGEK6uSXTY3h9acgiT1dZVtOOUtifo/Dn8daDQ5dUgVsA==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30000984", - "electron-to-chromium": "^1.3.191", - "node-releases": "^1.1.25" + "caniuse-lite": "^1.0.30000989", + "electron-to-chromium": "^1.3.247", + "node-releases": "^1.1.29" } }, "buffer": { @@ -1677,9 +1773,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30000985", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000985.tgz", - "integrity": "sha512-1ngiwkgqAYPG0JSSUp3PUDGPKKY59EK7NrGGX+VOxaKCNzRbNc7uXMny+c3VJfZxtoK3wSImTvG9T9sXiTw2+w==", + "version": "1.0.30000997", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000997.tgz", + "integrity": "sha512-BQLFPIdj2ntgBNWp9Q64LGUIEmvhKkzzHhUHR3CD5A9Lb7ZKF20/+sgadhFap69lk5XmK1fTUleDclaRFvgVUA==", "dev": true }, "chalk": { @@ -1888,14 +1984,13 @@ "dev": true }, "core-js-compat": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.1.4.tgz", - "integrity": "sha512-Z5zbO9f1d0YrJdoaQhphVAnKPimX92D6z8lCGphH89MNRxlL1prI9ExJPqVwP0/kgkQCv8c4GJGT8X16yUncOg==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.2.1.tgz", + "integrity": "sha512-MwPZle5CF9dEaMYdDeWm73ao/IflDH+FjeJCWEADcEgFSE9TLimFKwJsfmkwzI8eC0Aj0mgvMDjeQjrElkz4/A==", "dev": true, "requires": { - "browserslist": "^4.6.2", - "core-js-pure": "3.1.4", - "semver": "^6.1.1" + "browserslist": "^4.6.6", + "semver": "^6.3.0" }, "dependencies": { "semver": { @@ -1906,12 +2001,6 @@ } } }, - "core-js-pure": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.1.4.tgz", - "integrity": "sha512-uJ4Z7iPNwiu1foygbcZYJsJs1jiXrTTCvxfLDXNhI/I+NHbSIEyr548y4fcsCEyWY0XgfAG/qqaunJ1SThHenA==", - "dev": true - }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -2157,9 +2246,9 @@ } }, "electron-to-chromium": { - "version": "1.3.200", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.200.tgz", - "integrity": "sha512-PUurrpyDA74MuAjJRD+79ss5BqJlU3mdArRbuu4wO/dt6jc3Ic/6BDmFJxkdwbfq39cHf/XKm2vW98XSvut9Dg==", + "version": "1.3.269", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.269.tgz", + "integrity": "sha512-t2ZTfo07HxkxTOUbIwMmqHBSnJsC9heqJUm7LwQu2iSk0wNhG4H5cMREtb8XxeCrQABDZ6IqQKY3yZq+NfAqwg==", "dev": true }, "elliptic": { @@ -3314,16 +3403,16 @@ } }, "history": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/history/-/history-4.9.0.tgz", - "integrity": "sha512-H2DkjCjXf0Op9OAr6nJ56fcRkTSNrUiv41vNJ6IswJjif6wlpZK0BTfFbi7qK9dXLSYZxkq5lBsj3vUjlYBYZA==", + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", + "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", "requires": { "@babel/runtime": "^7.1.2", "loose-envify": "^1.2.0", - "resolve-pathname": "^2.2.0", + "resolve-pathname": "^3.0.0", "tiny-invariant": "^1.0.2", "tiny-warning": "^1.0.0", - "value-equal": "^0.4.0" + "value-equal": "^1.0.1" } }, "hmac-drbg": { @@ -4067,9 +4156,9 @@ } }, "node-releases": { - "version": "1.1.26", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.26.tgz", - "integrity": "sha512-fZPsuhhUHMTlfkhDLGtfY80DSJTjOcx+qD1j5pqPkuhUHVS7xHZIg9EE4DHK8O3f0zTxXHX5VIkDG8pu98/wfQ==", + "version": "1.1.33", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.33.tgz", + "integrity": "sha512-I0V30bWQEoHb+10W8oedVoUrdjW5wIkYm0w7vvcrPO95pZY738m1k77GF5sO0vKg5eXYg9oGtrMAETbgZGm11A==", "dev": true, "requires": { "semver": "^5.3.0" @@ -4607,9 +4696,9 @@ } }, "react": { - "version": "16.9.0", - "resolved": "https://registry.npmjs.org/react/-/react-16.9.0.tgz", - "integrity": "sha512-+7LQnFBwkiw+BobzOF6N//BdoNw0ouwmSJTEm9cglOOmsg/TMiFHZLe2sEoN5M7LgJTj9oHH0gxklfnQe66S1w==", + "version": "16.10.1", + "resolved": "https://registry.npmjs.org/react/-/react-16.10.1.tgz", + "integrity": "sha512-2bisHwMhxQ3XQz4LiJJwG3360pY965pTl/MRrZYxIBKVj4fOHoDs5aZAkYXGxDRO1Li+SyjTAilQEbOmtQJHzA==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", @@ -4617,25 +4706,25 @@ } }, "react-dom": { - "version": "16.9.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.9.0.tgz", - "integrity": "sha512-YFT2rxO9hM70ewk9jq0y6sQk8cL02xm4+IzYBz75CQGlClQQ1Bxq0nhHF6OtSbit+AIahujJgb/CPRibFkMNJQ==", + "version": "16.10.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.10.1.tgz", + "integrity": "sha512-SmM4ZW0uug0rn95U8uqr52I7UdNf6wdGLeXDmNLfg3y5q5H9eAbdjF5ubQc3bjDyRrvdAB2IKG7X0GzSpnn5Mg==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", "prop-types": "^15.6.2", - "scheduler": "^0.15.0" + "scheduler": "^0.16.1" } }, "react-is": { - "version": "16.8.6", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz", - "integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==" + "version": "16.10.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.10.1.tgz", + "integrity": "sha512-BXUMf9sIOPXXZWqr7+c5SeOKJykyVr2u0UDzEf4LNGc6taGkQe1A9DFD07umCIXz45RLr9oAAwZbAJ0Pkknfaw==" }, "react-router": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.0.1.tgz", - "integrity": "sha512-EM7suCPNKb1NxcTZ2LEOWFtQBQRQXecLxVpdsP4DW4PbbqYWeRiLyV/Tt1SdCrvT2jcyXAXmVTmzvSzrPR63Bg==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.1.1.tgz", + "integrity": "sha512-ozTXqxKZsn4GfZqpG5rVFHSSxlNuDoMNxgyjM+mFJVhqlnPwwkRsAPkDm1PcNjBdYxMzqAhtz48HkQB6fSYaAQ==", "requires": { "@babel/runtime": "^7.1.2", "history": "^4.9.0", @@ -4650,15 +4739,15 @@ } }, "react-router-dom": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.0.1.tgz", - "integrity": "sha512-zaVHSy7NN0G91/Bz9GD4owex5+eop+KvgbxXsP/O+iW1/Ln+BrJ8QiIR5a6xNPtrdTvLkxqlDClx13QO1uB8CA==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.1.1.tgz", + "integrity": "sha512-r8R8H0Vt2ISqpk02rR6VZBLk+JZdR6pZV+h9K1y0ISh3/G4GGByNevYBS69x6czcOcWVRcZmXjwY8l9UBCKV+w==", "requires": { "@babel/runtime": "^7.1.2", "history": "^4.9.0", "loose-envify": "^1.3.1", "prop-types": "^15.6.2", - "react-router": "5.0.1", + "react-router": "5.1.1", "tiny-invariant": "^1.0.2", "tiny-warning": "^1.0.0" } @@ -4728,20 +4817,14 @@ "safe-regex": "^1.1.0" } }, - "regexp-tree": { - "version": "0.1.11", - "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.11.tgz", - "integrity": "sha512-7/l/DgapVVDzZobwMCCgMlqiqyLFJ0cduo/j+3BcDJIB+yJdsYCfKuI3l/04NV+H/rfNRdPIDbXNZHM9XvQatg==", - "dev": true - }, "regexpu-core": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.5.4.tgz", - "integrity": "sha512-BtizvGtFQKGPUcTy56o3nk1bGRp4SZOTYrDtGNlqCQufptV5IkkLN6Emw+yunAJjzf+C9FQFtvq7IoA3+oMYHQ==", + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.6.0.tgz", + "integrity": "sha512-YlVaefl8P5BnFYOITTNzDvan1ulLOiXJzCNZxduTIosN17b87h3bvG9yHMoHaRuo88H4mQ06Aodj5VtYGGGiTg==", "dev": true, "requires": { "regenerate": "^1.4.0", - "regenerate-unicode-properties": "^8.0.2", + "regenerate-unicode-properties": "^8.1.0", "regjsgen": "^0.5.0", "regjsparser": "^0.6.0", "unicode-match-property-ecmascript": "^1.0.4", @@ -4802,9 +4885,9 @@ "dev": true }, "resolve": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.1.tgz", - "integrity": "sha512-vIpgF6wfuJOZI7KKKSP+HmiKggadPQAdsp5HiC1mvqnfp0gF1vdwgBWZIdrVft9pgqoMFQN+R7BSWZiBxx+BBw==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", + "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==", "dev": true, "requires": { "path-parse": "^1.0.6" @@ -4849,9 +4932,9 @@ "dev": true }, "resolve-pathname": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-2.2.0.tgz", - "integrity": "sha512-bAFz9ld18RzJfddgrO2e/0S2O81710++chRMUxHjXOYKF6jTAMrUNZrEZ1PvV0zlhfjidm08iRPdTLPno1FuRg==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", + "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==" }, "resolve-url": { "version": "0.2.1", @@ -4909,9 +4992,9 @@ } }, "scheduler": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.15.0.tgz", - "integrity": "sha512-xAefmSfN6jqAa7Kuq7LIJY0bwAPG3xlCj0HMEBQk1lxYiDKZscY2xJ5U/61ZTrYbmNQbXa+gc7czPkVo11tnCg==", + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.16.1.tgz", + "integrity": "sha512-MIuie7SgsqMYOdCXVFZa8SKoNorJZUWHW8dPgto7uEHn1lX3fg2Gu0TzgK8USj76uxV7vB5eRMnZs/cdEHg+cg==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" @@ -5456,12 +5539,6 @@ "repeat-string": "^1.6.1" } }, - "trim-right": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", - "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", - "dev": true - }, "tslib": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", @@ -5659,9 +5736,9 @@ "dev": true }, "value-equal": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-0.4.0.tgz", - "integrity": "sha512-x+cYdNnaA3CxvMaTX0INdTCN8m8aF2uY9BvEqmxuYp8bL09cs/kWVQPVGcA35fMktdOsP69IgU7wFj/61dJHEw==" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", + "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==" }, "vm-browserify": { "version": "1.1.0", diff --git a/package.json b/package.json index 1cad4fc..df2f3e9 100644 --- a/package.json +++ b/package.json @@ -20,14 +20,14 @@ "homepage": "https://github.com/Sarsoo/spotify-web#readme", "dependencies": { "axios": "^0.19.0", - "react": "^16.9.0", - "react-dom": "^16.9.0", - "react-router-dom": "^5.0.1" + "react": "^16.10.1", + "react-dom": "^16.10.1", + "react-router-dom": "^5.1.1" }, "devDependencies": { - "@babel/cli": "^7.5.5", - "@babel/core": "^7.5.5", - "@babel/preset-env": "^7.5.5", + "@babel/cli": "^7.6.2", + "@babel/core": "^7.6.2", + "@babel/preset-env": "^7.6.2", "@babel/preset-react": "^7.0.0", "babel-loader": "^8.0.6", "clean-webpack-plugin": "^3.0.0", diff --git a/spotify/api/__init__.py b/spotify/api/__init__.py index fce51c0..69f0936 100644 --- a/spotify/api/__init__.py +++ b/spotify/api/__init__.py @@ -1 +1,2 @@ -from .api import blueprint as api_blueprint \ No newline at end of file +from .api import blueprint as api_blueprint +from .player import blueprint as player_blueprint diff --git a/spotify/api/api.py b/spotify/api/api.py index 41bee14..64b37ae 100644 --- a/spotify/api/api.py +++ b/spotify/api/api.py @@ -4,19 +4,15 @@ import os import datetime import json import logging -import functools from google.cloud import firestore from google.cloud import tasks_v2 from google.protobuf import timestamp_pb2 from werkzeug.security import check_password_hash, generate_password_hash +from spotify.api.decorators import login_required, login_or_basic_auth, admin_required, gae_cron, cloud_task from spotify.tasks.run_user_playlist import run_user_playlist as run_user_playlist from spotify.tasks.play_user_playlist import play_user_playlist as play_user_playlist -from spotframework.model.track import SpotifyTrack -from spotframework.model.uri import Uri -from spotframework.model.service import Context -from spotframework.player.player import Player import spotify.db.database as database @@ -29,168 +25,6 @@ task_path = tasker.queue_path('sarsooxyz', 'europe-west2', 'spotify-executions') logger = logging.getLogger(__name__) -def is_logged_in(): - if 'username' in session: - return True - else: - return False - - -def is_basic_authed(): - if request.authorization: - if request.authorization.get('username', None) and request.authorization.get('password', None): - if database.check_user_password(request.authorization.username, request.authorization.password): - return True - - return False - - -def login_required(func): - @functools.wraps(func) - def login_required_wrapper(*args, **kwargs): - if is_logged_in(): - return func(username=session['username'], *args, **kwargs) - else: - logger.warning('user not logged in') - return jsonify({'error': 'not logged in'}), 401 - return login_required_wrapper - - -def login_or_basic_auth(func): - @functools.wraps(func) - def login_or_basic_auth_wrapper(*args, **kwargs): - if is_logged_in(): - return func(username=session['username'], *args, **kwargs) - elif is_basic_authed(): - return func(username=request.authorization.username, *args, **kwargs) - else: - logger.warning('user not logged in') - return jsonify({'error': 'not logged in'}), 401 - - return login_or_basic_auth_wrapper - - -def admin_required(func): - @functools.wraps(func) - def admin_required_wrapper(*args, **kwargs): - user_dict = database.get_user_doc_ref(session['username']).get().to_dict() - - if user_dict: - if user_dict['type'] == 'admin': - return func(*args, **kwargs) - else: - logger.warning(f'{user_dict["username"]} not authorized') - return jsonify({'status': 'error', 'message': 'unauthorized'}), 401 - else: - logger.warning('user not logged in') - return jsonify({'error': 'not logged in'}), 401 - - return admin_required_wrapper - - -def gae_cron(func): - @functools.wraps(func) - def gae_cron_wrapper(*args, **kwargs): - - if request.headers.get('X-Appengine-Cron', None): - return func(*args, **kwargs) - else: - logger.warning('user not logged in') - return jsonify({'status': 'error', 'message': 'unauthorised'}), 401 - - return gae_cron_wrapper - - -def cloud_task(func): - @functools.wraps(func) - def cloud_task_wrapper(*args, **kwargs): - - if request.headers.get('X-AppEngine-QueueName', None): - return func(*args, **kwargs) - else: - logger.warning('non tasks request') - return jsonify({'status': 'error', 'message': 'unauthorised'}), 401 - - return cloud_task_wrapper - - -@blueprint.route('/play', methods=['POST']) -@login_or_basic_auth -def play(username=None): - user_ref = database.get_user_doc_ref(username) - user_dict = user_ref.get().to_dict() - - if user_dict.get('spotify_linked', None): - request_json = request.get_json() - - if 'uri' in request_json: - try: - uri = Uri(request_json['uri']) - if uri.object_type in [Uri.ObjectType.album, Uri.ObjectType.artist, Uri.ObjectType.playlist]: - context = Context(uri) - - net = database.get_authed_network(username) - - player = Player(net) - device = None - if 'device_name' in request_json: - devices = net.get_available_devices() - device = next((i for i in devices if i.name == request_json['device_name']), None) - - player.play(context=context, device=device) - logger.info(f'played {uri}') - return jsonify({'message': 'played', 'status': 'success'}), 200 - else: - return jsonify({'error': "uri not context compatible"}), 400 - except ValueError: - return jsonify({'error': "malformed uri provided"}), 400 - elif 'playlist_name' in request_json: - net = database.get_authed_network(username) - playlists = net.get_playlists() - if playlists is not None: - playlist_to_play = next((i for i in playlists if i.name == request_json['playlist_name']), None) - - if playlist_to_play is not None: - player = Player(net) - device = None - if 'device_name' in request_json: - devices = net.get_available_devices() - device = next((i for i in devices if i.name == request_json['device_name']), None) - - player.play(context=Context(playlist_to_play.uri), device=device) - logger.info(f'played {request_json["playlist_name"]}') - return jsonify({'message': 'played', 'status': 'success'}), 200 - else: - return jsonify({'error': f"playlist {request_json['playlist_name']} not found"}), 404 - else: - return jsonify({'error': "playlists not returned"}), 400 - elif 'tracks' in request_json: - try: - uris = [Uri(i) for i in request_json['tracks']] - uris = [SpotifyTrack.get_uri_shell(i) for i in uris if i.object_type == Uri.ObjectType.track] - - if len(uris) > 0: - net = database.get_authed_network(username) - - player = Player(net) - device = None - if 'device_name' in request_json: - devices = net.get_available_devices() - device = next((i for i in devices if i.name == request_json['device_name']), None) - - player.play(tracks=uris, device=device) - logger.info(f'played tracks') - return jsonify({'message': 'played', 'status': 'success'}), 200 - else: - return jsonify({'error': "no track uris provided"}), 400 - except ValueError: - return jsonify({'error': "uris failed to parse"}), 400 - else: - return jsonify({'error': "no uris provided"}), 400 - else: - return jsonify({'error': "spotify not linked"}), 400 - - @blueprint.route('/playlists', methods=['GET']) @login_or_basic_auth def get_playlists(username=None): @@ -296,7 +130,7 @@ def playlist(username=None): # if playlist_id is None or playlist_shuffle is None: # return jsonify({'error': 'parts and id required'}), 400 - from spotify.api.spotify import create_playlist as create_playlist + from spotify.tasks.create_playlist import create_playlist as create_playlist to_add = { 'name': playlist_name, @@ -439,9 +273,9 @@ def user(username=None): @blueprint.route('/users', methods=['GET']) -@login_required +@login_or_basic_auth @admin_required -def users(): +def users(username=None): dic = { 'accounts': [] @@ -629,9 +463,9 @@ def run_user_task(): @blueprint.route('/playlist/run/users', methods=['GET']) -@login_required +@login_or_basic_auth @admin_required -def run_users(): +def run_users(username=None): execute_all_users() return jsonify({'message': 'executed all users', 'status': 'success'}), 200 diff --git a/spotify/api/decorators.py b/spotify/api/decorators.py new file mode 100644 index 0000000..8c8e2d2 --- /dev/null +++ b/spotify/api/decorators.py @@ -0,0 +1,111 @@ +import functools +import logging + +from flask import session, request, jsonify + +from spotify.db import database as database + +logger = logging.getLogger(__name__) + + +def is_logged_in(): + if 'username' in session: + return True + else: + return False + + +def is_basic_authed(): + if request.authorization: + if request.authorization.get('username', None) and request.authorization.get('password', None): + if database.check_user_password(request.authorization.username, request.authorization.password): + return True + + return False + + +def login_required(func): + @functools.wraps(func) + def login_required_wrapper(*args, **kwargs): + if is_logged_in(): + return func(username=session['username'], *args, **kwargs) + else: + logger.warning('user not logged in') + return jsonify({'error': 'not logged in'}), 401 + return login_required_wrapper + + +def login_or_basic_auth(func): + @functools.wraps(func) + def login_or_basic_auth_wrapper(*args, **kwargs): + if is_logged_in(): + return func(username=session['username'], *args, **kwargs) + elif is_basic_authed(): + return func(username=request.authorization.username, *args, **kwargs) + else: + logger.warning('user not logged in') + return jsonify({'error': 'not logged in'}), 401 + + return login_or_basic_auth_wrapper + + +def admin_required(func): + @functools.wraps(func) + def admin_required_wrapper(*args, **kwargs): + user_dict = database.get_user_doc_ref(kwargs.get('username')).get().to_dict() + + if user_dict: + if user_dict['type'] == 'admin': + return func(*args, **kwargs) + else: + logger.warning(f'{user_dict["username"]} not authorized') + return jsonify({'status': 'error', 'message': 'unauthorized'}), 401 + else: + logger.warning('user not logged in') + return jsonify({'error': 'not logged in'}), 401 + + return admin_required_wrapper + + +def spotify_link_required(func): + @functools.wraps(func) + def spotify_link_required_wrapper(*args, **kwargs): + user_dict = database.get_user_doc_ref(kwargs.get('username')).get().to_dict() + + if user_dict: + if user_dict['spotify_linked']: + return func(*args, **kwargs) + else: + logger.warning(f'{user_dict["username"]} spotify not linked') + return jsonify({'status': 'error', 'message': 'spotify not linked'}), 401 + else: + logger.warning('user not logged in') + return jsonify({'error': 'not logged in'}), 401 + + return spotify_link_required_wrapper + + +def gae_cron(func): + @functools.wraps(func) + def gae_cron_wrapper(*args, **kwargs): + + if request.headers.get('X-Appengine-Cron', None): + return func(*args, **kwargs) + else: + logger.warning('user not logged in') + return jsonify({'status': 'error', 'message': 'unauthorised'}), 401 + + return gae_cron_wrapper + + +def cloud_task(func): + @functools.wraps(func) + def cloud_task_wrapper(*args, **kwargs): + + if request.headers.get('X-AppEngine-QueueName', None): + return func(*args, **kwargs) + else: + logger.warning('non tasks request') + return jsonify({'status': 'error', 'message': 'unauthorised'}), 401 + + return cloud_task_wrapper diff --git a/spotify/api/player.py b/spotify/api/player.py new file mode 100644 index 0000000..d0bceeb --- /dev/null +++ b/spotify/api/player.py @@ -0,0 +1,142 @@ +from flask import Blueprint, request, jsonify + +import logging + +from google.cloud import firestore + +from spotify.api.decorators import login_or_basic_auth, spotify_link_required +import spotify.db.database as database + +from spotframework.model.track import SpotifyTrack +from spotframework.model.uri import Uri +from spotframework.model.service import Context +from spotframework.player.player import Player + +blueprint = Blueprint('player_api', __name__) +db = firestore.Client() + +logger = logging.getLogger(__name__) + + +@blueprint.route('/play', methods=['POST']) +@login_or_basic_auth +@spotify_link_required +def play(username=None): + request_json = request.get_json() + + if 'uri' in request_json: + try: + uri = Uri(request_json['uri']) + if uri.object_type in [Uri.ObjectType.album, Uri.ObjectType.artist, Uri.ObjectType.playlist]: + context = Context(uri) + + net = database.get_authed_network(username) + + player = Player(net) + device = None + if 'device_name' in request_json: + devices = net.get_available_devices() + device = next((i for i in devices if i.name == request_json['device_name']), None) + + player.play(context=context, device=device) + logger.info(f'played {uri}') + return jsonify({'message': 'played', 'status': 'success'}), 200 + else: + return jsonify({'error': "uri not context compatible"}), 400 + except ValueError: + return jsonify({'error': "malformed uri provided"}), 400 + elif 'playlist_name' in request_json: + net = database.get_authed_network(username) + playlists = net.get_playlists() + if playlists is not None: + playlist_to_play = next((i for i in playlists if i.name == request_json['playlist_name']), None) + + if playlist_to_play is not None: + player = Player(net) + device = None + if 'device_name' in request_json: + devices = net.get_available_devices() + device = next((i for i in devices if i.name == request_json['device_name']), None) + + player.play(context=Context(playlist_to_play.uri), device=device) + logger.info(f'played {request_json["playlist_name"]}') + return jsonify({'message': 'played', 'status': 'success'}), 200 + else: + return jsonify({'error': f"playlist {request_json['playlist_name']} not found"}), 404 + else: + return jsonify({'error': "playlists not returned"}), 400 + elif 'tracks' in request_json: + try: + uris = [Uri(i) for i in request_json['tracks']] + uris = [SpotifyTrack.get_uri_shell(i) for i in uris if i.object_type == Uri.ObjectType.track] + + if len(uris) > 0: + net = database.get_authed_network(username) + + player = Player(net) + device = None + if 'device_name' in request_json: + devices = net.get_available_devices() + device = next((i for i in devices if i.name == request_json['device_name']), None) + + player.play(tracks=uris, device=device) + logger.info(f'played tracks') + return jsonify({'message': 'played', 'status': 'success'}), 200 + else: + return jsonify({'error': "no track uris provided"}), 400 + except ValueError: + return jsonify({'error': "uris failed to parse"}), 400 + else: + return jsonify({'error': "no uris provided"}), 400 + + +@blueprint.route('/next', methods=['POST']) +@login_or_basic_auth +@spotify_link_required +def next_track(username=None): + net = database.get_authed_network(username) + player = Player(net) + + player.next() + return jsonify({'message': 'skipped', 'status': 'success'}), 200 + + +@blueprint.route('/shuffle', methods=['POST']) +@login_or_basic_auth +@spotify_link_required +def shuffle(username=None): + request_json = request.get_json() + + if 'state' in request_json: + if isinstance(request_json['state'], bool): + net = database.get_authed_network(username) + player = Player(net) + + player.shuffle(state=request_json['state']) + return jsonify({'message': f'shuffle set to {request_json["state"]}', 'status': 'success'}), 200 + else: + return jsonify({'error': "state not a boolean"}), 400 + else: + return jsonify({'error': "no state provided"}), 400 + + +@blueprint.route('/volume', methods=['POST']) +@login_or_basic_auth +@spotify_link_required +def volume(username=None): + request_json = request.get_json() + + if 'volume' in request_json: + if isinstance(request_json['volume'], int): + if 0 <= request_json['volume'] <= 100: + net = database.get_authed_network(username) + player = Player(net) + + player.set_volume(value=request_json['volume']) + return jsonify({'message': f'volume set to {request_json["volume"]}', 'status': 'success'}), 200 + else: + return jsonify({'error': "volume must be between 0 and 100"}), 400 + else: + return jsonify({'error': "volume not a integer"}), 400 + else: + return jsonify({'error': "no volume provided"}), 400 diff --git a/spotify/spotify.py b/spotify/spotify.py index fd8d9d7..be85c5a 100644 --- a/spotify/spotify.py +++ b/spotify/spotify.py @@ -4,7 +4,7 @@ from google.cloud import firestore import os from spotify.auth import auth_blueprint -from spotify.api import api_blueprint +from spotify.api import api_blueprint, player_blueprint db = firestore.Client() @@ -12,6 +12,7 @@ app = Flask(__name__, static_folder=os.path.join(os.path.dirname(__file__), '..' app.secret_key = db.collection(u'spotify').document(u'config').get().to_dict()['secret_key'] app.register_blueprint(auth_blueprint, url_prefix='/auth') app.register_blueprint(api_blueprint, url_prefix='/api') +app.register_blueprint(player_blueprint, url_prefix='/api/player') @app.route('/') diff --git a/spotify/api/spotify.py b/spotify/tasks/create_playlist.py similarity index 100% rename from spotify/api/spotify.py rename to spotify/tasks/create_playlist.py