diff --git a/.gitignore b/.gitignore index f0d2c8b..12fa6a3 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,8 @@ npm-debug.log node_modules bower_components package.zip +package-lock.json +app/package-lock.json *.map js/* .idea diff --git a/app/package.json b/app/package.json index 6807339..4949c2b 100644 --- a/app/package.json +++ b/app/package.json @@ -1,7 +1,7 @@ { "name": "sonos-controller-unofficial", "description": "Unoffical sonos controller for linux.", - "version": "0.0.10", + "version": "0.0.11", "author": "Pascal Opitz ", "main": "main.js", "dependencies": { @@ -14,7 +14,7 @@ "lodash": "^4.17.4", "moment": "^2.18.1", "omit-keys": "^0.1.0", - "preact": "^7.2.1", + "preact": "^8.1.0", "preact-virtual-list": "^0.3.1", "request": "^2.81.0", "uuid": "^3.0.0", diff --git a/package.json b/package.json index d855519..f581884 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sonos-controller-unofficial", - "version": "0.0.10", + "version": "0.0.11", "description": "Unoffical sonos controller for linux", "main": "app/main.js", "scripts": { @@ -45,20 +45,19 @@ }, "license": "ISC", "devDependencies": { - "babel": "^6.23.0", - "babel-cli": "^6.24.0", - "babel-eslint": "^7.2.1", + "babel-cli": "^6.24.1", + "babel-eslint": "^7.2.3", "babel-plugin-syntax-async-functions": "^6.13.0", - "babel-plugin-transform-async-to-module-method": "^6.22.0", - "babel-plugin-transform-class-properties": "^6.23.0", - "babel-plugin-transform-react-jsx": "^6.23.0", + "babel-plugin-transform-async-to-module-method": "^6.24.1", + "babel-plugin-transform-class-properties": "^6.24.1", + "babel-plugin-transform-react-jsx": "^6.24.1", "babel-preset-node5": "^12.0.1", - "babel-preset-react": "^6.23.0", - "babel-register": "^6.24.0", - "electron": "^1.6.4", - "electron-builder": "^16.5.1", - "eslint": "^3.18.0", - "eslint-plugin-react": "^6.10.3", + "babel-preset-react": "^6.24.1", + "babel-register": "^6.24.1", + "electron": "^1.7.2", + "electron-builder": "^18.1.0", + "eslint": "^3.19.0", + "eslint-plugin-react": "^7.0.1", "material-design-icons": "^3.0.1", "watch": "^1.0.2" }, diff --git a/src/ui/actions/MusicServiceManagementActions.js b/src/ui/actions/MusicServiceManagementActions.js index 790ec23..08996b5 100644 --- a/src/ui/actions/MusicServiceManagementActions.js +++ b/src/ui/actions/MusicServiceManagementActions.js @@ -39,8 +39,18 @@ export default { }, getLink (client) { - if(client.auth === 'DeviceLink' || client.auth === 'AppLink') { - client.getDeviceLinkCode().then((link) => { + + let promise; + + if(client.auth === 'DeviceLink') { + promise = client.getDeviceLinkCode(); + } + + if(client.auth === 'AppLink') { + promise = client.getAppLink(); + } + + promise.then((link) => { Dispatcher.dispatch({ actionType: Constants.MUSICSERVICE_ADD_LINK_RECEIVED, link: link @@ -60,10 +70,9 @@ export default { }); window.clearInterval(poll); }); - }) + }); }, 5000); }); - } }, addAnonymousService (client) { diff --git a/src/ui/components/BrowserList.jsx b/src/ui/components/BrowserList.jsx index e83b903..9a94cfe 100644 --- a/src/ui/components/BrowserList.jsx +++ b/src/ui/components/BrowserList.jsx @@ -158,7 +158,7 @@ class BrowserList extends Component { } return ( -
+
{headlineNodes}
    { @@ -218,6 +219,25 @@ class MusicServiceClient { }); } + getAppLink() { + + let headers = [ + '', + '' + ].join(''); + + let body = ['', + '', SonosService.householdId, '', + ''].join(''); + + return this._doRequest(this._serviceDefinition.SecureUri, 'getAppLink', body, headers) + .then((res) => { + let resp = xml2json(stripNamespaces(res)); + let obj = resp['Envelope']['Body']['getAppLinkResponse']['getAppLinkResult']; + return obj.authorizeAccount.deviceLink; + }); + } + getDeviceAuthToken(linkCode, linkDeviceId) { let headers = ['', diff --git a/src/ui/services/PandoraServiceClient.js b/src/ui/services/PandoraServiceClient.js new file mode 100644 index 0000000..adfa549 --- /dev/null +++ b/src/ui/services/PandoraServiceClient.js @@ -0,0 +1,120 @@ +// import _ from 'lodash'; + +// import moment from 'moment'; + +import crypto from 'crypto'; +import requestHelper from 'request'; +// import xml2json from 'jquery-xml2json'; + +// import SonosService from '../services/SonosService'; + +// const NS = 'http://www.sonos.com/Services/1.1'; +// const deviceProviderName = 'SonosControllerForChrome'; +// const RUNTIME_ID = 'pascal-sonos-app'; + +const PARTNER_USERNAME = 'iphone'; +const PARTNER_PASSWORD = 'P2E4FC0EAD3*878N92B2CDp34I0B1@388137C'; +const PARTNER_DEVICEID = 'IP01'; +const API_VERSION = '5'; + +const ENCRYPTION_PASSWORD = '721^26xE22776'; +const DECRYPTION_PASSWORD = '20zE1E47BE57$51'; + +function pandoraEncode(str) { + const buf = Buffer.from(ENCRYPTION_PASSWORD, "base64"); + let cipher = crypto.createCipheriv('bf-ecb', buf, Buffer.alloc(0)); + + let encrypted = cipher.update(str, 'utf8', 'hex'); + encrypted += cipher.final('hex'); + console.log(encrypted); + + return encrypted; +} + +function pandoraDecode(str) { + const buf = Buffer.from(DECRYPTION_PASSWORD, "base64"); + const decipher = crypto.createDecipheriv('bf-ecb', buf, Buffer.alloc(0)); + + var decrypted = decipher.update(str, 'hex', 'utf8'); + decrypted += decipher.final('utf8'); + console.log(decrypted); + + return decrypted; +} + +class PandoraServiceClient { + + constructor(serviceDefinition) { + this._serviceDefinition = serviceDefinition; + + this.name = serviceDefinition.Name; + this.auth = serviceDefinition.Auth; + } + + _getPartnerCode() { + return new Promise((resolve, reject) => { + requestHelper({ + method: 'POST', + uri: this._serviceDefinition.SecureUri + '?method=auth.partnerLogin', + json: { + username: PARTNER_USERNAME, + password: PARTNER_PASSWORD, + deviceModel: PARTNER_DEVICEID, + version: API_VERSION, + } + }, (err, res, body) => { + if(err) { + reject(err); + } else if (res.statusCode != 200) { + reject(body); + } else { + this.partnerAuthToken = body.result.partnerAuthToken; + this.partnerId = body.result.partnerId; + resolve(body); + } + }); + }); + } + + _userLogin(username, password) { + + let params = { + username, + password, + partnerAuthToken: this.partnerAuthToken, + loginType: 'user', + returnStationList: true, + }; + let body = pandoraEncode(JSON.stringify(params)); + + return new Promise((resolve, reject) => { + requestHelper({ + method: 'POST', + uri: this._serviceDefinition.SecureUri + '?method=auth.userLogin&partner_id=' + this.partnerId + '&partner_auth_token=' + escape(this.partnerAuthToken), + body, + }, (err, res, body) => { + if(err) { + reject(err); + } else if (res.statusCode != 200) { + reject(body); + } else { + resolve(body); + } + }); + }); + } + + getSessionId(username, password) { + return Promise.resolve() + .then(() => { + return this._getPartnerCode(); + }) + .then(() => { + return this._userLogin(username, password); + }); + } + + +} + +export default PandoraServiceClient;